6

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,
  });
};
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
Mateusz
  • 85
  • 1
  • 2
  • 6
  • Do you mean doing this with state variables? If you are using more complex state then obviously this can be done similar to how it's done in your class component example. For the former, see my [answer](https://stackoverflow.com/questions/56622854/how-do-you-destructure-a-react-usestate-hook-into-a-namespace/56623235#56623235) which demonstrates the ability to set variable state in a dynamically. – James Jul 09 '19 at 10:39
  • @ravibagul91 not necessarily, you can manage complex state with `useState` – James Jul 09 '19 at 10:40
  • @James Yeah, that's what I'm looking for. I'll delve into your linked answer – Mateusz Jul 09 '19 at 10:48

3 Answers3

12

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.

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • This does not use react hooks. – Dov Rine Jul 09 '19 at 10:37
  • This is correct, but not the way to do it using react hooks. – Monika Mangal Jul 09 '19 at 10:37
  • No. You used setState. They are not the same. – Dov Rine Jul 09 '19 at 10:40
  • @DennisVash: setState !== useState – Dov Rine Jul 09 '19 at 10:41
  • 1
    `setState` comes from `useState`: `const [state, setState] = useState(initialState);` – Dennis Vash Jul 09 '19 at 10:42
  • @DovRine you can manage complex state with `useState`, however, I guess we are to presume the OP is referring to state variables (I've asked for clarification) – James Jul 09 '19 at 10:42
  • 1
    @DennisVash: I apologize. You are correct. I didn't see your setState declaration and assumed that it was the same class setState method that OP seemed to be asking about. The confusion for me was your use of the same method name. – Dov Rine Jul 09 '19 at 10:43
  • @DennisVash I think it would be more clear if you explain that it's possible _if_ you are looking to manage complex state, state variables would require a bit of extra wizardry. – James Jul 09 '19 at 10:43
  • @James: For complex state, I think useReducer is better. – Dov Rine Jul 09 '19 at 10:45
  • @DovRine I'd argue that it completely depends on the complexity of the state, `useReducer` IMO can convolute a component with the introduction of having to fire actions - plus, if you already use Redux I think it can lead to further confusion. For reference, when I say "complex state" I'm simply refererring to state that isn't a single variable (that could simply be 2 properties) – James Jul 09 '19 at 10:47
  • OP: IMO, people normally use useState to manage single key/value pairs that used to be in traditional class state, like form variables. – Dov Rine Jul 09 '19 at 10:48
  • @James: If you use useState for a global state object, then you'll probably have unnecessary re-renders when state changes. – Dov Rine Jul 09 '19 at 10:49
  • @DovRine sounds a bit presumptuous - it completely depends on the circumstances. FWIW I'm not advocating the use of complex state in `useState`, I was merely pointing out its possible and that's what Dennis was trying to demonstrate with his answer. – James Jul 09 '19 at 10:53
  • 1
    @James: Not trying to be presumptuous. I think we actually agree. Your custom hook answer will ultimately probably be the answer OP is looking for. – Dov Rine Jul 09 '19 at 10:55
  • I added an example, please don't downvote without saying the reason – Dennis Vash Jul 09 '19 at 11:03
3

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)

david
  • 17,925
  • 4
  • 43
  • 57
-3

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;
Dov Rine
  • 810
  • 6
  • 12
  • 6
    I don't see how this answers the question? You've just given an example of how to use `setState`, how does the OP deduce from this how to set it dynamically? – James Jul 09 '19 at 10:43