react render state child component vars not updated? - reactjs

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.

Related

How does Redux useSelector affect on react component rendering?

I don't understand how does my component Word will rerender. I have a redux state keeping my {history: {letters}} state. So the question is: If {letters} are passed into useEffect deps array, will my component Word rerender if {words} property is changed?
`
function Word() {
const { history: {letters, words} } = useAppSelector(state => state)
useEffect(() => {
}, [letters])
return (
<div>
</div>
)
}
`
I expect my component rerender only if letters are changed.
You are maintaining a state using Redux. So, the component re-renders if any state used in the component itself is changed. In your case, your Word component will re-render if letters or words or both got changed. That's how it works.
BTW, your useEffect should only be triggered upon any change in letters only since you have included only letters in its dependency array.
If you want to optimize the performance, you can memorize things using useMemo and useCallback wherever necessary. Pass the dependencies correctly to recalculate them only upon required state changes.
UseSelector will already rerender if the selected part of the state has change. you dont have to add [letters] on useEffect
UseSelector is subscribed to the state and if detects any change, it calls checkForUpdates
subscription.onStateChange = checkForUpdates
and checkForUpdates checks if the reference of the state changed, if it did. it calls forceRender function so your component renders.
github useSelector
useEffect is used to dispatch actions to populate the state before the component first renders so useSelector will have access to newly populated the state

How to force an update for a functional component?

I'm learning redux and want to find out how useSelector updates a component, because the component doesn't have its own state.
I understand that useSelector() subscribes the component to the store, and when the store is updated, the component also updates.
Class components have this.forceUpdate(), but functional components don't have it.
How does one force-update a functional component?
You can simply do this
Add a dummy state that you can change to reliably initiate a re-render.
const [rerender, setRerender] = useState(false);
...
//And whenever you want to re-render, you can do this
setRerender(!rerender);
And this will re-render the component, since components always re-render on state change
The react-redux package relies on the rendering engine of react/react-dom to trigger the re-render of a given component that uses the useSelector hook.
If you take a look at the source of useSelector you can notice the use of useReducer:
const [, forceRender] = useReducer((s) => s + 1, 0)
As the name (forceRender) implies, redux uses this to trigger a re-render by react.
With v8 of react-redux the implementation of this mechanism changes but still relies on react-hooks for the re-render.
If you are curious how React handles re-renders, take a look at this excellent SO answer. It provides a great entry on the implementation details of how react-hooks are associated with the calling component.
I don't repeat Ryan here, but to sum it up:
The renderer keeps a reference to the component that is currently rendered. All hooks being executed during this render (no matter how deeply nested in custom-hooks they are) ultimately belong to this component.
So, the useReducer is associated with the component within which you called useSelector.
The dispatch function of useReducer triggers a re-render of this component (React either calls the render() method of a class-component or executes the function body of a functional component).
If you are curious how react-redux determines when it should force this re-render (by utilizing useReducer), take another look at the source code of useSelector.
Redux uses the subscriber-pattern to get notified of updates to the state. If the root-state of redux is updated the following things happen:
useSelector hooks in your application re-run their selector function
This re-selected state is compared to the previously selected state (by default via === comparison). The second argument to useSelector can be a comparison function to change this behavior
If the re-selected state differs from the previously selected state, a re-render is triggered via the useReducer hook.
The subscriber pattern is very react-like but potentially helps save many re-renders. Calling several useSelector hooks is cheap when compared with re-renders.
First of all, I want to mention that you don't need to do a force update when you use useSelector hook. Rerender will happen automatically whenever the selected state value will be updated.
But if you need to force update the functional component you can use this approach.
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => ++value); // update the state to force render
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
I highly recommend avoiding the use of this hack, in 99% of issues you can resolve them without force update. But in any case, it's good to know that there is such a possibility in the functional component exists too.
Maybe something like this could help you:
In a Class Component you could pass a property like the one below...
<Element onSomethingHappen={
()=>{
if(shouldComponentUpdate())
this.forceUpdate();
}}/>
In the function component you can call the updater like this one:
function FunctionComponent(props){
//When you need it you can update like this one...
props.onSomethingHappen();
// Here you are ;) let me know if this helps you
}
Continuing on other answers, to keep your code clean you can create a dummy state and then set it in your own forceUpdate function:
const [helper, setHelper] = useState(false);
function forceUpdate(){
setHelper(!helper);
}
Now you can just call forceUpdate() in the rest of your code:
<div onClick={() => forceUpdate()} />

useContext value changes on rerenders

So I have a component that looks something like
const App = () => {
const someContextValue = useSomeContext(); //custom hook that calls useContext
useEffect(() => {
someContextValue()
}, [someContextValue]);
return <div />
}
Whenever the component rerenders, the useEffect is triggered even though someContextValue hasn't really changed.
I got around this by using useMemo like
const someContextValue = useMemo(useSomeContext, [useSomeContext])
Now someContextValue doesn't change on rerenders. But I feel like this isn't quite right. What would be the right way to do this?
If you're returning an object {} or array [] from the context, then the context value HAS changed.
The someContextValue variable is defined inside the component.
The component is a function, and when a function runs, the values that are defined inside it get defined. If for example, your context returns an object containing state values, then that object is a different instance to the one from the previous render, and your useEffect runs because of that.
useEffect will compare reference-types by their instance and not the values inside them.
When you call useSomeContext(), you're creating a new object, and this happens every time the component renders.
This is not the same as a state value from useState, where the state value does not get redefined. The useState hook maintains the same instance of the value, therefore it is not recreated every time, and the useEffect sees that the state value is the same instance.
This is why, when using context, you should destructure the context object and refererence the values inside the object, which are either state values passed from a useState hook inside the context, or a value defined inside the context that does not get redefined when your consuming component re-renders (because the context does not re-render in that case):
const { someStateValue } = useSomeContext()
useEffect(() => {
someStateValue()
}, [someStateValue]);

