ReactJS rerenders more than twice when fetching data from API - reactjs

why does my page renders twice, then after fetching data from api, it will re-render again twice. here's my code
import React, {useState, useEffect} from "react"
import axios from "axios"
function Pokemon()
{
const [pokeList,setPokeList] = useState([]);
useEffect(()=>{
console.log('useState-mounted')
axios.get("https://pokeapi.co/api/v2/pokemon?limit=151")
.then((response) => {
setPokeList(response.data.results);
})
},[])
const generationOnePokemon = pokeList.map(i => {
return <h3 key={i.url}>{i.name}</h3>
})
return(
<>
<h1>Shop</h1>
{console.log('page-rendered')}
{generationOnePokemon}
</>
)
}
export default Pokemon
Result from console

This is the expected behaviour when using <React.StrictMode>. It only happens in development mode, so there are no production implications with this.
It ensures that your setState calls are properly implemented and do not rely on only being executed once.
You can read more about React StrictMode here.

Related

React - Persist Data on Page Refresh (useEffect, useContext, useState)

React noob here.
I'm trying to use a combination of Context, useState, useEffect, and sessionStorage to build a functioning global data store that persists on page refresh.
When a user logins into the app, I fetch an API and then setUserData when I receive the response.
That part works great, but I realized if a user ever "refreshes" their page that the App resets the userData const to null (and the page crashes as a result).
I studied some articles and videos on using a combination of useEffect and sessionStorage to effectively replace the userData with what is currently in sessionStorage, which seemed like a sensible approach to handle page refreshes. [Source: React Persist State to LocalStorage with useEffect by Ben Awad]
However, I can't seem to get the useEffect piece to work. For some reason useEffect is only firing on the / route and not on the /dashboard route?
I would expect because the useEffect is in App.js that it runs every time any route is refreshed (thus retrieving the latest data from sessionStorage).
I added some console logging to App.js for when events are being fired and included those logs below.
What am I not understanding correctly about useEffect? Why does it only fire when the / route is refreshed and not when the page /dashboard is refreshed?
App.js
import { useState, createContext, useEffect } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import '../css/app.css';
import Login from './auth/login';
import Dashboard from './dashboard/dashboard';
export const AppContext = createContext(null);
function App() {
console.log("App Load")
const [userData, setUserData] = useState(null);
useEffect(() => {
console.log("Use effect ran");
const user = sessionStorage.getItem("user");
if (user) {
setUserData(JSON.parse(user));
console.log("Retreived session storage");
}
},[]);
return (
<AppContext.Provider value={ { userData, setUserData } }>
<BrowserRouter>
<div className="App">
<Routes>
<Route path="/" element={<Login />}></Route>
<Route path="/dashboard" element={<Dashboard />}></Route>
</Routes>
</div>
</BrowserRouter>
</AppContext.Provider>
);
}
export default App;
Dashboard component
import { useContext } from 'react'
import '../../css/app.css'
import { AppContext } from '../app';
import Nav from '../../components/primary-nav';
const Dashboard = () => {
const { userData } = useContext(AppContext);
return (
<div>
<Nav />
<div id='dashboard'>
<div id='title' className='mt-[38px] ml-[11%]'>
<div className='h2'>Good morning, { userData.user_info.first_name }!</div>
</div>
</div>
</div>
)
}
export default Dashboard;
I would have expected useEffect() fires when the Dashboard page is refreshed, but here are the logs respectively.
Logs: Default Route Loads
Logs: Dashboard Route Loads
Bonus points
Can you help me understand why App Load is being fired more than once (seems it fires 4 times?)
Issue
The useEffect hook, with empty dependency array, runs only once, and since it is outside the router it's completely unrelated to any routing. Additionally, the useEffect hook runs at the end of the initial render, so the wrong initial state value is used on the initial render cycle.
Solution
Initialize the state directly from sessionStorage and use the useEffect hook to persist the local state as it changes.
Example:
function App() {
console.log("App Load")
const [userData, setUserData] = useState(() => {
const user = sessionStorage.getItem("user");
return JSON.parse(user) || null;
});
useEffect(() => {
console.log("userData updated, persist state");
sessionStorage.getItem("user", JSON.stringify(userData));
}, [userData]);
return (
...
);
}
As with any potentially null/undefined values, consumers necessarily should use a null-check/guard-clause when accessing this userData state.
Example:
const Dashboard = () => {
const { userData } = useContext(AppContext);
return (
<div>
<Nav />
<div id='dashboard'>
<div id='title' className='mt-[38px] ml-[11%]'>
{userData
? (
<div className='h2'>Good morning, { userData.user_info.first_name }!</div>
) : (
<div>No user data</div>
)
}
</div>
</div>
</div>
);
};
Can you help me understand why App Load is being fired more than once (seems it fires 4 times?)
The console.log("App Load") in App is in the main component body. This is an unintentional side-effect. If you want to log when the App component mounts then use a mounting useEffect hook.
Example:
useEffect(() => {
console.log("App Load");
}, []); // <-- empty dependency to run once on mount
The other logs are likely related to React's StrictMode component. See specifically Detecting Unexpected Side-effects and Ensuring Reusable State. The React.StrictMode component intentionally double-invokes certain component methods/hooks/etc and double-mounts the component to help you detect logical issues in your code. This occurs only in non-production builds.
What am I not understanding correctly about useEffect? Why does it only fire on the "/" route and not when the page /dashboard is refreshed?
useEffect with empty deps array will run only once when the component is mounted. Your component doesn't unmount when the route change because your router is declared as a child of this component.
Can you help me understand why App Load is being fired more than once (seems it fires 4 times?)
Components rerender every time the state is changed. When a component is rendered all code inside of it is run.
Most likely, you are seeing those multiple console logs due to StrictMode in React, which "invokes" your components twice on purpose to detect potential problems in your application, you can read more details about this here.
I will assume that when you say "refreshing" you mean that if you refresh or reload the browser on the route corresponding to your Dashboard component then this is when the problem arises.
Now, you need to take in consideration that useEffect runs once the component has been mounted on the DOM, and in your Dashboard component, you're trying to access userData which on the first render will be null and then the useEffect will fire and populate the state with the data coming from your sessionStorage. How to fix this? Add optional chaining operator in your Dashboard component like so:
{ userData?.user_info?.first_name }
Additionally, I would suggest you to move your userData state and the useEffect logic in your App to your AppContext (in a separate file). Let me know if this works for you.

