Working of `useState` function inside `useEffect` function - javascript

I am trying to use useState inside useEffect. I want to access and modify a state inside it( useEffect ), here named as isAuth and according to the new state render the component.
import React, { useState, useEffect } from 'react';
const Authentication = () => {
const [isAuth, setIsAuth] = useState(false);
useEffect(() => {
console.log(isAuth);
setIsAuth(true);
console.log(isAuth);
}, [isAuth]);
return <div>{isAuth ? <p>True</p> : <p>False</p>}</div>;
};
export default Authentication;
The thing is in console I am getting false, false, true, true.Instead of this console, I expected the 2nd console message to be true. Can someone explain it how it happens and how do I actually change the state before component renders?

setIsAuth doesn't cause the local variableisAuth to change its value. const data values can't be changed, and even if you defined it as let, that's not what setting state does. Instead, when you set state, the component rerenders. On that new render, the call to useState will return the new value, and you can use that new value for the new render.
The component renders for the first time. Then it runs the effect. The closure for the effect has the local variables from that first render, and your code uses those variables to log the false twice. Since you called set state, a new render will happen, and that new render will have different variables. When it's effect runs, it will log true twice, since that's the values in its closure.

Here are some comments in the code explaining how React setState will only update the local const once the component re-renders
import React, { useState, useEffect } from 'react';
const Authentication = () => {
// React will initialise `isAuth` as false
const [isAuth, setIsAuth] = useState(false);
useEffect(() => {
// Console log outputs the initial value, `false`
console.log(isAuth);
// React sets state to true, but the new state is not available until
// `useState` is called on the next render
setIsAuth(true);
// `isAuth` will remain false until the component re-renders
// So console.log will output `false` again the first time this is called
console.log(isAuth);
}, [isAuth]);
// The first time this is rendered it will output <p>False</p>
// There will be a re-render after the `setState` call and it will
// output <p>True</p>
return <div>{isAuth ? <p>True</p> : <p>False</p>}</div>;
};
export default Authentication;

That's actually correct. useEffect lets you access the latest state after an update. So during the first render, what you see is basically the initial state, no matter if you update it.
Calling setState inside useEffect will cause a rerender with the new state (isAuth = true), which will result in calling useEffect again. At this point, the new logged state is true.

Related

Why am I getting 'undefined' when trying to use the useState hook in React?

