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
Related
[This is the Codesandbox with all of the code that exemplifies the issue I'm having. Sorry, SO doesn't allow for proper React code with TSX, etc.]
I have some tabs, naturally, whenever I click on each of them, my state changes (namely selectedTab) and the body that's being rendered. Each tab item has a body that goes with it, the first tab, in my case, has a component that, if you click, a count is increased. Well, whenever the tab changes, that count gets reset. In fact, it's a full-rerender all around.
The problem with doing a full re-render is that - not only is it inefficient, given how the user's gonna use it, but it also adds fuel to the fire: what if the user inputs something on a tab, then switches to another tab to do something else and then back to the original one? He's just lost what he typed in.
Naturally, this can be solved if we render all of the tabs' content upfront and we just play with display: none, and it'd be a good solution, but I'm also trying to learn.
I understand how React re-renders things and why this happens, but is there a way to fix it?
In my case, if you look at useTabs, the content variable simply looks at the corresponding tab's component key and takes outputs that. It's there that, I think, I need to memoize things.
Uhm, I guess you can't prevent calling your custom hook, this will lead to invariant violation error.
These hooks in the component will be executed on each render -> thus 'resets' your tabs.
You should rely on useEffect dependencies argument to run conditional code to see if values needs to be changed.
as long as your in the same app you could use contextApi,react context docs otherwise you can use localstorage, localstorage tutorial
or just create this state in the parent component and pass it down as props?
I'm new to react and here is my question , is there's a way to re-render a component from another component?
I'm using Redux and some of the global state is effecting component B .
But in my example component B is not re-rendered after some Redux state is changing from component C .
component C and B are not father/child to each other ,
is there a simple way to do it?
thanks
Every component will re-render if any of the states connected to it change. So in order to cause a re-render, simply include that state in both components connect function.
There is an option on how to re-render component inside component in React 16.x using Fragments. Documentation on this can be found here.
Short explanation: Your DOM will not be polluted with extra nodes, but will allow your app to use less memory which is always great. More in-depth explanation available here.
A frequent problem in Redux that causes this symptom is not using the spread operator to update the state object in your reducer. Try and return something like { ...state, newValue: 'food' }
You can use useNavigate and navigate to the same url you are on. For example, as the last line of your function, you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error
I am storing some state in a context using useState, and using a useEffect in there to update history (via react-router-dom's useHistory).
After a user clicks a Cancel button, I clear these state values, and the useEffect then runs history.replace({search: ""}), emptying the query string.
However, if anything causes a re-render following that, the query string is restored with its previous value. This is despite the state in context being nulled out.
This happens without the history.push I use to update the query string in the first place, confirmed with debuggers, so it's happening somewhere outside of my code. It seems that, somehow, the router is jumping back in history one step on the render following my emptying the query string.
I'll try to write this in steps:
user interacts with a form
useState values in a context are bound to the inputs and update accordingly
useEffect watching those values uses history.push to update the query string (say /home?field1=foo)
user hits 'Cancel', which sets all of those state values to null
useEffect watching those values uses history.replace with {search: ""} (or {}), removing the query string from the URL (path is now /home)
user causes a re-render on an ancestor, and previous query string is restored to URL - /home?field1=foo
I'm at a loss, any help is much appreciated!
I haven't drilled down to the exact reason, but the problem here was that I was using useLocation and useHistory in multiple components, rendered at the same time. When search was updated, only the instance of useHistory that I called push or replace on would have the new state. The other instance would be left with the old state, and some other logic was causing it to use that false state to update the querystring again.
tl;dr if using useLocation and useHistory in multiple components, be careful as they are not aware of one another and not necessarily up to date as one would think.
I am new on react. I am working on react application with redux. I have a form (I am using redux-form) by which I can save data or edit data.
my problem is , In edit mode I populate data using componentWillReceiveProps. and populated perfectly, now when I try to clear any field on form its again fill.
componentWillReceiveProps(nextProps){
this.props.dispatch(initialize('NewProject', nextProps.project.project[0]));
}
I would be grateful for any help.
Is there a reason you're not dispatching this action somewhere else, like in componentDidMount? I can't say without seeing more code, but it's possible that whenever you edit your form, React again calls componentWillReceiveProps and overwrites whatever you did with the behavior you've given your component.
Per the React documentation:
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
It may be a good idea for you to move your dispatch to a more predictable event if possible, like componentDidMount. Typically, your render method should be capable of handling different cases if a component has multiple possible states. You could also create an edit and save version of your component, and render one or the other based on the props you receive. The best way to "populate" data, as you put it, is to define propTypes for your component, and then use your props to insert that data into elements in the render method.
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