编写通用的React输入钩子

时间:2019-11-25 08:30:31

标签: javascript reactjs react-hooks

我正在尝试编写一个通用的React Hook,以允许我更新对象。

我引用了以下内容:Input Hook-(来源:https://rangle.io/blog/simplifying-controlled-inputs-with-hooks/),并进行了一些更改:

import { useState } from "react";

export const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: (event) => {
        setValues({
          ...values,
          [event.target.id]: event.target.value
        })
      }
    }
  };
};

这在单级对象上效果很好:

{ name: '', type: '' }

但对于具有嵌套值的对象:

{ name: '', type: '', price: { dollar: 5, cents: 20  } }

我太确定如何替换[event.target.id]来读取嵌套的关卡对象。

有人可以建议吗?

已更新:

import { useState } from "react";

export const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: (event) => {
        // ###need make this part generic###
        // event.target.id will be "price.dollar"
        values['price']['dollar'] = event.target.value;
        setValues({
          ...values
        })
      }
    }
  };
};

1 个答案:

答案 0 :(得分:3)

通常,您的钩子应该接受namevalue来更新您的本地状态。显然,您的钩子始终会收到一个event,并且您将event.target.id提取为字段的name,并将event.target.value提取为字段的值。我建议您更新挂钩,以接收namevalue作为参数,并让使用挂钩的组件定义namevalue

基于钩子,您可以像这样更新嵌套对象。请看一下这个例子。

import React, { useState } from "react";
import ReactDOM from "react-dom";

const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: event => {
        setValues({
          ...values,
          [event.target.id]: event.target.value
        });
      }
    }
  };
};

const App = () => {
  const { values, bind } = useForm({
    name: "",
    type: "",
    price: { dollar: 5, cents: 20 }
  });
  return (
    <div>
      Hook state:
      <pre>{JSON.stringify(values, null, 4)}</pre>
      <div>
        <div>
          <label>
            Name : <br />
            <input id="name" onChange={bind.onChange} />
          </label>
        </div>
        <div>
          <label>
            Type : <br />
            <input id="type" onChange={bind.onChange} />
          </label>
        </div>
        <div>
          <label>
            Price - Dollar : <br />
            <input
              id="dollar"
              type="number"
              onChange={e => {
                bind.onChange({
                  target: {
                    id: "price",
                    value: { ...values.price, [e.target.id]: e.target.value }
                  }
                });
              }}
            />
          </label>
        </div>
        <div>
          <label>
            Price - Cents : <br />
            <input
              id="cents"
              type="number"
              onChange={e => {
                bind.onChange({
                  target: {
                    id: "price",
                    value: { ...values.price, [e.target.id]: e.target.value }
                  }
                });
              }}
            />
          </label>
        </div>
      </div>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

更新(11/25/2019):

但是,您可以按照以下说明更新钩子

const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);

  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: event => {
        setValues({
          ...values,
          [event.target.id]: event.target.value
        });
      },
      onNestedChange: (event, name) => {
        setValues({
          ...values,
          [name]: {
            ...values[name],
            [event.target.id]: event.target.value,
          }
        })
      }
    }
  };
};

然后在输入中,您可以编写以下内容:

<div>
  <label>
    Price - Dollar : <br />
    <input
      id="dollar"
      type="number"
      onChange={e => bind.onNestedChange(e, 'price')}
    />
  </label>
</div>
<div>
  <label>
    Price - Cents : <br />
    <input
      id="cents"
      type="number"
      onChange={e => bind.onNestedChange(e, 'price')}
    />
  </label>
</div>

这样,您为嵌套对象创建了另一个bind方法,也许您可​​以添加另一个名为array的东西。希望这能给您一些有关如何改进挂钩的想法。顺便说一下,有很多的方法可以做到这一点,这只是一个例子。可能有更好的方法。

更新2(11/25/2019):

我已经更新了您的useForm挂钩,现在您可以将嵌套对象属性设置为您的状态。但是,我尚未对数组进行测试,这可能会导致问题。

const useForm = initialObject => {
  const [values, setValues] = useState(initialObject);
  // Copied and modified from https://stackoverflow.com/a/18937118/11125492
  const nestedObjectSet = (obj, path, value) => {
    let schema = obj; // a moving reference to internal objects within obj
    const pList = path.split(".");
    const len = pList.length;
    for (let i = 0; i < len - 1; i++) {
      let elem = pList[i];
      if (!schema[elem]) schema[elem] = {};
      schema = schema[elem];
    }
    schema[pList[len - 1]] = value;
  };
  // handleOnChange update state value
  const handleOnChange = event => {
    let newValues = Object.assign({}, values);
    nestedObjectSet(newValues, event.target.name, event.target.value);
    setValues(newValues);
  };
  return {
    values: values || initialObject,
    setValues,
    reset: () => setValues({}),
    bind: {
      onChange: handleOnChange
    }
  };
};

您可以像这样使用它。请注意,我已将对象的keyevent.target.id更改为event.target.namekey应该在name中设置,而不是id

const App = () => {
  const { values, bind } = useForm({
    name: "",
    type: "",
    price: { dollar: 5, cents: 20 }
  });
  return (
    <div>
      Hook state:
      <pre>{JSON.stringify(values, null, 4)}</pre>
      <div>
        <div>
          <label>
            Name : <br />
            <input name="name" {...bind} />
          </label>
        </div>
        <div>
          <label>
            Type : <br />
            <input name="type" {...bind} />
          </label>
        </div>
        <div>
          <label>
            Price - Dollar : <br />
            <input name="price.dollar" type="number" {...bind} />
          </label>
        </div>
        <div>
          <label>
            Price - Cents : <br />
            <input name="price.cents" type="number" {...bind} />
          </label>
        </div>
      </div>
    </div>
  );
};

沙盒演示链接:https://codesandbox.io/s/react-useform-hook-nested-object-cqn9j?fontsize=14&hidenavigation=1&theme=dark