UseEffect without Array always iterate, why?

Question:
WHY does the code without array always iterate in relation compare to the code with array that only execute once?
Stackblitz:
Without array:
https://stackblitz.com/edit/react-ts-9w3cgd?file=App.tsx
With array:
https://stackblitz.com/edit/react-ts-xrrvan?file=App.tsx
Thank you!
Without array
import axios from 'axios';
import * as React from 'react';
import { useEffect, useState } from 'react';
import './style.css';
export default function App() {
const [data, setData] = useState();
useEffect(() => {
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((result) => setData(result.data));
//console.log(data);
console.log('t');
});
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
</div>
);
}
With array
import axios from 'axios';
import * as React from 'react';
import { useEffect, useState } from 'react';
import './style.css';
export default function App() {
const [data, setData] = useState();
useEffect(() => {
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((result) => setData(result.data));
console.log(data);
//console.log("t");
}, []);
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
</div>
);
}
From the official React hooks documentation:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
Basically, a useEffect hook with an empty dependency array should run only once when the component mounts.
Alternatively, if we were to not include a dependency array, the callback will run every time the component re-renders.
Which occurs whenever there is a change in state or the state of any of it's parent components.
With that said, it's probably better to avoid using the useEffect hook without a dependency array, since we would usually want to use it to act in a specific way upon the change of a specific state.
Using it in such a way is valid, but not really advised.

React useEffect showing unnecessary dependency warning

I created a custom hook inside a react function component .Inside the custom hook, I wanted to access some random date (stored inside a state 'data') which is also defined inside the function component so I used useEffect hook and provided the 'data' as a dependency. The code works just fine and I can also console log 'data' but the terminal showing "React Hook useEffect has an unnecessary dependency: 'data'" .
I tried removing 'data' from the dependency array, but then I can't console log 'data'. Anyone please explain me why useEffect showing this behavior inside custom hooks also I need to know if it is a right practice to create a custom hook inside a function component. Thank you
import './App.css';
import { useEffect, useState } from 'react'
function App() {
const [data, setData] = useState()
useEffect(() => {
setData('some random data')
}, [])
const useData = () => {
useEffect(() => {
console.log(data)
}, [data])
}
useData()
return (
<>
</>
)
}
export default App;

