Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS: useEffect is not run when the url changes

I have a ReactJS website, and I'm still a noob using it.

I've 2 Components

  • The first one is the / page, that must be rendered after a request to a web server, when the user get to that route, I've used useEffect for this.
  • The second one is the /mostraMaterie/:id that must be rendered after another request to the web server.

The first component work like a charm, whereas the second one doesn't trigger the useEffect function.

When the user clicks a button on the / component, it is redirected to /mostraMateria/:id with the according id.

The problem is the /mostraMateria component I have another useEffect function, but it is not triggered.

As suggested in the comment, this is the mostraMateria file in JSFiddle mostraMaterie.js

App.js handles the user connection

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import MostraMaterie from "./Contenuti/MostraMaterie";
import "./App.css";

import Main from "./Contenuti/Main";

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/mostraMateria/:id" component={MostraMaterie} />{" "}
        <Route exact path="/" component={Main} />{" "}
      </Switch>{" "}
    </Router>
  );
}

export default App;

Main.js handles the page rendering if the location is / and work perfectly.

import React, { useEffect, useState } from "react";
import {  Link } from "react-router-dom";
import Indirizzi from "./Indirizzi";
import Bottone from "../Util/Bottone";
import "../App.css";
import FullNavBar from "../NavBar/FullNavBar";

function Main() {
  const [indirizzi, cambiaIndirizzi] = useState([]);

  const fetchIndirizzi = () => {
    async function prendiIndirizzi() {
      let indirizzi = await fetch(
        "https://vps.lellovitiello.tk/Riassunty/API/indirizzi.php"
      );
      let ind = await indirizzi.json();

      for (let i = 0; i < ind.length; i++) {
        async function prendiMaterie() {
          let materie = await fetch(
            `https://vps.lellovitiello.tk/Riassunty/API/materie.php?indirizzo=${ind[i].Indirizzo}`
          );
          materie = await materie.json();

          for (let materia of materie) {
            Object.defineProperty(
              materia,
              "nome",
              Object.getOwnPropertyDescriptor(materia, "Materia")
            );
            delete materia["Materia"];

            Object.defineProperty(
              materia,
              "id",
              Object.getOwnPropertyDescriptor(materia, "IDMateria")
            );
            delete materia["IDMateria"];
            delete materia["Indirizzo"];
          }

          Object.defineProperty(
            ind[i],
            "nome",
            Object.getOwnPropertyDescriptor(ind[i], "Indirizzo")
          );
          delete ind[i]["Indirizzo"];
          Object.assign(ind[i], { dati: materie });
          Object.assign(ind[i], { id: i });
        }

        await prendiMaterie();
      }
      console.log("Main.js", ind);
      cambiaIndirizzi(ind);
    }
    prendiIndirizzi();
  };
  useEffect(fetchIndirizzi, []);

  return (
    <React.Fragment>
      <FullNavBar elementi={indirizzi} />{" "}
      <Link to="/Login">
        <Bottone TestoBottone="Per caricare un riassunto" />
      </Link>{" "}
      {indirizzi.map(indirizzo => {
        console.log(indirizzo);
        return <Indirizzi dati={indirizzo} />;
      })}
    </React.Fragment>
  );
}

export default Main;

MostraMaterie.js handles the page rendering if the location is /mostraMaterie/:id and doesn't work.

import React, { useEffect, useState } from "react";
import "../App.css";
import history from "../Util/history";
import { Link } from "react-router-dom";
import FullNavBar from "../NavBar/FullNavBar";
import Indirizzi from "./Indirizzi";
import Bottone from "../Util/Bottone";

