React update useState() when variable value changes - reactjs

How to update UseState() when variable 'matchUrl' changes. when navigating to another route the 'matchUrl' value changes. and the changes is reflected on console.log and on the 'h1' tag. but not the usestate(), it keeps the old value.
import React, {useState} from 'react'
import useInfinityScroll from './hooks/useInfinityScroll'
import names form './data/names'
function TableList({ match }) {
let matchUrl = match.params.name
const dataMatch = names.filter(name => name.occupation === matchUrl)
const [listNames, setListNames] = useState(dataMatch.slice(0, 10))
const [isFetching, setIsFetching] = useInfinityScroll(fetchMoreListItems) //fetches more items when scrolling to bottom of page
function fetchMoreListItems() {
setTimeout(() {
setListNames([...dataMatch.slice(0, 20)])
setIsFetching(false)
}, 2000)
}
return (
<>
<h1>{matchUrl}</h1>
<div>
{listNames.filter(name => name.occupation === matchUrl).map(listNames => <Table key={listNames.id} name={listNames} />)}
</div>
</>
)
}
export default TableList

useState hook takes initial value of the state as an argument, but it only assign the initial value to the state at the component mounting phase, not during re-rendering of the component if anything changes. Therefore, after initial rendering, state won't be changed even if the dataMatch variable changes.
Therefore, you need to have useEffect hook having dependency on dataMatch variable to keep track on its changes. Then you will be able to update the state when you've component re-rendering.
Use the following way to update the state.
useEffect(() => {
setListNames(dataMatch.slice(0, 10));
}, [dataMatch]);
For more information on useEffect hook, please refer https://reactjs.org/docs/hooks-effect.html
Hope this will solve your issue.

You haven't set the state for 'matchUrl'.Do the following modification to your code.
function TableList({ match }) {
let [matchUrl, setMatchUrl] = useState(match.params.name);
return (<><h1>{matchUrl}</h1></>);
}
Also when you change 'matchUrl' manually for any reason, you should use setMatchUrl('value') function to update the value

Here is the sample code. Hope this will help you 😇
import React, {useState, useEffect} from 'react'
function TableList({ match }) {
let matchUrl = match.params.name
useEffect(() => {
// YOUR_CODE_HERE WILL RUN WHEN the match.params.name is change
}, [match.params.name])
}
you can take a look also about documentation about useEffect

Related

Why is my boolean state value not toggling?

