Shayan
Shayan

Reputation: 2828

Should React Reducer really be a pure function?

It is said that the reducer function used in useReducer is a pure function. If I'm not wrong, "Its behaviour depends only on its input parameters -- so calling it twice with the same input parameters has the same resulting effect." (from here). And also (from here):

A reducer should:

  • never mutate its arguments

  • never generate side-effects (no API calls changing anything)

  • never call non-pure functions, functions that change their output based on factors other than their input (e.g. Date.now() or Math.random())

I have two questions regarding this:

  1. Can any one explain why the reducer must be a pure function? e.g. what can go wrong if it returns two different outputs while receiving the same input? Or, what happens if it has side-effects?
  2. Consider the following sample code:
    export function MyComponent(props: IPropTypes) {
        const reducer = (prevState, action) => {
            newState = deepClone(prevState);
            newState.count = newState.count + props.count;
            return newState;
        }
        
        const [state, dispatch] = useReducer(reducer, ....);
        
        return (<div>
             ...
             </div>)
    }

Am I right that the above reducer is not a good reducer because it is also dependent on props (which is not its input)? Why is this a bad thing?

Upvotes: 3

Views: 829

Answers (1)

Hyunwoong Kang
Hyunwoong Kang

Reputation: 530

Referring the official Redux documentation, writing reducers with pure functions increases the chance you reuse your reducers.

I think you can make your reducer pure, just by giving the props.count as an argument of a function, by putting it inside the action object. I prefer making a payload field.

Here is a working code I wrote: codesandbox, and this is how I changed your Component:

const reducer = (state, action) => {
  switch(action.type){
    case "INCREMENT":
      return {
        ...state,
        count: state.count + action.payload
      };
    default:
      return state;
  }
};

export default (props) => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  const increment = () => dispatch({ type: "INCREMENT", payload: props.count });

  return (
    <div>
      <p>Current: {state.count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

Upvotes: 3

Related Questions