Why's my response being called more than once when the variable's passed down from top level App component?

Viewed 47

Whenever I sent the uploadsData variable from App.js into the <Gallery data={uploadsData}/>} /> component via a prop, it gets called more than one time.

Furthermore, whenever I hover over the blue !, I get that warning illustrated in the screenshot. This means I can never properly access this object properly.

How can I make so that it's executed once and only once so I can access the object in the proper way?

I thought having a [] in useEffect() (2nd param) as the dependency would cause pave the way for this to only be executed once?

Thanks in advance for taking the time to read. I'm open to any sort of feedback including code improvements/changes that are considered better practice

Here's App.js:

import { useEffect, useState } from 'react';
import { BrowserRouter as Router, Switch, Route, useHistory} from 'react-router-dom';
import ReactDOM from 'react-dom';
import '../../sass/HomePage/homePage.scss';
import LoginRegister from "./LoginRegister/LoginRegister";
import Gallery from "./Gallery/Gallery";
import Cookies from 'js-cookie';

const App = () => {
    const [uploadsData, setUploadsData] = useState([]);
    let { push } = useHistory();
    let authToken = Cookies.get('token');

    useEffect(() => {
        getUploads();
    },[]);

    function getUploads() {
        const headers = {
            "Accept": 'application/json',
            "Authorization": `Bearer ${authToken}`
        }

        axios.get('http://localhost:8005/api/get-uploads', {headers})
            .then(resp => {
                let uData = [...uploadsData,resp];
                setUploadsData(uData);
                if (authToken !== null) {
                    push('/gallery');
                } else {
                    console.log("User's NOT authenticated, returning to login view");
                    push('/');
                }
            }).catch(error => {
            console.log(error);
        })
    }

    return (
        <>
            <Switch>
                <Route exact path="/" component={LoginRegister} />
                <Route component={() => <Gallery data={uploadsData}/>} />
            </Switch>
        </>
    );
}

export default App;

if (document.getElementById('example')) {
    ReactDOM.render(<Router><App/></Router>, document.getElementById('example'));
}

Here's Gallery.js:

import React from 'react';

const Gallery = (data) => {    
    return (
        <>    
            {console.log(data)}
        </>
    );
}

export default Gallery;
1 Answers

Here is a replication of your code in Playground with some explanations: https://codesandbox.io/s/practical-sun-9cc1v?file=/src/App.js (there is react-router-dom v6 instead of v5 - non-significant changes)

import { useEffect, useState } from "react";
import { Routes, Route, useNavigate } from "react-router-dom";
import Gallery from "./Gallery";
import axios from "axios";

const App = () => {
  const [uploadsData, setUploadsData] = useState([]);
  let navigate = useNavigate();

  useEffect(() => {
    console.log("initial render");
    getUploads();
  }, []);

  function getUploads() {
    const headers = {
      Accept: "application/json"
    };

    axios
      .get("https://jsonplaceholder.typicode.com/todos/1", { headers })
      .then((resp) => {
        console.log("loading start");
        let uData = [...uploadsData, resp];
        setUploadsData(uData);
        console.log("navigate and re-render");
        navigate("/gallery");
        console.log("loading end");
      })
      .catch((error) => {
        console.log(error);
      });
  }

  return (
    <Routes>
      <Route exact path="/" element={<div />} />
      <Route path="gallery" element={<Gallery data={uploadsData} />} />
    </Routes>
  );
};

export default App;

So initially (when starting on /) we have a single render/console output. But after the first run (/gallery), we have already 3 renders/console outputs - as far as I understand it's the initial issue. The reason of it:

  • initial render
  • setState provokes one more render
  • navigate from /gallery to /gallery provokes one more

Feel free to comment mentioned code blocks and check the console

Regarding best practices here, there is a good example from react-router and remix https://stackblitz.com/github/remix-run/react-router/tree/main/examples/auth.

Don't hesitate to comment if the answer isn't full enough

UPD: an example w/o re-render of the child component with minimal changes codesandbox.io/s/flamboyant-archimedes-h0fhr?file=/src/App.js

Related