I know there are other articles and posts on this topic and almost all of them say to use the ! operator for a Boolean state value. I have used this method before but for the life of me I can not toggle this Boolean value.
import { useState } from 'react';
const [playerTurn, setPlayerTurn] = useState(true);
const changePlayerTurn = () => {
console.log(playerTurn); // returns true
setPlayerTurn(!playerTurn);
console.log(playerTurn); // also returns true
};
changePlayerTurn();
I have also tried setPlayerTurn(current => !current), commenting out the rest of my code to avoid interference, and restarted my computer in case that would help but I am still stuck with this issue.
Can anyone point out why this is not working?
The setPlayerTurn method queues your state change (async) so reading the state directly after will provide inconsistent results.
If you use your code correctly in a react component you will see that playerTurn has changed on the next render
You creating a async function, to solve this you can create a button in your component, which will run the function and you can use the "useEffect" hook to log every time the boolean changes... so you can see the changes taking place over time, like this:
import React, { useEffect } from "react";
import { useState } from "react";
const Player = () => {
const [playerTurn, setPlayerTurn] = useState(true);
useEffect(() => {
console.log(playerTurn);
}, [playerTurn]);
return <button onClick={() => setPlayerTurn(!playerTurn)}>change player turn</button>;
};
export default Player;
This is happening because setPlayerTurn is async function.
You can use another hook useEffect() that runs anytime some dependencies update, in this case your playerTurn state.
export default YourComponent = () => {
const [playerTurn, setPlayerTurn] = useState(true);
useEffect(() => {
console.log('playerTurn: ', playerTurn);
}, [playerTurn]);
const changePlayerTurn = () => {
setPlayerTurn(!playerTurn);
}
return (
<button onClick={changePlayerTurn}>Click to change player turn</button>
);
}
Basically whenever you use setState React keeps a record that it needs to update the state. And it will do some time in the future (usually it takes milliseconds). If you console.log() right after updating your state, your state has yet to be updated by React.
So you need to "listen" to changes on your state using useEffect().
useEffect() will run when your component is first mounted, and any time the state in the dependencies array is updated.
The value of the state only changes after the render. You can test this like:
// Get a hook function
const Example = ({title}) => {
const [playerTurn, setPlayerTurn] = React.useState(true);
React.useEffect(() => {
console.log("PlayerTurn changed to", playerTurn);
}, [playerTurn]);
console.log("Rendering...")
return (<div>
<p>Player turn: {playerTurn.toString()}</p>
<button onClick={() => setPlayerTurn(!playerTurn)}>Toggle PlayerTurn</button>
</div>);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
The callback inside the useEffect runs during the component mount and when one of the values inside the second argument, the dependecy array, changes. The depency here is playerTurn. When it changes the console will log.
As you will see, before this happens, the "Rendering..." log will appear.

Why is the updated state variable not shown in the browser console?

I understand that the method returned by useState is asynchronous, however, when I run the this code, I am delaying the console.log by upto 5 seconds, but it still logs the previous value and not the updated value of the state variable. The updated value would be 2, but it still logs 1. In the react developer tools however, I can see the state changing as I press the button, though I am curious to know why after even such a delay the console prints an obsolete value? This is not the case with class components and setState but with function components and useState.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
setTimeout(() => {
console.log(variable);
}, 2000);
};
return <button onClick={handleClick}>Button</button>;
}
export default App;
In your code your setTimeout is getting the variable value from the closure at the time it was invoked and the callback function to the setTimeout was created. Check this GitHub issue for the detailed explanation.
In the same issue, they talk about utilizing useRef to do what you are attempting. This article by Dan Abramov packages this into a convenient useInterval hook.
State updates are asynchronous. That means, that in order to view the new value, you need to log It on the next render using useEffect and adding it to the dependencies array:
In this example, give a look at the order the logs appear:
First, you will have the current one, and once triggered, you will have the new value, and then it will become the 'old value' until triggered again.
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => { console.log(`new state rolled: ${counter}`);
}, [counter]);
console.log(`Before rolling new State value: ${counter}`);
const handleClick = () => setCounter(counter++)
return <button onClick={handleClick}>Button</button>;
}
export default App;
Another technic to console.log a value afterward a state change is to attach a callback to the setState:
setCounter(counter++, ()=> console.log(counter));
I hope it helps.
A state take some time to update. The proper way to log state when it updates, is to use the useEffect hook.
setTimeout attaches the timer and wait for that time, but it will keep the value of variable from the beginning of the timer, witch is 1
import "./App.css";
import React, { useState, useEffect } from "react";
function App() {
const [variable, setVariable] = useState(1);
const handleClick = () => {
setVariable(2);
};
useEffect(() => {
console.log(variable);
}, [variable]);
return <button onClick={handleClick}>Button</button>;
}
export default App;
This is not the case with class components and setState but with
function components and useState
In class components, React keep the state in this.state & then call the Component.render() method whenever its need to update due to a setState or prop change.
Its something like this,
// pseudocode ( Somewhere in React code )
const app = MyClassComponent();
app.render();
// user invoke a callback which trigger a setState,
app.setState(10);
// Then React will replace & call render(),
this.state = 10;
app.render();
Even though you cannot do this.state = 'whatever new value', React does that internally with class components to save the latest state value. Then react can call the render() method and render method will receive the latest state value from this.state
So, if you use a setTimeout in a class component,
setTimeout(() => {
console.log(this.state) // this render the latest value because React replace the value of `this.state` with latest one
}, 2000)
However in functional component, the behaviour is little bit different, Every time when component need to re render, React will call the component again, And you can think the functional components are like the render() method of class components.
// pseudocode ( Somewhere in React code )
// initial render
const app = MyFuctionalComponent();
// state update trigger and need to update. React will call your component again to build the new element tree.
const app2 = MyFunctionalComponent();
The variable value in app is 1 & variable value in app2 is 2.
Note: variable is just a classic variable which returned by a function that hooked to the component ( The value save to the variable is the value return by the hook when the component was rendering so it is not like this.state i.e its hold the value which was there when the component is rendering but not the latest value )
Therefore according to the Clouser, at the time your setTimeout callback invoke ( Which was called from app ) it should log 1.
How you can log the latest value ?
you can use useEffect which getting invoke once a render phase of a component is finished. Since the render phase is completed ( that mean the local state variables holds the new state values ) & variable changed your console log will log the current value.
useEffect(() => {
console.log(variable);
}, [variable])
If you need the behaviour you have in class components, you can try useRef hook. useRef is an object which holds the latest value just like this.state but notice that updating the value of useRef doesn't trigger a state update.
const ref = useRef(0);
const handleClick = () => {
setVariable(2); // still need to setVariable to trigger state update
ref.current = 2 // track the latest state value in ref as well.
setTimeout(() => {
console.log(ref.current); // you will log the latest value
}, 2000);
};

