Mateusz
Mateusz

Reputation: 95

Is there a generic way to set state in React Hooks? How to manage multiple states?

I have a question, if I can use useState generic in React Hooks, just like I can do this in React Components while managing multiple states?

state = {
  input1: "",
  input2: "",
  input3: ""
  // .. more states
};

handleChange = (event) => {
  const { name, value } = event.target;
  this.setState({
    [name]: value,
  });
};

Upvotes: 7

Views: 10050

Answers (3)

Dennis Vash
Dennis Vash

Reputation: 53874

Yes, with hooks you can manage complex state (without 3rd party library) in three ways, where the main reasoning is managing state ids and their corresponding elements.

  1. Manage a single object with multiple states (notice that an array is an object).
  2. Use useReducer if (1) is too complex.
  3. Use multiple useState for every key-value pair (consider the readability and maintenance of it).

Check out this:

// Ids-values pairs.
const complexStateInitial = {
  input1: "",
  input2: "",
  input3: ""
  // .. more states
};

function reducer(state, action) {
  return { ...state, [action.type]: action.value };
}

export default function App() {
  const [fromUseState, setState] = useState(complexStateInitial);

  // handle generic state from useState
  const onChangeUseState = (e) => {
    const { name, value } = e.target;
    setState((prevState) => ({ ...prevState, [name]: value }));
  };

  const [fromReducer, dispatch] = useReducer(reducer, complexStateInitial);

  // handle generic state from useReducer
  const onChangeUseReducer = (e) => {
    const { name, value } = e.target;
    dispatch({ type: name, value });
  };
 
  return (
    <>
      <h3>useState</h3>
      <div>
        {Object.entries(fromUseState).map(([key, value]) => (
          <input
            key={key}
            name={key}
            value={value}
            onChange={onChangeUseState}
          />
        ))}
        <pre>{JSON.stringify(fromUseState, null, 2)}</pre>
      </div>

      <h3>useReducer</h3>
      <div>
        {Object.entries(fromReducer).map(([key, value]) => (
          <input
            name={key}
            key={key}
            value={value}
            onChange={onChangeUseReducer}
          />
        ))}
        <pre>{JSON.stringify(fromReducer, null, 2)}</pre>
      </div>
    </>
  );
}

Edit Handle Multiple State

Notes

  • Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Refer to React Docs.

Upvotes: 12

david
david

Reputation: 18258

The correct way to do what you're trying to do is to create your own hook that uses useState internally.

Here is an example:

// This is your generic reusable hook.
const useHandleChange = (initial) => {
  const [value, setValue] = React.useState(initial);
  const handleChange = React.useCallback(
    (event) => setValue(event.target.value), // This is the meaty part.
    []
  );
  return [value, handleChange];
}

const App = () => {
  // Here we use the hook 3 times to show it's reusable.
  const [value1, handle1] = useHandleChange('one');
  const [value2, handle2] = useHandleChange('two');
  const [value3, handle3] = useHandleChange('three');
  return <div>
    <div>
      <input onChange={handle1} value={value1} />
      <input onChange={handle2} value={value2} />
      <input onChange={handle3} value={value3} />
    </div>
    <h2>States:</h2>
    <ul>
      <li>{value1}</li>
      <li>{value2}</li>
      <li>{value3}</li>
    </ul>
  </div>
}

ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Note the use of React.useCallback to stop your hook from returning a new handler function on every render. (We don't need to specify setValue as a dependency because React guarantees that it will never change)

Upvotes: 3

Dov Rine
Dov Rine

Reputation: 840

I didn't actually test this, but it should work.

See https://reactjs.org/docs/hooks-reference.html#usestate for more info.

import React, {useState} from 'react';

const MyComponent = () => {
    const [name, setName] = useState('Default value for name');
    return (<div><button onClick={()=>setName('John Doe')}}>Set Name</button></div>);
};
export default MyComponent;

Upvotes: -3

Related Questions