Passed-in prop used to initialize a useState hook gets updated when calling the hook's updater method

Given the following code:
const MyComponent = ({ myObj }) => {
const [myStatefulObj, setMyStatefulObj] = useState(myObj.subObj)
const handleChange = () => {
const updatedObj = { newProperty: Math.random() }
setMyStatefulObj(updatedObj) // myObj is updated too!
}
return <div />
}
I am trying to initialize a new local prop with useState, and hydrating its initial value with a prop that is passed in from a parent component.
When I update my prop using setMyStatefulObj everything works fine, except that it's also updating the prop that's passed into the component.
This is an undesired side effect and one I haven't come across before.
The prop that is getting passed in is from a parent that creates it with a selector from react-redux's useSelector hook.
There are no actions being dispatched or anything, but simply logging the value of the myObj prop shows that it is indeed being updated every time the local setMyStatefulObj method gets called.
The effect you are describing doesn't make sense and I was not able to reproduce it. Maybe you could create a snippet and share it.
I don't think that was caused by the hooks function.
I have created a simple example in CodeSandBox as you said, and it obviously not reproduced. You can edit this sandbox and try to simulate your situation maybe you can find the root cause.
But since you are using redux, I would suggest you to use useSelector to select the state you need inside hooks function, then you would not have this kind of concern, and it will have better performance(if you pass the prop, every time myObj changes the hooks component will re-render).

`componentWillReceiveProps` explanation

I recently wanted to upgrade my knowledge of React, so I started from the component lifecycle methods. The first thing that got me curious, is this componentWillReceiveProps. So, the docs are saying that it's fired when component is receiving new (not necessarily updated) props. Inside that method we can compare them and save into the state if needed.
My question is: Why do we need that method, if changes in props of that component (inside parent render) will trigger the re-render of this child component?
One common use case are state (this.state) updates that may be necessary in response to the updated props.
Since you should not try to update the component's state via this.setState() in the render function, this needs to happen in componentWillReceiveProps.
Additionally, if some prop is used as a parameter to some fetch function you should watch this prop in componentWillReceiveProps to re-fetch data using the new parameter.
Usually componentDidMount is used as a place where you trigger a method to fetch some data. But if your container, for example, UserData is not unmounted and you change userId prop, the container needs to fetch data of a user for corresponding userId.
class UserData extends Component {
componentDidMount() {
this.props.getUser(this.props.userId);
}
componentWillReceiveProps(nextProps) {
if (this.props.userId !== nextProps.userid) {
this.props.getUser(nextProps.userId);
}
}
render() {
if (this.props.loading) {
return <div>Loading...</div>
}
return <div>{this.user.firstName}</div>
}
}
It is not a full working example. Let's imagine that getUser dispatch Redux action and Redux assign to the component user, loading and getUser props.
It 'serves' as an opportunity to react to the incoming props to set the state of your application before render. If your call setState after render you will re-render infinitely and that's why you're not allowed to do that, so you can use componentWillReceiveProps instead.
But... you are beyond CORRECT in your confusion, so correct in fact that they are deprecating it and other Will-lifecycle hooks Discussion Deprecation.
There are other ways to accomplish what you want to do without most of those Will-lifecycle methods, one way being don't call setState after render, just use the new incoming props directly in render (or wherever) to create the stateful value you need and then just use the new value directly, you can then later set state to keep a reference for the next iteration ex: this.state.someState = someValue, this will accomplish everything and not re-render the component in an infinite loop.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.
Look at this article
the componentWillReceiveProps will always receive as param "NxtProps", componentWillReceiveProps is called after render().
some people use this method use this to compare nxtProps and this.props to check, if something should happen before the component call render, and to some validations.
check the react's documentation to know more about react lifecycle!
hope this could help you!
changes in props of that component (inside parent render) will trigger the re-render of this child component
You are absolutely right. You only need to use this method if you need to react to those changes. For instance, you might have a piece of state in a child component that is calculated using multiple props.
Small Example:
class Test extends Component {
state = {
modified: "blank"
};
componentDidMount(){
this.setState({
modified: `${this.props.myProp} isModified`
});
}
componentWillReceiveProps(nextProps) {
this.setState({
modified: `${nextProps.myProp} isModified`
});
}
render() {
return <div className="displayed">{this.state.modified}</div>
}
}
In this example, componentDidMount sets the state using this.props. When this component receives new props, without componentWillReceiveProps, this.state.modified would never be updated again.
Of course, you could just do {this.props.myProp + "IsModified"} in the render method, but componentWillReceiveProps is useful when you need to update this.state on prop changes.

Resources