Proper way to render content without flickering ? React + Redux + TS

Viewed 832

Could you help me with the logic for rendering content? I'm doing pet-project with React, Redux, TS and have troubles with content rendering on page load.

Actually there are two problems.

The first problem:

  1. When I click on a card the GET request is sent for the card data.
  2. Card's page is opens (dynamic routing with useParams)
  3. When data is received from server it sends to Redux State
  4. Page renders is ok (because initial state was empty).

Then I click on another card and here's the problem: 5) I click on another card and send GET request for the new date. 6) At this moment state stores previous card data and renders it 7) New data comes from servers and renders. 8) For time very small amount of time I see previous card data ...it's flickring. Gif below

enter image description here

My question is: should I reset card state to its initial state (empty state) when clicking on a card? Or there should be another way to do this?

Second problem: To fix first propblem I addeed onClick function which resets card state to its initial state:

const initialState: IMovieState = {
  movie: {
    filmId: 0,
    nameRu: '',
    nameEn: '',
    webUrl: '',
    posterUrl: '',
    year: 0,
    filmLength: '',
    slogan: '',
    description: '',
    type: '',
    countries: [],
    genres: [],
    rating: {
      rating: 0,
      ratingVoteCount: 0,
      ratingImdb: 0,
      ratingImdbVoteCount: 0,
    },
    images: {
      posters: [],
    },
  },
  isLoading: false,
  movieError: null,
};

This seems to fix problem 1, but there's another...on page Load there's yellow button and image's alt data (which fail to load) is visible at the top left corner. Gif below. enter image description here

My question is: should I add a condition: if object key (no matter what key) has data then render it or should I add a new key in state something like isData: false/true and condition will be if data=true then render it.

MoviePage.tsx

interface IMovieProps {
  filmId: string;
}

const MoviePage: React.FC = () => {
  const { filmId } = useParams<IMovieProps>();
  const dispatch = useDispatch();
  const { isLoading, movie, movieError } = useTypedSelector((state) => state.singleMovie);
  const { nameRu, nameEn, description, posterUrl, year, genres, filmLength } = movie;
  
  useEffect(() => {
    dispatch(fetchMovie(filmId));
  }, [filmId, dispatch]);


  return (
    <section className={styles.section}>

      <div className={styles.backgroundImage} style={{ backgroundImage: `url(${posterUrl})` }} />

      {isLoading && <Preloader />}

      {!isLoading && !!movieError && <Message message={movieError} />}

      {!isLoading && !movieError && (
        <>
          <GoBackLink text={'Назад'} />

          <div className={styles.content}>
            <img src={posterUrl} alt='Постер' className={styles.poster} />
            <div>
              <h1 className={styles.title}>{nameRu}</h1>
              <h2 className={styles.subtitle}>
                {nameEn && `${nameEn}, `}
                <span>{year}</span>
              </h2>
              {filmLength && <p className={styles.subtitle}>Продолжительность: {filmLength} ч</p>}

              <ul className={styles.genresList}>
                {genres.map(({ genre }, idx) => (
                  <Genres genre={genre} key={idx} />
                ))}
              </ul>

              {description && (
                <>
                  <h3 className={styles.heading}>Описание</h3>
                  <p className={styles.text}>{description}</p>
                </>
              )}
            </div>
          </div>
        </>
      )}
    </section>
  );
};

export default MoviePage;

State of a card on click: enter image description here

1 Answers

What I like to do is that I like to use condition with useState() as well as check that the particular key has data. I don't reset the state before each click. Rather I check if the particular post key has already data present in redux state. If that particular key has data then I just use that data and do not make a fetch request every time the same component is rendered. If it does not have data then i make request.

1- Check if the item key has already data present. if it has then no need to make a fetch request, use the data to render component.
2- concat data to redux store. Don't override the state.
3- store data in the form of Objects. Where keys would be item's unique key. This way you can check if key is present (eg: data[key]?.length > 0)


Hope it helps.

// example
  
  const [loading, setLoading] = useState(false);
  const data = useSelector(getItemData) // here getItemData is a seletor from reselect library 
  const dispatch = useDispatch();
  
  
  const hasData = Boolean(data.length)
  useEffect(() => {
    if (hasData) return;
    const fetchData = async () => {
      setLoading(true);
      await dispatch(fetchItems()); // make fetchItems redux thunk async as well
      setLoading(false)
    }

    fetchData()
  }, [condition]);


  if (loading) return <div>Loading ...</div>
  
  return <MyComponent />

Related