Search code examples
javascriptreactjsonchange

Adding handlers and state dynamically in React


I need to render plenty of inputs for every name of the array, but the problem is creating dynamically useState const and onChange handler for every rendered input. I try to create handleChange key for every item in an array and pass it to input onChange but got the "String Instead Fuction" error. How to resolve the problem in another way and also avoid code duplication?

export const myArr = [
  { name: "Crist", data: "crist", color: "lightblue", handleChange: "cristHandleChange"},
  { name: "Ruby", data: "ruby", color: "lightpink", handleChange: "rubyHandleChange"},
  { name: "Diamond", data: "diamond", color: "white", handleChange: "diamondHandleChange"},
];

export const myComponent = () => {
  const [cristQuantity, setCristQuantity] = useState(0);
  const [rubyQuantity, setRubyQuantity] = useState(0);
  const [diamondQuantity, setDiamondQuantity] = useState(0);

  function cristHandleChange(event) {
    setCristQuantity(event.target.value);
  }

  function rubyHandleChange(event) {
    setRubyQuantity(event.target.value);
  }

  function diamondHandleChange(event) {
    setDiamondQuantity(event.target.value);
  }

  return (
    <>
      {myArr
        ? myArr.map((item, index) => (
            <div className="main-inputs__wrapper" key={`item${index}`}>
              <label htmlFor={item.data}>{item.name}</label>
              <input
                type="number"
                name={item.data}
                style={{ background: item.color }}
                onChange={item.handleChange} //???
              />
            </div>
          ))
        : null}
    </>
  );
};

Solution

  • You should create one handler for all inputs, and save values in a object with a key as item.data. Such way: {crist: 1, ruby: 3, diamond: 5}

    export const myArr = [
      {
        name: "Crist",
        data: "crist",
        color: "lightblue"
      },
      {
        name: "Ruby",
        data: "ruby",
        color: "lightpink"
      },
      {
        name: "Diamond",
        data: "diamond",
        color: "white"
      }
    ];
    
    export function MyComponent() {
      // Lazy initial state 
      // https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
      const [quantities, setQuantities] = useState(() =>
        myArr.reduce((initialQuantities, item) => {
          initialQuantities[item.data] = 0;
          return initialQuantities;
        }, {})
      );
    
      // common handler for every onChange with computed property name
      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names
      const handleChange = (event) => {
        setQuantities((prevQuantities) => ({
          ...prevQuantities,
          [event.target.name]: event.target.value
        }));
      };
    
      return (
        <>
          {Array.isArray(myArr)
            ? myArr.map((item, index) => (
                <div className="main-inputs__wrapper" key={`item${index}`}>
                  <label htmlFor={item.data}>{item.name}</label>
                  <input
                    type="number"
                    name={item.data}
                    style={{ background: item.color }}
                    onChange={handleChange}
                    value={quantities[item.data] || ""}
                  />
                </div>
              ))
            : null}
        </>
      );
    }