Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React infinite loop dispatch, from useReducer hook

The main logic of the component is that when the page is loaded, need to save the parameters from the URL (token) and pass them from the dispatch() function. To do this, I have a state value were initially null is stored, but after rendering the component I add a token.

component.js

const [state, dispatch] = useReducer()
const [value, setValue] = useState(null)

  useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    setValue(token)
  }, [value])

   useEffect(() => {
    if (value && value !== null) {
      // console.log(1)
      dispatch({
        type: 'TEST_TYPE',
        payload: { data: value },
      })
    }
  }, [value])

When I want to call the dispatch() function in useEffect() the component is reloaded until the memory is full. I tested the second method, called the dispatch() function into useCallback() to call once if there are new changes. But the dispatch function is not called

const dis = useCallback(() => dispatch, [])

  useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    if (token) {
      dis({
         type: 'TEST_TYPE',
         payload: { data: token },
      })
    }
  }, [dis])

How can I properly make dispatch work once when the component is loaded and without an infinite loop?

like image 839
stepbystep Avatar asked Dec 19 '20 13:12

stepbystep


People also ask

How do I use dispatch in useReducer?

Dispatching an action Dispatch is just like a function which we can pass around to other components through props. You must have noticed that useReducer returns two values in an array. The first one is the state object, and the second one is a function called dispatch. This is what is used to dispatch an action.

What does useReducer Dispatch Return?

1. useReducer() The useReducer(reducer, initialState) hook accept 2 arguments: the reducer function and the initial state. The hook then returns an array of 2 items: the current state and the dispatch function.

Is useReducer better than Redux?

Can useReducer replace Redux? The useReducer hook should be used in components that have complex logic behind it. It shows as the main confusion with the Redux library, because developers tend to think that useReducer could replace the state manager library. But in fact, its use should be restricted to components.

Is useReducer better than useState?

The useReducer hook is usually recommended when the state becomes complex, with state values depending on each other or when the next state depends on the previous one. However, many times you can simply bundle your separate useState statements into one object and continue to use useState .


3 Answers

Just like Rostyslav said, you're not using useReducer in the right way. This is different from the useState hook: in that one, the parameter that it receives will serve as a default value for the defined variable within the component' state, which is why it's possible to leave it blank. When you're going to modify its contents, you just need to update its value by using the setter function returned by the hook.

However, useReducer requires that you explicitly define the way in which the data will change according to certain actions of your choice. It's necessary for this method to receive at least two parameters: the reducer function and an initial state which will be the starting point for upcoming data transformations.

The aim of the reducer function is to perform changes on the state in response to the dispatched actions. This method will receive the current state and an action object (which is the value that you're sending via dispatch). Depending on the received action, the reducer method must then apply some transformations in the data and return a new object that will override the current state.

The following piece of code illustrates a reducer that would work in your case. By default, the token property of the state will be set to null. As you can see in the code of the reducer, when an action of type UPDATE_TOKEN is triggered, the token property of your state will be updated to contain the token bundled into the action payload.

const initialState = {
  token: null
};

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

Since you'll be storing the token into the store by dispatching an action, there's no need for you to use useState to save it into a value property beforehand. If you need to access the token from anywhere in your component, you can retrieve it directly from your appState variable, as in the code below:

const App = () => {
  const [appState, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    const query = window.location.search || "";
    const queryString = new URLSearchParams(query || "");
    const token = queryString.get("token");

    if (token) {
      dispatch({
        type: "UPDATE_TOKEN",
        payload: { token }
      });
    }
  }, []);

  return <div className="value">Token: {appState.token}</div>;
};

The full working example can be found here.

Even though this should work, you may have to reconsider if you really need useReducer or not. Adding reducers and actions adds complexity to the code, so it only make sense for larger data structures where more logic is required to update them. In this particular example it doesn't seems worth the effort, as the only action you need to perform is to change the value of the token variable: since this variable is merely a string, this can be easily achieved by just using useState.

like image 185
LonelyPrincess Avatar answered Oct 28 '22 00:10

LonelyPrincess


You said that : I have a state value where initially null is stored, but after rendering the component I add a token. if that is the case then you are doing it incorrectly .Instead of:

useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    setValue(token)
  }, [value])

it should be:

useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    setValue(token)
  }, [])

Now as soon as your component renders ,value will get updated. Now after this the below useEffect will come into picture:

useEffect(() => {
    if (value && value !== null) {
      // console.log(1)
      dispatch({
        type: 'TEST_TYPE',
        payload: { data: value },
      })
    }
  }, [value])

and dispatch will be called.

like image 25
Sakshi Avatar answered Oct 27 '22 23:10

Sakshi


When using useReducer hook you have to specify the reducer function and the initial state.

function reducer(state, action) {
  return action.payload;
}

function YourComponent() {
  const [state, dispatch] = useReducer(reducer, { data: null });

...

Then you can just utilize your state from useReducer instead of storing duplicated data in both state from useReducer and state from useState

  useEffect(() => {
    const query = window.location.search || "";
    const queryString = new URLSearchParams(query || "");
    const token = queryString.get("token");

    if (token) {
      dispatch({
        type: "TEST_TYPE",
        payload: { data: token },
      });
    }
  }, []);

So to sum up, your code should resemble the following

import React, { useReducer, useEffect } from "react";

function reducer(state, action) {
  return action.payload;
}

export default function YourComponent() {
  const [state, dispatch] = useReducer(reducer, { data: null });

  useEffect(() => {
    const query = window.location.search || "";
    const queryString = new URLSearchParams(query || "");
    const token = queryString.get("token");

    if (token) {
      dispatch({
        type: "TEST_TYPE",
        payload: { data: token },
      });
    }
  }, []);

  return (
    <div>
      <pre>{JSON.stringify(state)}</pre>
    </div>
  );
}
like image 36
Rostyslav Avatar answered Oct 28 '22 01:10

Rostyslav