rgdzv
rgdzv

Reputation: 505

How to avoid rerender of a component in React?

Creating a simple app using React and Redux.

The point is to get photos from the server, show them and if you click on the photo show modal window with bigger photo and comments.

The code for App component

import React, { useEffect } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import './App.scss'
import List from './components/list/List'
import Header from './components/header/Header'
import Footer from './components/footer/Footer'
import ModalContainer from './containers/ModalContainer'
import { getPhotos, openModal } from './redux/actions/actions'

const App = () => {

  const { isFetching, error } = useSelector(({ photos }) => photos)
  const photos = useSelector(({ photos }) => photos.photos)
  const { isOpen } = useSelector(({ modal }) => modal)
  const dispatch = useDispatch()
  
  useEffect(() => {
    dispatch(getPhotos())
  }, [])

  const getBigPhoto = (id) => {
    dispatch(openModal(id))
  }

  return (
    <div className="container">
      <Header>Test App</Header>
      <div className="list__content">
        {isFetching 
          ? <p>Loading...</p>
          : error 
          ? <p>{error}</p>
          : photos.map(({ id, url }) => (
              <List
                key={id}
                src={url}
                onClick={() => getBigPhoto(id)}
              />
            ))
        }
      </div>
      <Footer>&copy; 2019-2020</Footer>
      {isOpen && <ModalContainer />}
    </div>
  )
} 

export default App

In this line I get photos only once to stop rerender if I refresh the page

  useEffect(() => {
    dispatch(getPhotos())
  }, [])

When I click on the photo my modal opens and I want to stop rerendering all the components. For example for my header I use React.memo HOC like this

import React, { memo } from 'react'
import './Header.scss'
import PropTypes from 'prop-types'

const Header = memo(({ children }) => {
  return <div className="header">{children}</div>
})

Header.propTypes = {
  children: PropTypes.string,
}

Header.defaultProps = {
  children: '',
}

export default Header

It works perfectly when I open and close my modal. Header and Footer are not rerendered. But List component is rerendered every time I open and close a modal window. It's happening because that prop onClick={() => getBigPhoto(id)} in List component creates a new anonymous function every time I click. As you know if your props changed, component is rerendered.

My question is how to avoid rerender of List component in my situation?

Upvotes: 1

Views: 211

Answers (1)

HMR
HMR

Reputation: 39360

You can create a container for List that receives getBigPhoto and an id, create getBigPhoto with useCallback so the function doesn't change:

const ListContainer = React.memo(function ListContainer({
  id,
  src,
  getBigPhoto,
}) {
  return (
    <List
      key={id}
      src={scr}
      onClick={() => getBigPhoto(id)}
    />
  );
});
const App = () => {
  const { isFetching, error, photos } = useSelector(
    ({ photos }) => photos
  );
  const { isOpen } = useSelector(({ modal }) => modal);
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(getPhotos());
  }, []);
  //use callback so getBigPhoto doesn't change
  const getBigPhoto = React.useCallback((id) => {
    dispatch(openModal(id));
  }, []);

  return (
    <div className="container">
      <Header>Test App</Header>
      <div className="list__content">
        {isFetching ? (
          <p>Loading...</p>
        ) : error ? (
          <p>{error}</p>
        ) : (
          photos.map(({ id, url }) => (
            // render pure component ListContainer
            <ListContainer
              key={id}
              src={url}
              id={id}
              getBigPhoto={getBigPhoto}
            />
          ))
        )}
      </div>
      <Footer>&copy; 2019-2020</Footer>
      {isOpen && <ModalContainer />}
    </div>
  );
};

Upvotes: 1

Related Questions