I am trying to use the useState hook in React to set an initial value as an empty function, but when I try to console.log the state variable, I get undefined.
Here is the code I am using:
import {useState} from 'react';
function MyComponent() {
const [callback, setCallback] = useState(() => {});
console.log(callback);
return <div>My component</div>;
}
I have also tried using the useEffect hook to update the state, but I am still getting undefined.
I am new to React and I am not sure what I am doing wrong. Can someone please help me understand why I am getting undefined and how I can fix it?
Passing a function to useState indicates lazy initialization - the function gets invoked once when the component mounts, and the value returned from the function determines the initial value.
If you want state to be a function, you'll need to have a function that returns a function.
const [callback, setCallback] = useState(() => () => {});
But it rarely makes sense for state to be a function. Use something more appropriate like useCallback, or just declare a plain function.
const callback = () => {
// ...
};
useState can optionally use a function to produce the initial value as a performance enhancement. So it's seeing your function that returns undefined as an initializer.
Try
// ,----- this is a function that returns a
// v function that does nothing
useState(() => () => {})
The argument you pass to useState is the initial value that React holds on to for every render until that state needs to change (through the "set function" which is the second value returned from the useState hook.)
We don't typically use functions as that initial value - but it can be done as explained in the other answers!
Since we're using functions (the useState hook) inside of functions (your component) we're creating closures that are going to be re-created every render. Now there's a serious issue with recreating things every render: the component can't "remember" anything when the closure gets recreated! This is why we have state. It allows the component to remember what previously exists.
We use the useEffect hook to "step outside" of React and go sync to an external system. Often we useEffect to retrieve some data:
import React, {useState, useEffect} from 'react';
function MyComponent() {
const [data, setData] = useState();
useEffect(() => {
async function retrieveData(){
const myData = await fetch(https://whatever...)
// now pass the data to the set state function
setData(myData)
}
}, [])
return <div>My component</div>;
}
I highly advise you to read through the new React docs here They do an excellent job of walking you through everything!
If you want to use useState's initial value as a function, you need to use Lazy Initial State:
const [callback, setCallback] = useState(functionName())
So if you do:
function functionName(){
return 'some computations'
}
Now if you log, functionName will return some computations as a result(You can return anything you want).

React Hooks: Why I have this output?

I'm studying React hooks but i am not able to understand why I got this output in the console. Someone with great heart could explain in detail the "running execution" oh these hooks?
import { useState, useEffect } from "react";
function Homepage() {
const [state, setState] = useState();
useEffect(() => {
console.log("useEffect");
console.log("useEffect not executed");
setState("hello")
}, []);
if (!state) {
console.log("state not defined");
return <div>State undefined</div>;
}
return <div>ciao</div>;
}
export default Homepage;
console output:
state not defined
state not defined
useEffect
useEffect not executed
useEffect
useEffect not executed
Basically it's a combination of React.StrictMode double invoking the function body twice as a way to help you detect unexpected side-effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies <-- this
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer
remounting the component to ensure reusable state
To help surface these issues, React 18 introduces a new
development-only check to Strict Mode. This new check will
automatically unmount and remount every component, whenever a
component mounts for the first time, restoring the previous state on
the second mount.
and the useEffect hook being called at the end of the render cycle.
function Homepage() {
const [state, setState] = useState();
useEffect(() => {
console.log("useEffect"); // logs second as expected side-effect
console.log("useEffect not executed");
setState("hello");
}, []);
if (!state) {
console.log("state not defined"); // logs first as unintentional side-effect
return <div>State undefined</div>;
}
return <div>ciao</div>;
}
...
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import Homepage from "./Homepage";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<Homepage />
</StrictMode>
);
Explaining the logs
console output:
state not defined // <-- initial render
state not defined // <-- double invocation of function body
useEffect // <-- effect at end of initial render
useEffect not executed // <-- effect at end of initial render
...unmount/mount
useEffect // <-- effect at end of render
useEffect not executed // <-- effect at end of render
useEffect runs when the page renders and the other functions execute before useEffect so your code runs the if (!state) first then useEffect runs and state sets to "hello"; here is a link to fully understand useEffect hook: useEffect; Good luck;
your if statement is running before the useEffect even tho is after it, you need to put both divs inside the return and remove the if
return (
{!state ? <div>State undefined</div> : <div>ciao</div>}
)
state not defined first render of your component Homepage
state not defined 2nd render
useEffect useEffect not executed your component is mounted, so the useEffect hook is triggered.
useEffect useEffect not executed here your component rerender with the state hello the useEffect hook is triggered again
In practice, useEffect hook runs at minimum following your dependency array. Here your set [] so the useEffect hook runs when the component mounts, but sometimes it can still be triggered.

How to setState with fetched data in React

