4

Hi I am having a initial state as empty object,

let [value,setValue] = useState({});

function onSubmit(){
  setValue({"new Object"})
}

and updating it onSubmit in button click. Is there any way to implement this scenario in useReducer and also here i want to create a new state instead of mutating it. Your help or suggestion is much appreciated.

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
SDK
  • 1,356
  • 3
  • 19
  • 44
  • The code in your question does not warrant using useReducer, useState would work fine with code given. – HMR May 16 '20 at 11:45

2 Answers2

5

Both hooks define an initial state and return a setter and a getter to handle the state:

  [value,fn] = useReducer(...); // or useState(...);

But, in the above code snippet, the behavior of fn differs from useReducer to useState. Also, useReducer needs 2 parameters while useState needs only 1. Basically:

  • useReducer: expect 2 parameters, (a) a function (known as the reducer function), and (b) the initial value for the state. The reducer function will be called to modify the state whenever you call fn with some value. And the value passed to fn will be passed through to the reducer function (as the second parameter).

  • useState: expect only 1 parameter, the initial value for the state. It will just replace the state with the value passed as an argument to fn (optionally, you can also pass a function to the fn, that receives the current state as a parameter and returns the new state).

Example of useState:

const { useState } = React;

const App = () => {
  const initialState = { called: false, name: 'Jane' };
  const [value, setValue] = useState(initialState);
  const replaceState = () => {
    setValue({ 
      called: true, 
      fullName: `${value.name ? value.name : 'Just'} Fonda` 
    });
  }
  return (
    <div className="App">
      <button onClick={replaceState}>Replace Name</button>
      <button onClick={(e) => setValue(initialState)}>Reset</button>
      <pre>{JSON.stringify(value)}</pre>
      {value.called 
       ? <pre>Notice that there's no 'name' attribute anymore</pre> 
       : null}
    </div>
  );
}

ReactDOM.render(<App />,root);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

Example of useReducer:

You must build a function (the reducer) that will modify the state. That function must receive 2 parameters: the actual state and any value you need to modify the state.

Also, this function must return a value that React will set as the new state.

If you have ever used the redux pattern, this behavior must be familiar to you. So, if you, like everyone, want to stick to the redux pattern, use action as the name of the second parameter of the reducer function (that will be used to modify the state). It'll be an object containing 2 attributes: type and payload.

The logic inside the reducer process the action, by using the type (string) to decide how to modify the state using the payload (any). It's not uncommon to have a switch-case inside the reducer. So, let's follow the tradition and build a reducer using this redux-like pattern.

const { useReducer } = React;

// Notice the two parameters of the reducer:
//   state: current state
//   payload: object with the shape {type: string, payload: any}
// Also, notice that the reducer MUST return a value to be used
//   as the new state. Remember that if you return nothing
//   javascript will return undefined
const reducer = (state, action) => {
  switch(action.type) {
    case 'inc': return state + action.payload;
    case 'dec': return state - action.payload;

    // for any other type, return the current state
    default: return state;
  }
}

const App = () => {
  // specify the reducer and an initial value for the state
  const [value, setValue] = useReducer(reducer, 0);

  const inc = (e) => setValue({type: 'inc', payload: 1});
  const dec = (e) => setValue({type: 'dec', payload: 1});

  return (
    <div className="App">
      <button onClick={inc}>Increment</button>
      <button onClick={dec}>Decrement</button>
      Counter:{value}
    </div>
  );
}

ReactDOM.render(<App />,root);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

As a final side note, the setValue name as I used on the snippet above is not even close to the most common name used for the function returned by useReducer. It's much more common to call it dispatch.

julianobrasil
  • 8,954
  • 2
  • 33
  • 55
4

The reducer used by useReducer is a function that is called with the current state, and an action to change the state by. The action can be anything you want.

You can easily create useState with useReducer by returning the new state (the action) from the reducer:

const { useReducer } = React;

function reducer(_, newState) {
  return newState;
}

function App() {
  const [value, setValue] = useReducer(reducer, { a: "2" });


  function onSubmit() {
    setValue({ b: 2 });
  }
  return (
    <div className="App">
      <button onClick={onSubmit}>onSubmit</button>
      {JSON.stringify(value)}
    </div>
  );
}

ReactDOM.render(
  <App />,
  root
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<Div id="root"></div>

However, if you just need to replace the current state, useState is enough. If, however, you want to add to the current state by replacing it, useReducer is a good choice:

const { useReducer } = React;

function reducer(state, newState) {
  return {
    ...state,
    ...newState
  };
}

function App() {
  const [value, setValue] = useReducer(reducer, { a: "1" });


  function onSubmit() {
    setValue({ b: 2 });
  }
  return (
    <div className="App">
      <button onClick={onSubmit}>onSubmit</button>
      {JSON.stringify(value)}
    </div>
  );
}

ReactDOM.render(
  <App />,
  root
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<Div id="root"></div>

The state after passing through the reducer would be { a: 1, b: 2 }.

The classic way is to pass an action with a type, and a payload. According to the type The reducer can then "decide" what to do with the payload, and the current state to generate the new state. See useReducer api documentation.

Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • I have a added your code in codeSandbox i am not able to get the initial State onload . Could u please look into that https://codesandbox.io/s/patient-cherry-mvie8?file=/src/App.js – SDK May 16 '20 at 11:57
  • @DhanushKumarS - added snippet to the answer. Replace `useState` with `useReducer` in your code. It was an error in my answer. – Ori Drori May 16 '20 at 12:04