Reputation: 13
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
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
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