Reputation: 47
I'm learning react, and i created simple component which display people and count like this:
const getPeople = async (pageNumber) => {
return await axios.get("https://randomuser.me/api",
{ params: {page: pageNumber, results: 8 }})
.then((res) => {
console.log(res.data.results);
return res.data;
})
.catch((err) => {
throw new Error("Smth went wrong...");
});
};
const People = () => {
const [pageNumber, setPageNumber] = useState(1);
const [error, setError] = useState(null);
const [people, setPeople] = useState([]);
const clickHandler = () => {
setPageNumber((prev) => (prev = prev + 1));
};
useEffect(() => {
async function Get() {
try {
const { results } = await getPeople(pageNumber);
setPeople((prevState) => [...prevState, ...results]);
} catch (err) {
setError(err.message);
}
}
Get();
}, [pageNumber]);
return (
<div>
{error && <h3>{error}</h3>}
<h2>{people.length}</h2>
<div className="person-grid">
{people.map(({ name, picture }) => (
<div className="person-card">
<img src={picture.large} alt={name.first} />
<p>{name.first + " " + name.last}</p>
</div>
))}
</div>
<div className="button-wrapperr">
<button className="load-button" onClick={clickHandler}>
Load more.
</button>
</div>
</div>
);
};
And the result of component is:
On button click new data is fetched and added to array and all that work ok. But when component is mounted like first time and i have 8 items in array after i change something in html like className, this useEffect is being called but my dependency isnt changed. So array also update and i have 16 items in array.
So my question is how to prevent this useEffect to be called on html change, i want it to just be called when pageNumber changes not when i change className of some element. I need logic to call useEffect only when pageNumber change, not when html change.
Note: This is because hot reload, this works normaly when i refresh page after changes, i need to prevent it when i change something, i dont want to refresh page and lose state evry time i change className...
Codesandbox: https://codesandbox.io/s/currying-framework-9ok0e?file=/src/People.js
Upvotes: 0
Views: 1741
Reputation: 367
I redid your call and worked with it a bit differently. Now it should work as expected: https://codesandbox.io/s/silly-glade-1duit?file=/src/People.js
The biggest difference here is the existence of async
since it doesn't feel that necessary. Instead, we're doing a basic API call and we deal with the .then()
and .catch()
(notice that I didn't do the .catch()
) right when we call the function.
Here's your new getPeople
const getPeopleNew = (pageNumber) => {
return axios
.get("https://randomuser.me/api", {
params: { page: pageNumber, results: 8 }
})
.then((response) => {
const { data } = response;
return data;
});
};
After cleaning the API call
I did the same with the useEffect
and the click
event.
We don't really need to have an useEffect
to deal with the requests for when the page changes if we already have a function to deal with that.
Here's your new clickHandler
and your starter useEffect
const clickHandler = () => {
setPageNumber((prev) => (prev = prev + 1));
getPeopleNew(pageNumber).then((response) => {
setPeople((prevState) => [...prevState, ...response.results]);
});
};
useEffect(() => {
getPeopleNew(1).then((response) => {
setPeople(response.results);
});
}, []);
And your entire new component
import axios from "axios";
import { useEffect, useState } from "react";
import "./People.scss";
const getPeopleNew = (pageNumber) => {
return axios
.get("https://randomuser.me/api", {
params: { page: pageNumber, results: 8 }
})
.then((response) => {
const { data } = response;
return data;
});
};
const People = () => {
const [pageNumber, setPageNumber] = useState(1);
const [error, setError] = useState(null);
const [people, setPeople] = useState([]);
const clickHandler = () => {
setPageNumber((prev) => (prev = prev + 1));
getPeopleNew(pageNumber).then((response) => {
setPeople((prevState) => [...prevState, ...response.results]);
});
};
useEffect(() => {
getPeopleNew(1).then((response) => {
setPeople(response.results);
});
}, []);
return (
<div>
{error && <h3>{error}</h3>}
<h2>{people.length}</h2>
<div className="person-grid">
{people.map(({ name, picture }) => (
<div className="person-card">
<img src={picture.large} alt={name.first} />
<p>{name.first + " " + name.last}</p>
</div>
))}
</div>
<div className="button-wrapperr">
<button className="load-button" onClick={clickHandler}>
Load more.
</button>
</div>
</div>
);
};
export default People;
Upvotes: 1
Reputation: 948
useEffect is running the first time & then reruns on dependency change. it seems like this is exactly what you want. the issue you are describing happens this way: you change the HTML, so the component lifecycle happens from the beginning, useEffect runs once, then reruns on dependency change. the fact that you see the items that were fetched previously, is because of FAST_REFRESH which is a webpack plugin that new versions of create-react-app uses and it doesn't reload the page after a change in files. I suggest you ignore this issue because it's not gonna be a problem in production. but if it really annoys you, you can just do this when you start your development server:
FAST_REFRESH=false npm start
or add FAST_REFRESH=false
to .env
file
Upvotes: 0
Reputation: 1679
To avoid useless rerendering you can use this package : https://github.com/gaearon/react-hot-loader
You can specify option to avoid rerendering on hook update : https://github.com/gaearon/react-hot-loader#hook-support
You can try to look into FAST_REFRESH env variable also, but I don't know if it works the same way with hooks update.
EDIT: on my config I used react-app-rewired to change the webpack config, after the installation you can just create a file 'config-overrides.js' at the root of your project with this line inside :
const rewireReactHotLoader = require("react-app-rewire-hot-loader");
/* config-overrides.js */
module.exports = function override(config, env) {
config = rewireReactHotLoader(config, env);
return config;
};
and change your package.json script to this:
"scripts": {
"build": "react-app-rewired build",
"start": "react-app-rewired start",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
then add this at the top of your App.jsx component :
import { hot } from "react-hot-loader/root";
import { setConfig } from "react-hot-loader";
setConfig({
reloadHooks: false,
});
...
const App = () => <>blablabla</>
export default process.env.NODE_ENV === "development" ? hot(App) : App; // to avoid babel config, don't forget to set your NODE_END to development
it should do the work
Upvotes: 0