Notch
Notch

Reputation: 47

useEffect is called on html change

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:

enter image description here

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

Answers (3)

ItzaMi
ItzaMi

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

e.a.
e.a.

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

Nicolas Menettrier
Nicolas Menettrier

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

Related Questions