I need to setState with an object that I'm getting from a redux store. The problem is when I call setState in my component I am getting undefined. Any idea on how to set a default value of state with fetched data?
Here is my component:
import React, { useEffect, Fragment, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
const contactDetails = useSelector((state) => state.contactDetails)
const upFields = contactDetails?.contact?.data?.fields
const [contact, setContact] = useState({
fields: upFields <---- this is returning undefined.. The name is correct, but maybe setState is running too fast?
})
console.log(contact) <---- this shows {fields: undefined}
console.log(upFields) <---- this console.logs just fine
you use useEffect() and trigger it using the object
useEffect(()=>{
setContact(fetched_data)
}, [fetched_data]) // <-- data array will trigger every time this data change
you can trigger it on first component mount with
useEffect(()=>{
setContact(fetched_data)
}, []) // <-- empty array will only trigger once
So, I ended up setting my state one component higher, and passing the "upFields" to the child component that needed this state. Once in the child component I just ran a useEffect and setState with the data I needed

Why setState causes too many rerender Error even though the state hasn't changed

Hi I'm learning React now and having trouble with the state..
I know when the state changes, the component re-renders and the code in UseEffect widout depth only runs once.
But I can't explain exactly why infinite rendering occurs when I write setState in JSX or in the render syntax.
below code causes infinite re-render
import React, { useState, useEffect } from 'react'
const index = () => {
const [active, setActive] = useState(false);
console.log("render", active);
setActive(false);
return (
<div>
</div>
)
}
export default index
But the code below has no problem even though it keeps calling setState.
import React, { useState, useEffect } from 'react'
const index = () => {
const [active, setActive] = useState(false);
console.log("render", active);
useEffect(() => {
setInterval(()=>{
console.log("run")
setActive(true)
},0);
}, [])
return (
<div>
</div>
)
}
Does setState trigger re-rendering regardless of state value?
I want to know the exact reason why using setState outside of useEffect causes an error.
This is happening because, in the first case, when useEffect is not used,
you are updating your state right after declaring it.
Even if you are setting the state as false again, but for react, the state has been updated. And the first rule of thumb for react is, if state update happens, component will re render.
This is why you are getting an infinite rerendering.
your code is following the below flow:
Declare state variable and pass value as false
Update state to false
State updated, hence component re rendered.
Step 1 again.
In second case, where use effect is used, your state will update only when the component is mounted, which means, after that any state update won't trigger your useEffect.
Based on React documentation: https://reactjs.org/docs/hooks-reference.html#usestate
The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.
And there is an addition: https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.) Note that React may still need to render that specific component again before bailing out.
And here is the important part: React may still need to render that specific component again before bailing out.
So yes, the component may still rerender even if the values are the same.

Triggering useEffect only certain conditions

I have basic understanding of useEffect. Without second parameter (dependency array) it runs on every render. With empty array, it runs on first render. With parameters in array, it runs whenever some of parameters changes.
Say I have useEffect with two dependencies (from GraphQL query): result.data and result.loading. I want useEffect to run if result.data changes, and result.loading is false. Purpose is for example to update Redux store:
useEffect(() => {
if (result.loading) return;
dispatch(updatePhotos([...photos, ...result.data.photos]));
}, [result.data, result.loading]);
But there's a catch: I have to include photos to list of dependencies. However, photos variable will be updated in other place, and it triggers this useEffect again.
How can I run useEffect only when those two variables changes?
I can of course use useState to store variable resultFetched, set it to true in useEffect and then dispatch only if it is false. But at some point I have to change it back to true, and useEffect runs again, since I can't manually change result.data or result.loading.
I'm lost how to properly use useEffect in these situations when there is lots of variables to handle.
Currently I'm building infinite scrolling photo list, where list is loaded part by part via GraphQL. But when user opens some photo and eventually returns to photo list, it is restored from Redux to same state and scroll position as it was before opening the photo.
I have spent countless hours trying to get it work, but this useEffect-thing is spoiling my every attempt. :) They always gets triggered before I want them to trigger, because there is so many changing variables.
Also, sometimes I want to run a function within useEffect (function added to dependency array), and I use useCallback for that function to memoize it. But then I also have to add all variables that function uses to dependency array of that useCallback, so function gets regenerated when those variables changes. That means that useEffect suddenly runs again, because the function in dependency array changes.
Is there really no way to use functions/variables in useEffect, without them to trigger useEffect?
It all depends on how updatePhotos works. If that creates an action then the problem is you are creating the new state in the wrong place. The previous value of photos shouldn’t be used here because as you pointed out, that causes a dependency.
Instead your reducer will have the old value of photos you can use and you simply pass the new request data to your reducer.
Described in more detail here: https://overreacted.io/a-complete-guide-to-useeffect/#decoupling-updates-from-actions
You can have two separate useEffect functions inside the same component and they will work independent one of another. use one for photos and one for data loading. I hope this example helps you to wrap your head around this.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
const [step, setStep] = useState(1);
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + step);
}, 1000);
return () => clearInterval(id);
}, [step]);
useEffect(() => {
const id = setInterval(() => {
setCount2((c) => c + step);
}, 1500);
return () => clearInterval(id);
}, [step]);
return (
<div>
<h1>{count}</h1>
<h1>{count2}</h1>
<input value={step} onChange={(e) => setStep(Number(e.target.value))} />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("container"));
Please refer to this example in sandbox
https://codesandbox.io/s/react-playground-forked-6h0oz

Categories

Resources