Does setState function of useState hook trigger re-render of whole component or just the returned JSX part of the code?

I am a beginner in React and I am learning it from a udemy course. Initially I thought the whole code inside the functional component gets re rendered/re run after state update. But then I saw this implementation of countdown before redirect and got confused.
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
const LoadingToRedirect = () => {
const [count, setCount] = useState(5);
let history = useHistory();
useEffect(() => {
const interval = setInterval(() => {
setCount((currentCount) => --currentCount);
}, 1000);
// redirect once count is equal to 0
count === 0 && history.push("/");
// cleanup
return () => clearInterval(interval);
}, [count]);
return (
<div className="container p-5 text-center">
<p>Redirecting you in {count} seconds</p>
</div>
);
};
export default LoadingToRedirect;
So why is setInterval needed here if setCount triggers a re-render of the whole code? Can't we do the same thing with setTimeout? So I tried that and it worked. Surely there is a reason he did it that way? Am I missing something?
Of course React re-renders the whole component but it also depends on some conditions. For example if you look at your code you have passed count variable as a dependency to useEffect hook, it means if the value of count changes React will render the effect inside of the useEffect hook.
Yes, you can achieve the same using setTimeout;setInterval is
pointless because it totally depends on count variable you passed as a
dependency.
if you remove count as a dependency then you can easily see it will
not redirect you the required page.

Why useRef can be used to store the setInterval reference in react but not a normal variable?

I am trying to understand the useRef hook in React.
I have created a simple time in react . The code for which is available below.
import { useRef, useState, useEffect } from 'react';
function Parent() {
const [count,setCount]=useState(5);
const ref=useRef(0);
//let hold=0;
const countdown=()=>{
ref.current=setInterval(()=>{
// console.log('ref.current-->',ref.current);
setCount((c)=>c-1)
},1000)
}
useEffect(()=>{
if(count<1)
clearInterval(ref.current)
},[count])
return(
<>
<h3>Timer : {count}</h3>
<br/>
<button onClick={countdown}>countdown</button>
</>
)
}
export default Parent;
Here I have created a ref using the hook and I am monitoring the count state . When it hits 0 , I am calling the 'clearInteval' function to clear up the timer.
This code is working fine .
But when I try to do the same using a normal variable rather than the one created by the hook , the interval is not getting cleared .
Please find below the code for the same.
import { useRef, useState, useEffect } from 'react';
function Parent() {
const [count,setCount]=useState(5);
const ref=useRef(0);
let hold=0;
const countdown=()=>{
hold=setInterval(()=>{
// console.log('ref.current-->',ref.current);
setCount((c)=>c-1)
},1000)
}
useEffect(()=>{
if(count<1)
clearInterval(hold)
},[count])
return(
<>
<h3>Timer : {count}</h3>
<br/>
<button onClick={countdown}>countdown</button>
</>
)
}
export default Parent;
What am I not understanding here ?
This code should have worked in normal javaScript.
const myRef = useRef() will provide a ref object such that myRef.current's value will persist across renders. When you use let myVar = something, myVar will be re-created on each render, so you will lose and replace its value each time.
Your Parent component is getting re-rendered each time your state changes, so you benefit from keeping your interval reference as a ref from useRef.

