In the code below, whenever I get new props from the parent, the new props are logged correctly on the console, but the rendered HTML is never updated after the initial render:
export default function(props) {
const [state, setState] = useState(props)
// initially, props.something is defined
// every time props changes (from the parent) props.something is redefined as expected and logged here
console.log(props.something)
// initially, props.something is rendered correctly
// every time props.something changes (from the parent) the HTML never updates
return (
{state.something && <div>{state.something}</div>}
)
}
I already tried using useEffect() even though I don't see the point, but it it didn't fix anything.
State will not update just because props change, this is why you need useEffect.
export default function(props) {
const [state, setState] = useState(props)
useEffect(() => {
setState(props.something)
}, [props.something])
// initially, props.something is defined
// every time props changes (from the parent) props.something is redefined as expected and logged here
console.log(props.something)
// initially, props.something is rendered correctly
// every time props.something changes (from the parent) the HTML never updates
return (<div>{state.something}</div>)
}
adding props.something to the array as the second argument to useEffect tells it to watch for changes to props.something and run the effect when a change was detected.
Update: In this specific example, there is no reason to copy props to state, just use the prop directly.
In your example you copy props to state only once, when initial values set.
It's almost never a good idea to copy props to component state though. You can read about it react docs
Related
why this code give me infinite loop?
import { useState, useEffect } from 'react';
import './style.css';
import { faker } from '#faker-js/faker';
export default function App() {
const data = faker.name.firstName();
const [name, setName] = useState(data);
useEffect(() => {
setName(faker.name.firstName());
}, [name]);
return <div>{name}</div>;
}
https://stackblitz.com/edit/react-ts-6ept5o?file=App.tsx
I want a new generated name using faker everytime i refreshed the page.
Well there's several problems here. Firstly, React state isn't persisted when you refresh the page. To do that you'd have to web storage or some library to help store when changing/refreshing the page. So you don't need an effect here nor to worry about getting new fake data on refresh, you will.
Also, when a functional component re-renders, the entire function gets run again. So this: const data = faker.name.firstName(); gets run every time you render. If you want to use it as an initial state, use the function argument to useState which runs it once on page load: useState(() => faker.name.firstName());
Finally, in regards to the infinite loop, your effect has name as a dependency, but inside it you also set name state. So you're getting a loop.
Component renders:
-> effect runs, setting name state
-> set state triggers re-render with new name
-> new name triggers effect, since name is a dependency
-> effects sets name state to new value
-> set state triggers re-render
and so on. But in this case, you don't actually need an effect at all, given reasons mentioned above
You have a useEffect that runs every time name changes.
When it runs, you're setting name to a new value.
This changes name and triggers the useEffect again.
When it runs, you're setting name to a new value.
This changes name and triggers the useEffect again.
And so on...
You need to understand how useEffect works.
you have set name in useEffect array dependency. So everytime the component renders name changes, as name changes your component renders again and goes into infinity loop. So you leave empty array:
const [name, setName] = useState("");
useEffect(() => {
setName(faker.name.firstName());
},[]);
return <div>{name}</div>
I am doing a project where i have a toast function which implements toast there i call the function of fetching data from api and updating my state so that whenever i click the update feed button fetching data from api function called, updation of state and toast of success appears. Now the question is i have a component of categories post displays seperate category post inside of all post component which has the function to display toast, how could i pass the updated state,fetching data from api function from child component that is category post component to parent component that is all post component to implement toast for category component.
If I understand your question correctly -- at a high level, you're trying to figure out how to update a state variable of a parent component from within a child component. Easiest way would be with the useState hook, and then by passing the setState function to the child component.
const ParentComponent = () => {
const [state, setState] = useState([])
useEffect(() => {
// logic that will be executed every time the state variable changes
}, [state])
return <ChildComponent setState={setState} />
}
const ChildComponent = ({setState}) => {
const handleClick = () => {
setState((currentState) => currentState.concat(1))
}
return <Button onClick={handleClick} />
}
Edit: To answer your question from the comment -- a few things to point out here:
You can pass a value to useState which will be the starting value of the variable. In our example, it's an empty array
setState has access to the current state, so you can push a value to an array with this syntax: setState((previousState) => previousState.concat(val))
useEffect is a hook which is invoked whenever there's a change in the value of the dependency (or dependencies) passed in the second argument. So by including state in its dependency array, we can execute whatever logic we want every time the value of the state variable changes
I would also recommend looking into useMemo. It similarly allows you to have aspects of your component logic that are re-executed only when values of certain variables change. For example:
const ParentComponent = () => {
const [state, setState] = useState([])
useEffect(() => {
// logic that will be executed every time the state variable changes
}, [state])
const renderCards = useMemo(() => {
return state.map(val => <SomeOtherComponent val={val}/>)
}, [state])
return (
<div>
{renderCards}
<ChildComponent setState={setState} />
</div>
)
}
By wrapping the function inside renderCards in the useMemo hook, the evaluated result is "memoized". And so it won't be executed on every render, unless the variable in the dependency array changes.
Passing down setState to a child component in order to trigger a re-render in the parent component is straightforward when it's an immediate child. If the child component is nested deeper, or there are multiple components that need to react to a change in a variable (e.g. light/dark mode) -- that's when you want to look into a state management tool like Redux or Context.
There are two ways I can think of to achieve what you are trying to do here, i.e. get the child component's state in a parent component.
You can make use of refs. You should be familiar with React hooks to use this approach. The documentation is really good.
You can also make use of a global state using either Redux or Context.
I have a component that is being called, then called again with updated information, but the var topics is not being updated by react. Can someone explain why the first example does not update on the screen, but the second one does? Does it have something to do with the useState call?
App.js calls DisplayQuestions once during initial render, then the page takes in user input and re-renders.
<DisplayQuestionsByTopic topics={topics} />
.....
topics var is NOT updated on screen:
export function DisplayQuestionsByTopic(props) {
console.log(`DisplayQuestionsByTopic`, props.topics)
const [topics, setTopics] = useState(props.topics) //<<<<<<<<
render(<h1>topics.join(',')</h1>)
}
topics var IS updated on screen:
export function DisplayQuestionsByTopic(props) {
console.log(`DisplayQuestionsByTopic`, props.topics)
render(<h1>props.topics.join(',')</h1>) //<<<<<<<<<<<
}
The argument passed to useState is only considered the first time the component renders - that is, on mount. On mount, the prop is put into the topics state. On further renders, changes to the prop don't result in a change to the state because the state has already been initialized to its initial value before.
While you could use useEffect to change the state in the child when the prop changes:
useEffect(() => {
setTopics(props.topics);
}, [props.topics]);
Unless you're using setTopics somewhere, it would make more sense just to render the prop, like you're doing in the final snippet.
If you do need to do setTopics in the child, consider passing down the state setter from the parent instead - that way, all the state is handled in the parent, and you don't have duplicate state holders in different components that need to be synchronized.
I have a question about useRef: if I added ref.current into the dependency list of useEffect, and when I changed the value of ref.current, the callback inside of useEffect won't get triggered.
for example:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Also I know I can use useState here. This is not what I am asking. And also I know that ref stay referentially the same during re-renders so it doesn't change. But I am not doing something like
const myRef = useRef(1);
useEffect(() => {
//...
}, [myRef]);
I am putting the current value in the dep list so that should be changing.
I know I am a little late, but since you don't seem to have accepted any of the other answers I'd figure I'd give it a shot too, maybe this is the one that helps you.
Shouldn't it be when useRef.current changes, the stuff in useEffect gets run?
Short answer, no.
The only things that cause a re-render in React are the following:
A state change within the component (via the useState or useReducer hooks)
A prop change
A parent render (due to 1. 2. or 3.) if the component is not memoized or otherwise referentially the same (see this question and answer for more info on this rabbit hole)
Let's see what happens in the code example you shared:
export default function App() {
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<div className="App">
<button
onClick={() => {
myRef.current = myRef.current + 1;
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
</div>
);
}
Initial render
myRef gets set to {current: 1}
The effect callback function gets registered
React elements get rendered
React flushes to the DOM (this is the part where you see the result on the screen)
The effect callback function gets executed, "myRef current changed" gets printed in the console
And that's it. None of the above 3 conditions is satisfied, so no more rerenders.
But what happens when you click the button? You run an effect. This effect changes the current value of the ref object, but does not trigger a change that would cause a rerender (any of either 1. 2. or 3.). You can think of refs as part of an "effect". They do not abide by the lifecycle of React components and they do not affect it either.
If the component was to rerender now (say, due to its parent rerendering), the following would happen:
Normal render
myRef gets set to {current: 1} - Set up of refs only happens on initial render, so the line const myRef = useRef(1); has no further effect.
The effect callback function gets registered
React elements get rendered
React flushes to the DOM if necessary
The previous effect's cleanup function gets executed (here there is none)
The effect callback function gets executed, "myRef current changed" gets printed in the console. If you had a console.log(myRef.current) inside the effect callback, you would now see that the printed value would be 2 (or however many times you have pressed the button between the initial render and this render)
All in all, the only way to trigger a re-render due to a ref change (with the ref being either a value or even a ref to a DOM element) is to use a ref callback (as suggested in this answer) and inside that callback store the ref value to a state provided by useState.
https://reactjs.org/docs/hooks-reference.html#useref
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead.
use useCallBack instead, here is the explanation from React docs:
We didn’t choose useRef in this example because an object ref doesn’t
notify us about changes to the current ref value. Using a callback ref
ensures that even if a child component displays the measured node
later (e.g. in response to a click), we still get notified about it in
the parent component and can update the measurements.
Note that we pass [] as a dependency array to useCallback. This
ensures that our ref callback doesn’t change between the re-renders,
and so React won’t call it unnecessarily.
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
Ok so I think what you're missing here is that changing a ref's value doesn't cause a re-render. So if it doesn't cause re-renders, then the function doesn't get run again. Which means useEffect isn't run again. Which means it never gets a chance to compare the values. If you trigger a re-render with a state change you will see that the effect will now get run. So try something like this:
export default function App() {
const [x, setX] = useState();
const myRef = useRef(1);
useEffect(() => {
console.log("myRef current changed"); // this only gets triggered when the component mounts
}, [myRef.current]);
return (
<button
onClick={() => {
myRef.current = myRef.current + 1;
// Update state too, to trigger a re-render
setX(Math.random());
console.log("myRef.current", myRef.current);
}}
>
change ref
</button>
);
}
Now you can see it will trigger the effect.
I want to create a component Person that is fully controlled by its state. It should also be able to sync the props change (firstName, lastName) passed from its parent component to the state. I have the following code. It does what I want which is syncing props to state and re-render after state has been changed.
However one issue I noticed is that useEffect gets invoked after DOM update triggered by the parent props change. So the initial re-render is wasted since I only want it to re-render after useEffect gets invoked and state is changed.
import React,{useState, useEffect} from 'react';
const Person = ({firstName, lastName}) => {
const [name, setName] = useState(firstName + lastName)
useEffect(() => {
setName(firstName + lastName);
console.log("state changed!");
}, [firstName, lastName])
console.log("re-render!");
return <div>render {name}</div>;
}
export default Person;
I created a simple demo here https://codesandbox.io/s/sparkling-feather-t8n7m. If you click the re-render button, in the console you will see below output. The first re-render! is triggered by props change which I want to avoid. Any idea how to achieve this? I know there are other solutions such as making it fully uncontrolled, but I'd like to know if there is any workaround to this solution
re-render!
state changed!
re-render!
you will need to add a condition in your useEffect. something like :
const [didUpdate, setDidUpdate] = useState(false);
useEffect(() => {
if(didUpdate){
setName(firstName + lastName);
console.log('state changed!');
} else {
setDidUpdate(true);
}
}, [firstName, lastName]);
Here it reproduce the componentDidUpdate() behavior.
On the first rendering, component is mounted, didUpdate is initialised to false, so the effect will only set it to true for the next updates.
Note that a state (useState) initialised with a props isn't updated when the prop changes.
It's basically React core behavior, according to doc
The function passed to useEffect will run after the render is committed to the screen.
in order to avoid bugs caused by side effects.
I guess you simplified your example a bit, but you should know that copying props to state unconditionally is an anti-pattern https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#anti-pattern-unconditionally-copying-props-to-state
Instead, you should directly use props :
const Person = ({firstName, lastName}) => (
<div>render {firstName + lastName}</div>;
);