React component state becomes invalid after change in parent component - reactjs

function UserCard({ user }) {
let defaultUserName = user.nicknames[0];
let [selectedNickname, setSelectedNickname] = React.useState(defaultUserName);
// The selected nickname becomes invalid when the user is changed!!!
const selectedNicknameIsValid = user.nicknames.includes(selectedNickname);
return (<div>
<UserCardWithNickName user={user} selectedNickname={selectedNickname} />
<SelectNickName user={user} setSelectedNickname={setSelectedNickname} />
</div>);
In the above snippet the component's state holds the selected nickname from the list of user nicknames. But when the user changes in the parent component this component is re-rendered with the same state. So the nickname in the state is for the wrong user.
What is the preferred way to handle this? A lot of google searching couldn't find much discussion.
Am I doing this in some fundamental non-react way?
Is the preferred solution to use useEffect to fix the state when it becomes invalid as discussed here React.useState does not reload state from props?
Working example on codesandbox

Yeah, state changes in the parent component will usually re-render the child component, and hence reset the state of the child component. I'd suggest moving states that you want preserved up into the parent tree.
State management is an important concept in React. As your app grows, you'll realize doing this will add a lot of bloat and repetitive code. Learning about state management will prove very useful for you!

Yes. The link you have posted is one way to do it. The question you have to look for is if your child component is managing the value of selectedNickname in anyway. That is the only reason you would want to keep it as a seperate state in the child component. If all it does is read the props from the parent, simply use the props and do not maintain an internal state at all.

Months later I finally found a robust discussion of this problem in the react docs.
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
The problem I was trying to describe is called derived state.
I eventually solved it by adding a key when using the component. When a key changes, React will create a new component instance rather than update the current one. This is one of the recommended approaches in the docs.
<UserCard user={user} key={user.id} />

Related

Ensuring Child Component Reloads When Parent Component Changes

I am working on creating an updated implementation of an existing react native app and am running into a situation where a child component doesn't re-load when the parent component changes.
My situation is this: I have a parent component for a particular record. Connected to this is a VisitTimer child component that keeps track of duration for a that record's session. What I'm noticing is that, when I load a record into the parent component for the first time, the child component <VisitTimer> does fire with componentDidMount(). However, if exit that screen, and load a DIFFERENT record, then the child <VisitTimer> component does not mount - i.e. componentDidMount in the VisitTimer child component does not fire.
What is the likely issue here? And how can I ensure that a child component always re-mounts when the parent component changes?
Here is the child component within the parent:
<VisitTimer
started={this.props?.session?.dates?.started?.value}
stopped={this.props?.session?.dates?.completed?.value}
durationOverride={this.props?.session?.duration?.total?.value}
maxDuration={maxVisitTime(this.props?.session?.type?.value)}
visit={this.props?.session}
ref={(ref) => (this.visitTimer = ref)}
stopTimeOffset={this.props.visit?.duration?.stopOffset?.value}
editable={statusCodeToEditableLevel(this.props.session?.status?.value) === 2}
onChange={this.onSessionTimerChange}
/>
See my comments but a way to force this to be recreated is to give it a unique key based on the record. In react, key is special, and if it changes the whole component will remount. You could set the key to a unique id from the record:
<VisitTimer
key={this.props?.session?.guid}
started={this.props?.session?.dates?.started?.value}
stopped={this.props?.session?.dates?.completed?.value}
durationOverride={this.props?.session?.duration?.total?.value}
maxDuration={maxVisitTime(this.props?.session?.type?.value)}
visit={this.props?.session}
ref={(ref) => (this.visitTimer = ref)}
stopTimeOffset={this.props.visit?.duration?.stopOffset?.value}
editable={statusCodeToEditableLevel(this.props.session?.status?.value) === 2}
onChange={this.onSessionTimerChange}
/>
I made an assumption the unique id is on session.guid.
By the way, this is generally a good idea over implementing componentDidUpdate in your situation. People often go that route and spend loads of time implementing reset behavior to set the state back to its original values, which is a source of bugs. Changing key guarantees you have a fresh start. The only time you shouldn't do this to achieve a "reset" on a component is if remounting it and its children are expensive.
Using a key will force a re-render but that's not the primary purpose of key. React docs do talk about using the key like this but those are for specific use cases. In this case I would encourage to ask ourselves:
Do we really need to re-mount the child component or do we just want it to re-render
Regardless of the answer to the above question. Why do we need the child component to re-render. Is it because it needs to show an updated data?
If yes, then the child component should receive a prop from the parent or something from a global state which changes (in your case probably the record itself). That kind of prop change/state change will automatically trigger a re-render
I hope this is helpful but the community can be even more helpful if you can share more details of your problem like - What does the parent component do, what does the child render, why do we want the child to re-render, etc

Why do we need a `props` parameter in React?

I still don't get why we need a prop(s) in react, seriously. Why can't we just declare everything we need as an argument or parameter in the child component, then declare it, why do we have to declare it in a parent element then pass the props to the child component then catch it. I don't understand why. It seems a bit confusing, I'm yet to see the why in it
Props are useful in the case that you have a controller in the parent component and you want to pass the value of that controller to the child to make a certain action. The replacement for props would be to store everything globally in redux or mobx, but that needs much work. for example
const ParentComponent = () =>{
const [flag, setFlag] = useState(false)
return(
<div>
<button onClick={()=>setFlag(!flag)}>click me!</button>
<ChildComponent flagValue={flag}/>
</div>
)
}
as in the example for some reason the button that changes the flag is in the parent and you need to use that value in the ChildComponent. So here you benefit a lot from using props.
Also in writing a cleaner code and drier one so as not to repeat the same values in different react components
You might not be familiar with React if you ask such questions (no anger at all). It's one of the main concepts of the React library.
You can easily split a huge component into smaller pieces. But then, you need to provide the same data here and there. To prevent repeating yourself (DRY - don't repeat yourself), you can share the prop with many child components.
If you are interested in React - check the documentation.
It's one of the prettiest documentation I've ever read.
prop changes trigger UI re-render for the child component.
That's one of the props of using props.

React Routing causes inputs to lose focus

I have an app with a component that has inputs in it. When the user changes the inputs, I want to update the query string in the URL to reflect the new state. I achieve this by detecting a difference between the current location.search values and the query string that my state would generate. If there's a difference, I return a <Redirect> component with the query args reflecting the new state.
The problem is that after every such redirect, the component re-renders and my inputs are replaced by new inputs, meaning that the input I just changed no longer exists, so it doesn't have focus. If it's a text input, then it loses focus after every keystroke.
How I dealt with it was to remember the ID of the element that had focus when the state changed and then set focus to the new element based on document.getElementById once the component is done re-rendering.
I've reduced the issue down to a minimal app that is at https://codesandbox.io/s/github/TrevorMills/react-routing-conundrum. The problem manifests on both functional components and React.Component classes.
My mechanism seems hacky to me. My question to the React experts out there is, have I missed something? Is there a better way to get the state into the location query parms than <Redirect>? I tried adding key to my inputs but it didn't make a difference. If my mechanism seems legit, is there a way to apply it to the app on whole in a higher order component?
Thanks in advance.
Since you were using redirect, on every change of input value, the component rendering toggles between the returned value and the redirect causing all components to remount instead of re-render. This causes the input field to lose focus.
Avoid using <Redirect /> and use history.replace after updating the state.
Instead of doing history.replace in onChangeText function, you can write a useEffect, like this.
useEffect(() => {
props.history.replace(props.location.pathname + "?" + qs.stringify(state));
}, [state]);
Updated working example is here:
https://codesandbox.io/s/cocky-swirles-kcqwo?file=/src/App.js
I've edited your functional component version of the code, however the changes I made should be applicable to both.
The main thing I did is I removed the state duplication. Instead of duplicating the state from the route to the component, I just made the component use the query params directly as state. And then I made the updateState function update the query params using props.history.replace which is how React Router allows imperative navigation.
Hope this helps!
https://codesandbox.io/s/great-williamson-0pxyb

state vs props for scenario with separate view and data model

I'm building an application where I would like to provide separate views for same data.
In my current implementation, data is obtained by web service call and persisted in state of App component in App.js. App component hosts (renders) another component called StackEditor, which acts as a view for this.state.components in App component.
UI elements rendered by StackEditor can be moved around, and to synchronize state of App I do it as below:
<StackEditor
components={this.state.components}
onLocationChanged={this.handleLocationChanged} />
In handleLocationChanged I update the state:
handleLocationChanged(e, data) {
this.setState(prevState => {
// event data copied to state here
return {components: prevState.components};
});
}
As state is now updated, this forces StackEditor to be rendered again, as its property components is bound to state as components={this.state.components} (see in the code snippet above).
This all works, but now I started questioning if I'm doing it right.
Q1: Should I be using state instead of props?
It seems that location of component is mutated in principle, although from StackEditor point of view, it is immutable (I can decide that change is invalid and not to update the state in event listener).
Q2: Is it possible to share part of the state between 2 components in React?
If I somehow convert StackEditor from getting components from state instead of props, will I get notification on state changed by child component (StackEditor) in my parent component (App)?
Q3: Also, are props more convenient to use than state in general?
When I created another component following HOC guidelines (https://reactjs.org/docs/higher-order-components.html) I discovered that props are easily forwarded to "wrapped" component, but not state. If I provide a function to call back via property (as I did above), "wrapped" component can easily call it, without even noticing that it's "wrapped". I don't see how I can easily notify "wrapped" component about state change in "wrapper", without writing some extra code.
If you imagine your application to be a tree of components in a well designed app it's usually like this:
the leafs are stateless components . They decide how data is rendered.
the nodes are stateful components. They decide which components and data to render.
Q1: Should I be using state instead of props?
It depends on which category of components you have (node or leaf).
Q2: Is it possible to share part of the state between 2 components in
React?
If you feel that your app has a lot of state that mutates and needs to be used by several components spread over your tree you usually start to introduce an external state management library (e.g. redux). Components can subscribe to your store and become stateless as your store now handles the state.
Q3: Also, are props more convenient to use than state in general?
They solve different problems so you can't really say that. A stateless component is usually easier to understand but has no capabilities to control anything.
Also read Identify where your state should live and When to use redux.
All that is only a rule of thumb. A lot of the time you will have components that have both state and props because they control parts of your app but delegate other parts to their children.
This all works, but now I started questioning if I'm doing it right.
As far as I can see from the code you provided this looks pretty much as it has to.

React: Parent component re-renders all children, even those that haven't changed on state change

I haven't been able to find a clear answer to this, hope this isn't repetitive.
I am using React + Redux for a simple chat app. The app is comprised of an InputBar, MessageList, and Container component. The Container (as you might imagine) wraps the other two components and is connected to the store. The state of my messages, as well as current message (the message the user is currently typing) is held in the Redux store. Simplified structure:
class ContainerComponent extends Component {
...
render() {
return (
<div id="message-container">
<MessageList
messages={this.props.messages}
/>
<InputBar
currentMessage={this.props.currentMessage}
updateMessage={this.props.updateMessage}
onSubmit={this.props.addMessage}
/>
</div>
);
}
}
The issue I'm having occurs when updating the current message. Updating the current message triggers an action that updates the store, which updates the props passing through container and back to the InputBar component.
This works, however a side effect is that my MessageList component is getting re-rendered every time this happens. MessageList does not receive the current message and doesn't have any reason to update. This is a big issue because once the MessageList becomes big, the app becomes noticeably slower every time current message updates.
I've tried setting and updating the current message state directly within the InputBar component (so completely ignoring the Redux architecture) and that "fixes" the problem, however I would like to stick with Redux design pattern if possible.
My questions are:
If a parent component is updated, does React always update all the direct children within that component?
What is the right approach here?
If a parent component is updated, does React always update all the direct children within that component?
No. React will only re-render a component if shouldComponentUpdate() returns true. By default, that method always returns true to avoid any subtle bugs for newcomers (and as William B pointed out, the DOM won't actually update unless something changed, lowering the impact).
To prevent your sub-component from re-rendering unnecessarily, you need to implement the shouldComponentUpdate method in such a way that it only returns true when the data has actually changed. If this.props.messages is always the same array, it could be as simple as this:
shouldComponentUpdate(nextProps) {
return (this.props.messages !== nextProps.messages);
}
You may also want to do some sort of deep comparison or comparison of the message IDs or something, it depends on your requirements.
EDIT: After a few years many people are using functional components. If that's the case for you then you'll want to check out React.memo. By default functional components will re-render every time just like the default behavior of class components. To modify that behavior you can use React.memo() and optionally provide an areEqual() function.
If a parent component is updated, does React always update all the direct children within that component?
-> Yes , by default if parent changes all its direct children are re-rendered but that re-render doesn't necessarily changes the actual DOM , thats how React works , only visible changes are updated to real DOM.
What is the right approach here?
-> To prevent even re-rendering of virtual DOM so to boost your performance further you can follow any of the following techniques:
Apply ShouldComponentUpdate Lifecycle method - This is applied only if your child component is class based , you need to check the current props value with the prev props value ,and if they are true simply return false.
Use Pure Component -> This is just a shorter version to above method , again works with class based components
Use React memo -> this is the best way to prevent Rerendering even if you have functional components ,you simply need to wrap your components export with React.memo like : export default React.memo(MessageList)
Hope that helps!
If parent component props have changed it will re-render all of its children which are made using React.Component statement.
Try making your <MessageList> component a React.PureComponent to evade this.
According to React docs: In the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component. check this link for more info
Hope this helps anyone who is looking for the right way to fix this.
If you're using map to render child components and using a unique key on them (something like uuid()), maybe switch back to using the i from the map as key. It might solve the re-rendering issue.
Not sure about this approach, but sometimes it fixes the issue

Resources