function MostraMaterie(props) {
  const [anni, cambiaAnni] = useState([]);

  // *** this function is never called ***
  const fetchAnni = () => {
    async function prendiAnni() {
      let anniJson = await fetch(
        "https://vps.lellovitiello.tk/Riassunty/API/ottieniAnni.php"
      );
      let idMateria = props.match.params.id;
      let ann = [];
      anniJson = await anniJson.json();

      for (let anno of anniJson) {
        async function prendiAnteprime() {
          let anteprimeFetch = await fetch(
            `https://vps.lellovitiello.tk/Riassunty/API/anteprima.php?idMateria=${idMateria}&anno=${anno}`
          );

          anteprimeFetch = await anteprimeFetch.json();
          for (let anteprima of anteprimeFetch) {
            Object.defineProperty(
              anteprima,
              "nome",
              Object.getOwnPropertyDescriptor(anteprima, "Titolo")
            );

            Object.defineProperty(
              anteprima,
              "id",
              Object.getOwnPropertyDescriptor(anteprima, "ID")
            );
            delete anteprima["Titolo"];
            delete anteprima["ID"];
          }

          let annoOggetto = {
            nome: anno + "",
            anteprime: anteprimeFetch
          };
          ann.push(annoOggetto);
        }

        await prendiAnteprime();
      }
      debugger;
      cambiaAnni(ann);
    }
    console.log("Vengo eseguito");
    prendiAnni();
  };

  useEffect(fetchAnni, []); 
  console.log(history.location);

  return (
    <React.Fragment>
      <FullNavBar indirizzi={anni} />{" "}
      <Link to="/Login">
        <Bottone TestoBottone="Per caricare un riassunto" />
      </Link>{" "}
      <h1> CIao CIAO CIAO CIAOC </h1>{" "}
      {
        //*** this depends on the response of the web server ***
         anni.map(anno => {
              console.log("Ciao");
              return <Indirizzi dati={anno} />;
            })}{" "}
    </React.Fragment>
  );
}

export default MostraMaterie;

How can I make this work?

Edit

I've remove the <FullNavBar indirizzi={anni} />{" "} and it works, now my request is made to the server and the ``fetchAnni``` function in fired.

The problem is, I need the piece of code I removed, it seems that the <FullNavBar indirizzi={anni} />{" "} is rendered before the call of fetchAnni, even adding the await prendiAnni and async fetchAnni as suggested by Artur doesn't work.

I've also changed from useEffect(fetchAnni, []); to useEffect(fetchAnni, [props.location.pathname]); that is a string

like image 383
Davide Vitiello Avatar asked Feb 18 '20 21:02

Davide Vitiello


People also ask

How do you run useEffect only when state changes?

Use the useEffect hook to listen for state changes in React. You can add the state variables you want to track to the hook's dependencies array and the logic in your useEffect hook will run every time the state variables change.

Does useEffect run after every update?

Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution. Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.

Does useEffect run when props change?

Just like the state, we can also use props as a dependency to run the side effect. In this case, the side effect will run every time there is a change to the props passed as a dependency. useEffect(() => { // Side Effect }, [props]);

Does useEffect run every time?

If the useEffect has no dependencies, then the callback function constantly executes whenever the component updates: useEffect(() => { // side-effects code here... })


2 Answers

Second useEffect arg [] :

First of all: Sending a [] as second arg of useEffect will say "NEVER RE-RUN this function" (only 2times, on mount and on unmount).

Removing it ! it's NO :

You do not want to remove it or your effect will be called on every render and cause performances issues.

Choosing the right var(s) to track:

You have to give React all the fields who will affect the re-render of your function, in your case "when the URL change".

Hint: React will test every time if one of variable has been update since last render, but we als know that non-primitive will not works:

const a = {name: "Foo"};
const b = {name: "Foo"};
const res = (a === b); // False 

So, you have to choose for primitive values only. In your case the simplest is the /:id on your Route: props.match.params.id.

  useEffect(fetchAnni, [props.match.params.id]); 
  console.log(`Render for ID: ${props.match.params.id}`);

Async/Await

Also, you're function prendiAnni is defined as async but you call it:

console.log("Vengo eseguito");
prendiAnni();

Instead of

await prendiAnni();
like image 175
Arthur Avatar answered Sep 17 '22 16:09

Arthur


import React, { useEffect } from 'react' 
import { useHistory } from 'react-router-dom'

const Root = () => {
    const history = useHistory()
    useEffect(() => {
        history.listen((location) => {
            console.log(`You changed the page to: ${location.pathname}`)
        })
    }, [history])
}
like image 42
sekhar javvadi Avatar answered Sep 19 '22 16:09

sekhar javvadi