Ad

UseEffect And SocketIO Infinite Rerendering, Why Is It Happening?

I am getting data, using useEffect Hook from sockets. Every time I get the response, one particular component rerenders, but I don't pass any socket data to that component.

Code:

  const App = () => {
  const isMounted = React.useRef(false);
  const [getSocketData, setSocketData] = useState([]);

  useEffect(() => {
    console.log('[App.js] Mounted!');
    isMounted.current = true;
    const socket = socketIOClient(process.env.REACT_APP_BOARD);
    socket.on('ServersInfo', (data) => {
      if (isMounted.current) {
        setSocketData([...data]);
      }
    });
    socket.on('connect_error', () => {
      if (isMounted.current) {
        setSocketData([]);
      }
      console.log('Connection error!');
    });
    return (() => {
      isMounted.current = false;
      socket.disconnect();
      console.log('[App.js] unmounted');
    });
  }, []);

  const routes = (
    <Switch>
      <Route path={['/', '/users/:id']} exact component={() => <MainPage axiosInstance={axiosInstance} />} />
      <Route path='/servers' render={(spProps) => <ServerPing {...spProps} />} />
      <Route render={(nfProps) => <NotFoundComponent {...nfProps} />} />
      {/* <Redirect to='/' /> */}
    </Switch>
  );
  return (
    <div className="App">
      <Layout>
        <Suspense fallback={<p>Loading...</p>}>
          {routes}
        </Suspense>
      </Layout>
    </div>
  );
};

export default App;

What my Components look like: - App.js - Layout (doesn't rerender) (which has 3 children) - MainPage (rerenders infinitely), ServerPing (doesn't rerender), NotFoundComponent (doesn't rerender)

The question is: why MainPage Component rerenders infinitely? I mean MainPage Component and its children unmount and mount again, when socket data fetches which is weird behaviour.

MainPageComponent:

const MainPage = ({ axiosInstance, ...props }) => {

  const isMounted = React.useRef(false);
  const [loadingPage, setLoadingPage] = useState(true);
  const [usernames, setUsernames] = useState([]);
  const [currentDay] = useState(new Date().getDay());

  useEffect(() => {
    isMounted.current = true;
    console.log('[MainPage.js] Mounted!');
    getUsers();
    return () => {
      console.log('[MainPage.js] Unmounted!');
      isMounted.current = false;
    };
  }, []);

  const getUsers = async () => {
    try {
      const res = await axiosInstance.get('/users');
      const newData = await res.data;
      const newArray = [];
      newData.map(user => (
        newArray.push({id: user._id, flag: user.flag, text: user.name, value: user.name.toLowerCase()})
      ));
      if (isMounted.current) {
        setUsernames(newArray);
        setLoadingPage(false);
      }
    } catch {
      if (isMounted.current) {
        setLoadingPage(false);
      }
    }
  };

  return...
Ad

Answer

Problem is that you are using component prop instead of render prop to render the MainPage which will remount the component on any re-render of App component if there is a callback given to a component prop.

Below code should fix the issue

<Route 
    path={['/', '/users/:id']} 
    exact 
    render={(props) => <MainPage {...props} axiosInstance={axiosInstance} />} 
/>

According to the react-router-dom docs

When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).

Ad
source: stackoverflow.com
Ad