How to prevent useEffect in customHook being called with every import of custom hook?

I'm writing chat app using react js and socket.io library.
All the logic where I subscribe to events form server and emit some events is written in useEffect of custom hook.
Then I return all data I need from this custom hook and reuse it in components that I need. However, I realized that logic written in useEffect is called every time I import this custom hook to external component.
If I put all the logic outside of useEffect, it's called even more times than custom hook is imported.
How do I prevent it if it's possible at all?
If it's not possible, what solution could you please suggest? I don't want to use redux for this app, I thought to keep everything in this custom hook component and just reuse data from it where I need.
I can't share working example because it won't work without server part so here is a simple codesandbox example. You can see in console that it's rendered twice.
https://codesandbox.io/s/custom-hook-bfc5j?file=/src/useChat.js
It renders twice because you call useChat() two times in your app (one in App.js, other in Text.js) What you can do is to create a reference of useChat component in your App.js and pass is as a prop to Text.js like:
App.js
import React from "react";
import useChat from "./useChat";
import Text from "./Text";
import "./styles.css";
export default function App() {
const myUseChat = useChat();
const { printMessage } = myUseChat;
return (
<div className="App">
<button onClick={printMessage}>Print</button>
<Text myUseChat={myUseChat} />
</div>
);
}
Text.js
import React from "react";
import useChat from "./useChat";
import "./styles.css";
export default function Text(props) {
const { text } = props.myUseChat;
return <div className="App">{text}</div>;
}
If you want to set up some side effects once but also consume the resulting data in multiple places, one way is to use the context feature.
// ws/context.jsx, or similar
const WsContext = React.createContext(defaultValue);
export const WsProvider = props => {
const [value, setValue] = useState(someInitialValue);
useEffect(() => {
// do expensive things, call setValue with new results
});
return (
<WsContext.Provider value={value}>
{props.children}
</WsContext.Provider>
);
};
export const useCustomHook = () => {
const value = useContext(WsContext);
// perhaps do some other things specific to this hook usage
return value;
};
You can expect the hook to work in any component that is a descendant of <WsProvider> in React's rendered tree of elements.
If you use the hook in a non-descendant of the provider component, the value returned will be the defaultValue we initialized the context instance with.

setTimeout causes TypeError: Object(...) is not a function

I'm developing a small phone book web page with React and when a person is either added or person's info is updated I want a small notification to show on the page. I'm doing this by using React hooks in order to give state to variables message and errorMessage.
After an update, the message in question receives its content (e.g. "New contact added") and setTimeout(() => {setMessage('')}, 6000) is used to clear the message.
I've tried to search every thread I've found with the key words "TypeError, setTimeout" etc. with no results. I don't even understand what is the problem. I'f I remove the setTimeout altogether everything works fine (the message won't disappear).
Code bellow isn't the code I use but it does contain the same exact problem, namely setTimeout causes TypeError. There is no update of contacts, we just try to change the state of the message with setTimeout.
import React, {useState, setTimeout} from 'react';
const Test = () => {
//Setting up message
const [message, setMessage] = useState('')
//Change the state of message to Testing after 6 seconds
setTimeout(() => {setMessage("Testing")}, 6000)
return (
<div></div>
)
}
export default Test
setTimeout comes with a JavaScript and NOT from React,
As you imported setTimeout from react, you are getting this error,
import React, {useState, setTimeout} from 'react';
just remove setTimeout from import like,
import React, {useState} from 'react';
You are importing setTimeout from React, that's the issue. Just remove it from the import, and you will be fine since its a native javascript method.
Also, try to use useEffect when you are making these kinds of changes.
import React, { useState, useEffect } from 'react';
useEffect(() => {
const timeout = setTimeout(() => {setMessage("Testing")}, 6000)
return () => {
clearTimeout(timeout)
}
});
This avoids setting up timeout every time component is rendered and clears it when unmounted.

Resources