Jesus Ramirez
Jesus Ramirez

Reputation: 13

Why my data is flickering between previous and new when state is updated in ReactJS?

I'm working on a SPA with data fetch from the Star Wars API. In the characters tab of the project, the idea is to display the characters per page and you can click next or prev to go to page 2, 3, etc. That works, but! the character names flicker everytime the page changes, it doesn't happen after the first click, but if you keep going, it happens more and more.

I tried to fixed it by cleaning the state before rendering again, but it's not working. The data is first fetched after the component mounts, then when the btn is clicked I use the componentwillupdate function to update the character component.

You can see the component here: https://github.com/jesusrmz19/Star-Wars-App/blob/master/src/components/Characters.js

And the live project, here: https://starwarsspa.netlify.app/#/Characters

Upvotes: 1

Views: 3840

Answers (2)

Mantas Astra
Mantas Astra

Reputation: 1062

I took a look at your Characters component and spent some time refactoring it to React Hooks to simplify it.

Instead of making multiple requests, I created a function that does one request and assigns a state when needed. Plus, I would use status instead of isLoading so that you can render the content better based on the status that your component is currently on.

Also, I used useEffect which does the same thing that componentDidMount + componentDidUpdate does. I then used the request function in there and added pageNumber as a dependency which means that the request will be made only when the pageNumber changes. In your case, it will change only if you press previous or next buttons.

I also simplified your getPrev and getNext page functions. Finally, I rendered the page content based on the status.

I've pulled your project down and run it locally and I cannot see the flickers anymore. Instead, it shows loading screen while it fetches the data and then nicely renders what you need.

Hope that helps. I would also advise starting looking at React Hooks as they make using React way simpler and it's a modern way to develop React applications as well. I've refactored your Characters component and if you want then use this as an example of how to refactor from Class Components to React Hooks.

import React, { useState, useEffect } from "react";
import Character from "./Character";

const baseUrl = "https://swapi.dev/api/people/?page=";

const Characters = () => {
  const [state, setState] = useState({
    characters: [],
    pageNumber: 1,
    status: "idle",
    error: null,
  });
  const { characters, pageNumber, status, error } = state;

  const fetchData = async (pageNumber) => {
    setState((prevState) => ({
      ...prevState,
      status: "fetching",
    }));

    await fetch(`${baseUrl}${pageNumber}`).then(async (res) => {
      if (res.ok) {
        const data = await res.json();

        setState((prevState) => ({
          ...prevState,
          characters: data.results,
          status: "processed",
        }));
      } else {
        setState((prevState) => ({
          ...prevState,
          characters: [],
          status: "failed",
          error: "Failed to fetch characters",
        }));
      }
    });
  };

  useEffect(() => {
    fetchData(pageNumber);
  }, [pageNumber]);

  const getNextPage = () => {
    if (pageNumber !== 9) {
      setState((prevState) => ({
        ...prevState,
        pageNumber: prevState.pageNumber + 1,
      }));
    }
  };

  const getPrevPage = () => {
    if (pageNumber !== 1) {
      setState((prevState) => ({
        ...prevState,
        pageNumber: prevState.pageNumber - 1,
      }));
    }
  };

  return (
    <div>
      {status === "failed" ? (
        <div className="charact--container">
          <div className="loading">{error}</div>
        </div>
      ) : null}
      {status === "fetching" ? (
        <div className="charact--container">
          <div className="loading">Loading...</div>
        </div>
      ) : null}
      {status === "processed" ? (
        <div className="charact--container--loaded">
          <h1>Characters</h1>
          <button onClick={getPrevPage}>Prev Page</button>
          <button onClick={getNextPage}>Next Page</button>
          <ul className="characters">
            {characters.map((character, index) => (
              <Character details={character} key={index} index={index} />
            ))}
          </ul>
        </div>
      ) : null}
    </div>
  );
};

export default Characters;

Upvotes: 0

Mr. A
Mr. A

Reputation: 333

see if this solves your problem

class Characters  extends React.Component { 
constructor(props) {
    super(props);
    this.state = {
      error: null,
      isLoaded: false,
      webPage: "https://swapi.dev/api/people/?page=",
      pageNumber: 1,
      characters: [],
    };
    this.fetchHero = this.fetchHero.bind(this);
  }



async fetchHero(nextOrPrev) {
    //nextOrPrev values-> 0: initial 1: next page -1:prev page
    let pageNum = this.state.pageNumber + nextOrPrev;
    try {
      const response = await fetch(this.state.webPage + pageNum);
      const data = await response.json();
      const characters = data.results;
      this.setState({
        pageNumber: pageNum,
        characters,
        isLoaded: true,
      });
    } catch (error) {
      this.setState({
        pageNumber: pageNum,
        isLoaded: true,
        error,
      });
    }
  }
  componentDidMount() {
    this.fetchHero(0);
  }

  /*you don't need this-> componentDidUpdate(prevState) {
    if (this.state.pageNumber !== prevState.pageNumber) {
      const fetchData = async () => {
        const response = await fetch(
          this.state.webPage + this.state.pageNumber
        );
        const data = await response.json();
        this.setState({ characters: data.results });
      };

      fetchData();
    }
  }*/
  getNextPage = () => {
    if (this.state.pageNumber < 9) {
      this.fetchHero(1);
    }
  };
  getPrevPage = () => {
    if (this.state.pageNumber > 1) {
      this.fetchHero(-1);
    }
  };
  render(
      // the rest of your code
  )
}

Upvotes: 1

Related Questions