useEffect hook - dependencies - re-render issues

The first case:
_Let's say I have a prop that is in redux state or in parent state.
_I do not want the useEffect to fire whenever this prop changes,
_But I do need to use the prop within the useEffect.
_React warns me to add the prop to the dependency array, but if I do so, then the useEffect will fire again.
The second case:
_I am using a function within a useEffect,
_But the function is also needed elsewhere.
_Don't want to duplicate the function code.
_React wants me to add the function to the dependency array, but I don't want the useEffect to fire every time that function reference changes.
While working with useEffect you should think about closures. If any props or local variable is used inside the useEffect then it is advised to include it inside your dependency array else you will be working with the stale data due to the closure.
Here I present a use case. Inside ComponentUsingRef we are using ref which works as the container. You can find more about it at https://reactjs.org/docs/hooks-reference.html#useref
Advantage of this approach is that you wont be bound to memoize fn in
your parent component. You will always be using latest value of
function and in the given case it won't even cause firing of useEffect as it won't be on your dependency
const Component=({fn})=>{
useEffect(()=>{
fn()
},[fn])
.....
.....
return <SomeComponent {...newProps}/>
}
const ComponentUsingRef=({fn}){
const fnRef = useRef(fn)
fnRef.current = fn // so that our ref container contains the latest value
useEffect(()=>{
fn.current()
},[ ])
.....
.....
return <SomeComponent {...newProps}/>
}
If you want to use abstraction then you should extract your logic inside the custom hook
If the only issue is the warning then don't worry about it, you can simply disable the rule for your effect and omit the prop from the dependency array if it's the only prop being used in the effect. For effects with multiple dependencies, you can use useRef to store the latest value of the prop without firing the effect, like this:
const Comp = ({someProp, ...props}) => {
const myProp = useRef(someProp);
myProp.current = someProp;
// this effect won't fire when someProp is changed
React.useEffect(() => {
doSomething(myProp.current, props.a, props.b);
}, [props.a, props.b, myProp]);
}
For your second case, I'd probably put that function in a separate file and import it in the components where it'll be used, however, you can use the same pattern for your second case.
To ignore the warnings react gives you, you can disable them by adding // eslint-disable-next-line react-hooks/exhaustive-deps to the useEffect code. You can read more about this in the Rules of Hooks section in the React documentation. These rules are included in the eslint-plugin-react-hooks package.
To prevent the useEffect from firing everytime the function reference changes, you can use useCallback. The useCallback hook will store the reference to the function instead of the function itself. The reference of the function will only be updated when one of the dependencies of the function is updated. If you don't want the function reference to be updated ever, you can leave the dependency array empty in the same way as the dependency array of the useEffect hook.
Here is a working example of both:
import React, { useState, useEffect, useCallback } from "react";
import "./styles.css";
export default function App() {
const [value, setValue] = useState(0);
const wrappedFunction = useCallback(
(caller) => {
const newValue = value + 1;
setValue(newValue);
console.log(`Firing function from ${caller}: ${newValue}`);
},
[value]
);
useEffect(() => {
console.log(`Logging from useEffect: ${value}`);
wrappedFunction("useEffect");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div>{value}</div>
<button onClick={() => wrappedFunction("button click")}>
Fire function
</button>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Refer this link for the First Problem Sandbox
Here we are just using a state variable to hold the initial value, whether its coming from the Parent or Store, so Even if the value change inside the Parent or store, the child will still be on the older value, until unless it updates it using useEffect.
Parent
export default function App() {
const [state, setState] = useState("monika");
return (
<div className="App">
<h1>My Name is {state}</h1>
<button
onClick={() => {
setState("Rohan");
}}
>
change name
</button>
<First username={state} />
</div>
);
}
Child
export default function First({ username }) {
const [name, setName] = useState(username);
return (
<div>
<h1>My Name is {name}</h1>
</div>
);
}

Resources