Search code examples
reactjscookiesinfinite-loopreact-cookie

useCookies from react-cookies causes rendering loop


I have a code that worked before but after I updated all the libraries, it started to render the App component continuously and React is throwing an Infinite Render Loop error: Minified React error #185

Here is my code:

import React from 'react';
import { CookiesProvider, useCookies } from 'react-cookie';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';

import store from './redux/store';

import { getUserDetailsByToken } from './redux/actions/user-actions';

const AUTHENTICATION_COOKIE = 'authat';

function App(props) {
  const { i18n, userDetailsLoaded } = props;
  const [cookies] = useCookies([AUTHENTICATION_COOKIE]);

  if (cookies[AUTHENTICATION_COOKIE] && !userDetailsLoaded) {
    store.dispatch(getUserDetailsByToken());
  }

  i18n.changeLanguage('en');

  return (
    <CookiesProvider>
      <div className="app">
        <Header />
        <main className="app-content full-height">
          ...
        </main>
      </div>
    </CookiesProvider>
  );
}

function mapStateToProps(state) {
  return {
    userDetailsLoaded: state.userReducers.userDetailsLoaded,
  };
}

export default connect(mapStateToProps)(App);

I tried to add a flag to only call the store function once considering that the user details gathering will cause the re-render, but did not work.

Also I commented out the whole cookies check without success.

If I try to comment out the line const [cookies] = useCookies([AUTHENTICATION_COOKIE]); and it stopped rendering uncontrollably but that means I would need another way to check the cookies when the application starts.


Solution

  • Both store.dispatch(getUserDetailsByToken()); and i18n.changeLanguage('en'); calls are side effects, and should be part of the render.

    When you call store.dispatch(getUserDetailsByToken()); it probably starts a process that causes a re-render, which triggers the dispatch again, and so on...

    In addition, you should use the react-redux useDispatch and useSelector hooks, instead of wrapping the component with the connect HoC, and getting dispatch by importing the store.

    import React from 'react';
    import { CookiesProvider, useCookies } from 'react-cookie';
    import { withTranslation } from 'react-i18next';
    import { useDispatch, useSelector } from 'react-redux';
    
    import { getUserDetailsByToken } from './redux/actions/user-actions';
    
    const AUTHENTICATION_COOKIE = 'authat';
    
    function App(props) {
      const dispatch = useDispatch();
      const userDetailsLoaded = useSelector(state => state.userReducers.userDetailsLoaded);
      const { i18n } = props;
      const [cookies] = useCookies([AUTHENTICATION_COOKIE]);
      
      const authCookie = cookies[AUTHENTICATION_COOKIE];
      
      // an effect that gets called on mount or whenever authCookie or userDetailsLoaded change
      useEffect(() => {
        if (cookies[AUTHENTICATION_COOKIE] && !userDetailsLoaded) {
          dispatch(getUserDetailsByToken());
        }
      }, [dispatch, authCookie, userDetailsLoaded]);
    
      // a one time effect on mount
      useEffect(() => {
        i18n.changeLanguage('en');
      }, []);
    
      return (
        <CookiesProvider>
          <div className="app">
            <Header />
            <main className="app-content full-height">
              ...
            </main>
          </div>
        </CookiesProvider>
      );
    }
    
    export default App;