Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React JS Freezes Browser

I have a React component which has 2000 elements and based on some filter conditions I update my state, which internally causes re-rendering. Everything seems to be working fine. But when I togglefilter from 2000 elements to say 1000 elements and back&forth, the rendering takes a lot of time and sometimes the browser freezes. I did chrome timeline profiling, the major time consuming piece is rendering. Any help would be appreciated.

enter image description here

like image 571
Hari Avatar asked Jan 12 '16 16:01

Hari


2 Answers

As suggested by @enjoylife is a great step but what if you have many components structures in your view, that would be very difficult to debug even memoising the component won't be able to subside the continuous or loop rendering.

I learnt this after I ran into strange freezing and weird error that wouldn't stop any time a user logged in on the homepage. Imagine of all screens. Sometimes, you would hardly notice your component re-rending.

Detect your screen/page (loop) re-rendering with console log

const Home = () => {
  conso.log('home re-rending')
  // some hooks

  return <BigComponent />
}

As written above. The logs must not show more than a limited time deemed after a component has mounted. In my case, it's once. But if it is too much(logs) and would certainly freeze your pc. Therefore, follow the below steps carefully and retrace your steps.

enter image description here

Tips and prerequisite before trying out this proposed solution. Please make sure you have style guide setup e.g. Eslint, it's great. In my case, I reproduced the source code with cra, then sorted out the first and last listed problem which I encountered.

  1. Be careful with the use of React hooks such as useEffect especially. Avoid causing a side effect in a component. In my case, I created a reusable useUpdateEffect hook and what I intend it to solve as par the name was to detect an update of React props or window props, but it backfires, I won't share the code.

    Also, do extra check if you passed correct and expected dependencies, on this Eslint deserve an accolade.

  2. Avoid random keys in React list. Use unique and constant keys in a component list as react depend on it to identify each item. According to react library

    Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity. You may use the item index as a key as a last resort:

  3. Avoid variable name conflict in your reducer and React component. Please consider the use of style guides as your friend to avoid this fall.

    I made the stupid mistake to create a Foo class and use in its render function, which also leads to the freezing scene. Write here for anyone who could meet this problem again.follow this thread.

  4. Avoid infinite loops, Imagine rendering a lot of data at a go. this happen

    just in case you share my fate, I urge you to check your loops and make sure you do not have a += instead of -= (or vice versa). Those infinite loops can be quite a big pain in the neck.

  5. Keep your reducer as a reducer, Avoid Action creator, an API call in your reducer or using another reducer in your reducer so, for instance, reducerA in reducerB. When you call to update reducerA in reducerB, the update in reducerA would trigger an update in reducerB whereby cause page/screen to re-render multiple times. for example

// this react reducer in my case
// reducer js file - reducerB
const useBusinesses = () => {
  // reducerB as discussed above - the loading context 
  const { loading } = useLoadingContext(); // the culprit
  const [data, setData] = useState(initialState); // initial state, 
  const [state, dispatch] = useReducer(reducer, data);

  useEffect(() => setData(state), [state, setData]);

  const { businesses, errorMessage } = state;

  const setBusinesses = (payload) => dispatch({ type: `${FETCH_BUSINESSES}_SUCCESS`, data: payload });

  const setBusinessesError = (payload) =>  dispatch({ type: `${FETCH_BUSINESSES}_ERROR`, data: payload });

  const fetchBusinesses = async (lglt, type = 'food', limit = 12) => {
    try {
      // update reducerB: triggers multiple update in reducerA while requesting is pending
      loading(FETCH_BUSINESSES, true);
      const request = await API.businesses.getWithquery(
        `long=${lglt[0]}&latt=${lglt[1]}&limit=${limit}&type=${type}`
      );
      loading(FETCH_BUSINESSES, false);
      setBusinesses(request.data);
    } catch (err) {
      loading(FETCH_BUSINESSES, false);
      // if (!err.response) dispatch(alertMessage(FETCH_BUKKAS, true, 'Please check your network'));
      setBusinessesError(err.response.data);
    }
  });

  return { businesses, errorMessage, fetchBusinesses };
};

export const [BusinessesProvider, useBusinessesContext] = constate(useBusinesses);

//home js file
Home = () => {
  const { fetchBusinesses } = useBusinessContext();
  conso.log('home re-rending')
  // some hooks
  useEffect(() => {
    console.log('am i in trouble, yes!, how many troubles')
    fetchBusinesses(coordinates)
  }, [fetchBusinesses, coordinates])

  return <BigComponent />
}
like image 169
Akolade Adesanmi Avatar answered Nov 11 '22 20:11

Akolade Adesanmi


A quick fix is to implement shouldComponentUpdate See the docs, for whichever child component is being rendered ~2000 times.

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value !== nextProps.value;
}

Another quick check is to ask yourself if your following the convention of using small, stateless children, passing only props. If not, it might be time to refactor.

like image 40
enjoylife Avatar answered Nov 11 '22 20:11

enjoylife