I have a component which passes an object as a prop to its children, as well as a function to update this object.
Now, I've noticed in one of the children that reading a nested property of the prop (obj.nested.property) object always returns the initial value as it is on mount, although the prop (obj) is updated successfully - I can see this in react dev tools, and also from the useEffect console.log
Here's the structure:
const Parent = () => {
const obj = useSelector(config);
const setObj = (newObj) => {
// update obj code
}
return (
<Child obj={obj} onUpdate={setObj}/>
)
}
const Child = ({ obj, onUpdate }) => {
useEffect(() => {
console.log(obj.nested.property);
// here everything is correct. Every time the obj is updated, this logs the new
// obj.nested.property value
}, [obj])
const onBlur = (newValue) => {
if (newValue !== obj.nested.property) {
// here, after you change the value once, and then just click inside the input
// and blur, this condition is always true, because obj.nested.property keeps the
// value as it was on mount
onUpdate(newValue)
}
}
return (
<Input value={obj.nested.property} onBlur={onBlur}/>
)
}
Did you try to make a useState with that object?
const Child = ({ propObj as obj, onUpdate }) => {
const [obj,setObj]= useState(propObj);
useEffect(() => {
console.log(obj.nested.property);
// here everything is correct. Every time the obj is updated, this logs the new
// obj.nested.property value
}, [obj])
const onBlur = (newValue) => {
if (newValue !== obj.nested.property) {
// here, after you change the value once, and then just click inside the input
// and blur, this condition is always true, because obj.nested.property keeps the
// value as it was on mount
onUpdate(newValue)
}
}
return (
<Input value={obj.nested.property} onBlur={onBlur}/>
)
}
According to react documentation, react doesn't update state immediately after calling setState function(because setState is async). new value will set in state after re-rendering the component(here is Child re-render).
React documentation says:
setState() does not immediately mutate state but creates a pending state transition. Accessing state after calling this method can potentially return the existing value. There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
therefore, immediately after calling component setState function and updating state(before re-rendering of component), the value of state is previous value. but in useEffect that calls after re-rendering of component, state has new value.
You can see this for more info.
Related
I am having a subscription, which I set up in the useEffect() Hook. Based on a variable from the store, I want to execute code (or not) which is also part of the body of the subscription.
const variableFromStore = useSelector(state => state.variableFromStore);
const dispatch = useDisptach();
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo());
}
});
})
Initially, variableFromStore is false. However, during interaction with the App (which includes unmounting the component, because the App is moved to background), it gets set to true. Then, some time later, subscriptionEvent gets fired.
But, when I console.log(variableFromStore)in there, it is always false, eventhough the redux debugger tells me it is true...
Is it not possible to pass a state/store variable into a subscription?
I assume this is because I am setting up the subscription in the useEffect() hook, which only gets executed once. But, if I set it up like this
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo());
}
});
},[variableFromStore])
Wouldn't that reinitialize the subscription every time variableFromStore changes?
This happens because your useEffect callback runs only once, and the variableFromStore is enclosed by callback closure, to fix that, redux provides another way of getting the state from the store by calling the store getState method, which returns your whole state object, so you need to import your store first, from wherever it was created, and call getState method, this way the same last value will be returned as per using the selector, but that is not affected by closure:
import store from '...'
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
const { variableFromStore } = store.getState()
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo());
}
});
})
If you use the useEffect with empty or without a dependency array, it will only run on the first render.
If you add the variable you want to use or run the effect on the variable's value change add that variable to the useEffect's dependency array.
Try this code down below and also check out this documentation for more.
const variableFromStore = useSelector(state => state.variableFromStore);
useEffect(() => {
const subscriptionEvent = SomeModule.AddSubscription(event => {
console.log(variableFromStore);
if(variableFromStore) {
dispatch(foo())
}
});
}, [variableFromStore])
I have a component with inner state that I want to update its initial state using props so I have to watch the props too. however I'd like to know when its the props that changed so that I run the function related to it once and keep it away from the inner component state.\
This is because I am unable to properly update the inner state as that thing will always run everytime the useEffect triggers
the following useEffect is for a Modal that I am setting visible or not from the parent component state(so its always mounted).
useEffect(() => {
// I want to run the following once
if (Object.getOwnPropertyNames(propState).length !== 0) {
console.log(propState);
setState(()=> {
innerState = propState.user_name
});
}
}, [propState, innerState]);
You can check value in inner state and propState. If they equal, when skip setState.
useEffect(() => {
if (propState?.user_name && propState !== innerState.user_name) {
setState(()=> {
innerState = propState.user_name
});
}
}, [propState, innerState]);
I'm trying to understand what happens when you have both props and useState in one component.
I wrote little example of it which has one parent component that prints its numbers with another child component -
const MyNumbers = (props) => {
const [numbers, setNumbers] = useState([...props.arr]);
function changeNumbers() {
setNumbers((nums) => [...nums.map(() => Math.floor(Math.random() * 10))]);
}
return (
<div className="MyNumbers">
<div>
<button onClick={changeNumbers}>Chane numbers</button>
</div>
<div>
{numbers.map((num, idx) => (
<SingleNumber key={idx} num={num}></SingleNumber>
))}
</div>
</div>
);
};
const SingleNumber = (props) => {
const [num] = useState(props.num);
useEffect(() => {
console.log("useEffect called");
});
return <h3>The number is {num}</h3>;
};
Here is the above demo
The SingleNumber component uses useState and as you can see clicking on the "Change numbers" action doesn't change the values in the children component.
But when I wrote almost the same code but now SingleNumber doesn't use useState then clicking on the "Change numbers" changes all the values in the children component (like in this demo).
Is it correct to say that a function component with a useState renders once and then only changed if the state changed but not if the props changed ?
OFC the component "rerenders" when the props change, the useEffect hook in SingleNumber is showing you that the "render phase" is run each time the props change.... effects are run each time the component is rendered.
const SingleNumber = (props) => {
const [num] = useState(props.num);
useEffect(() => {
console.log("useEffect called"); // <-- logged each time the component renders
});
return <h3>The number is {num}</h3>;
};
If you added a dependency on props.num and updated the local state (don't actually do this, it's an anti-pattern in react!), you'll see the UI again update each time the props update.
To answer your queston:
Is it correct to say that a function component with a useState renders
once and then only changed if the state changed but not if the props
changed?
No, this is not technically correct to say if "render" to you means strictly react rendered the component to compute a diff, react components rerender when state or props update. Yes, if "render" more generally means you visually see the UI update.
When you call useState it returns an array with two values in it:
The current value of that bit of the state
A function to update the state
If there is no current value when it sets the state to the default value and returns that.
(The default value is the argument you pass to useState).
If you change the values of props in your example, then the component rerenders.
useState returns the current value of that bit of the state. The state has a value, so it doesn't do anything with the argument you pass to useState. It doesn't matter that that value has changed.
Since nothing else has changed in the output, the rerendered component doesn't update the DOM.
Is it correct to say that a function component with a useState renders once and then only changed if the state changed but not if the props changed?
No, it does rerender but doesn't commit the changes.
When parent component MyNumbers re-renders by clicking changeNumbers, by default (unless React.memo used) all its children components (like SingleNumber) will be re-render.
Now when SingleNumber rerenders, notice useState docs.
During the initial render, the returned state (state) is the same as the value passed as the first argument (initialState).
You initial the state useState(props.num) but it can only be changed by calling the setter function, therefore the state num won't change because you never calling the setter.
But it will rerender on parent render as mentioned above (notice the useEffect logs).
You don't need to use useState in SingleNumber.
because useState called only once when it rendered.
const SingleNumber = (props) => {
// const [num] = useState(props.num);
// useEffect(() => {
// console.log("useEffect called");
// });
return <h3>The number is {props.num}</h3>;
};
if you want to use useState, you can use like this.
const SingleNumber = (props) => {
const [num, setNum] = useState(props.num);
useEffect(() => {
console.log("useEffect called");
setNum(props.num);
}, [props.num]);
return <h3>The number is {num}</h3>;
};
Going through a TypeScript + React course and building a todo list. My question is about a react feature though.
In the handler for adding a Todo, there is this function declared in setState
const App: React.FC= () => {
const [todos, setTodos] = useState<Todo[]>([])
const todoAddHandler = (text: string) => {
// when its called.... where does the prevTodos state come from?
setTodos(prevTodos => [...prevTodos,
{id: Math.random().toString(), text: text}])
}
return (
<div className="App">
<NewTodo onAddTodo={todoAddHandler}/>
<TodoList items={todos}></TodoList>
</div>
);
}
export default App;
When the function is called in setState, it automatically calls the current state with it. Is this just a feature of setState? That if you declare a function within it the parameter will always be the current state when the function is called?
Was very confused when this parameter just... worked. :#
TL;DR Is this just a feature of setState? - Yes
useState is a new way to use the exact same capabilities that this.state provides in a class
Meaning that its core still relies on old this.setState({}) functionality. If you remember using this.setState(), you will know that it has a callback function available, which can be used like this:
this.setState((currentState) => { /* do something with current state */ })
This has now been transfered to useState hook's second destructured item [item, setItem] setItem, thus it has the same capability:
setItem((currentState) => { /* do something with current state */ })
You can read more about it here
With hooks, React contains an internal mapping of each state name to its current value. With
const [todos, setTodos] = useState<Todo[]>([])
Whenever setTodos is called and todos state is set again, React will update the internal state for todos to the new value. It will also return the current internal state for a variable when useState is called.
You could think of it a bit like this:
// React internals
let internalState;
const setState = (param) => {
if (typeof param !== 'function') {
internalState = param;
} else {
param(internalState);
}
};
const useState = initialValue => {
internalState ??= initialValue;
return [internalState, setState];
}
Then, when you call the state setter, you can either pass it a plain value (updating internalState), or you can pass it a function that, when invoked, is passed the current internal state as the first parameter.
Note that the prevTodos parameter will contain the current state including intermediate updates. Eg, if you call setTodos twice synchronously before a re-render occurs, you'll need to use the callback form the second time in order to "see" the changes done by the first call of setTodos.
I have got an hook who catch getBoundingClientRect object of a ref DOM element. The problem is, at the first render, it return null and I need to get the value only on first render on my component.
I use it like that in a functional component:
const App = () => {
// create ref
const rootRef = useRef(null);
// get Client Rect of rootRef
const refRect = useBoundingClientRect(rootRef);
useEffect(()=> {
// return "null" the first time
// return "DOMRect" when refRect is update
console.log(refRect)
}, [refRect])
return <div ref={rootRef} >App</div>
}
Here the useBoundingClientRect hook, I call in App Component.
export function useBoundingClientRect(pRef) {
const getBoundingClientRect = useCallback(() => {
return pRef && pRef.current && pRef.current.getBoundingClientRect();
}, [pRef]);
const [rect, setRect] = useState(null);
useEffect(() => {
setRect(getBoundingClientRect());
},[]);
return rect;
}
The problem is I would like to cache boundingClientRect object on init and not the second time component is rerender :
// App Component
useEffect(()=> {
// I would like to get boundingClientRect the 1st time useEffect is call.
console.log(refRect)
// empty array allow to not re-execute the code in this useEffect
}, [])
I've check few tutorials and documentations and finds some people use useRef instead of useState hook to keep value. So I tried to use it in my useboundingClientRect hook to catch and return the boundingClientRect value on the first render of my App component. And it works... partially:
export function useBoundingClientRect(pRef) {
const getBoundingClientRect = useCallback(() => {
return pRef && pRef.current && pRef.current.getBoundingClientRect();
}, [pRef]);
const [rect, setRect] = useState(null);
// create a new ref
const rectRef = useRef(null)
useEffect(() => {
setRect(getBoundingClientRect());
// set value in ref
const rectRef = getBoundingClientRect()
},[]);
// return rectRef for the first time
return rect === null ? rectRef : rect;
}
Now the console.log(rectRef) in App Component allow to access the value on first render:
// App Component
useEffect(()=> {
console.log(refRect.current)
}, [])
But If I try to return refRect.current from useBoundingClientRect hook return null. (What?!)
if anyone can explain theses mistakes to me. Thanks in advance!
You need to understand references, mututation, and the asynchronous nature of updates here.
Firstly, when you use state to store the clientRect properties when your custom hook in useEffect runs, it sets value in state which will reflect in the next render cycle since state updates are asynchronous. This is why on first render you see undefined.
Secondly, when you are returning rectRef, you are essentially returning an object in which you later mutate when the useEffect in useBoundingClientRect runs. The data is returned before the useEffect is ran as it runs after the render cycle. Now when useEffect within the component runs, which is after the useEffect within the custom hook runs, the data is already there and has been updated at its reference by the previous useEffect and hence you see the correct data.
Lastly, if you return rectRef.current which is now a immutable value, the custom hook updates the value but at a new reference since the previous one was null and hence you don't see the change in your components useEffect method.