Preserve react component state on unmount - reactjs

React newbie here: I have noticed that the component state gets cleared on unmount. is there a way to prevent it? is it using redux?

As you say, when a component is unmount you can not access to the state. And thats the way it is because the lifecicle of the component.
What you can do is try to save the state of the component that was instantiated while it is mounted and every time it is updated.
You can use the storage and use simple javascript.
You can have the state in the parent or another ancester instance.
You can have the state in the redux store. Note that your component will receive the vars as props, so it wont be the state properly said.
You can use redux in order to manage the state and the states values through time. I recommend you the redux-devtools-extension and also this article about it.

You have a plethora of options. You can either use a state management tool, like redux, context API and so on, or you can pass-in a callback to your parent component and trigger it on childComponentWillUnmount like this:
ParentComponent.jsx:
childComponentWillUnmount = (data) => {
console.log('my data', data);
}
render(){
return <div>
<Child componentUnmountCallback={this.childComponentWillUnmount()}/>
<div>
}
<div>
Child.jsx
...
componentWillUnmount() {
this.props.childComponentWillUnmount(this.state);
}
...

Related

Update redux-store before ReactDOM.render(...)

Is there a way to update the Redux store before the ReactDOM.render(...) is runned?
Something like store.dispatch(...).then(() => ReactDOM.render(...))
Or a way to just replace the entire "state" of the store?
I am making an API with React components that is used inside another application with another framework, Dojo. Because of this we are using ReactDOM.render(...) and ReactDOM.unmountCompoentAtNode(...) when the component should appear/disappear.
So in the API, I am reusing the redux store, since it is not deleted from the memory/RAM even though a React component is unmounted via ReactDOM.unmountComponentAtNode(...). If I did not reuse the store, but made a new one every time the React component should render, the actions where "fetched by the Redux store" more and more times. Because of this I would like to ensure that I get to refresh the Redux store before I mount the component again.
Given that the rendered state should depend on the redux store, have you seen redux-thunk ?
One way is to have a show a spinner when the API endpoint is being reached out to.
After setting the state from the API response, go ahead and show the real component.
You are approaching the solution in the wrong way. For this use case, we have life cycle methods in components
ReactDOM.render(<App />, document.getElementById('root'))
In this code I am rendering App component, to solve your problem I'll use componentDidMount life cycle method of App and from there I'll dispatch the API call.
class App {
componentDidMount() {
dispatch(MY_ACTION)
}
render() {
return (<h1>dummy</h1>)
}
}
For hooks based functional component, you need to use useEfect
const App = () => {
useEffect(() => {
dispatch(MY_ACTION)
}, [])
return (<h1>dummy</h1>)
}

How to handle render method until redux has returned results?

I am creating a component that fetches some apis to populate result.There are some transformations needed on those api results before rendering. I've used componentWillMount method to set redux state. I need to handle render method until redux those results to props. This takes some time. Since data is not available, render method will fail at that moment. I've tried setting default state but that doesn't seem to work since redux state will be mapped to props with mapStateToProps method. What is the correct and efficient approach to handle such case??
Thanks in advance
Normally you will render a different thing (or you won't render at all) when your data is not present.
{this.props.unreadMessages.length > 0 &&
<h2>
You have {this.props.unreadMessages.length} unread messages.
</h2>
}
or:
if (this.props.unreadMessages.length > 0) {
return <Main unreadMessages={this.props.unreadMessages} />;
} else {
return <Loading />
}
You can also have your default initial state and render based on that when props does not have data. maybe with something like:
<h2>
Hello, {this.props.username || this.state.username}.
</h2>
Also, the recommended lifecycle hook for Ajax call is componentDidMount.
If your data is not ready, you can use conditional rendering. Add additional indicator about your data, Like this
render() {
if (!this.props.data) {
return null;
}
return <YourComponent />;
}
And you'd better to fetch data in componentDidMount lifecycle.
I would check for the props in the render method with a ternary expression as a sort of guard clause. Something like this, for example:
render(){
return (<React.Fragment>
{ this.props.user ? <IntendedComponent ...this.props.user /> : <Loading user={defaultUser}/> }
<React.Fragment/>);
}
If the user prop has not been populated yet, it should be empty and display an alternate component (like the loading component in the example) or the intended component with default, hardcoded values. Once the data is in place, you can pass the data to a component as you see fit.
First of all componentWillMount is not a good method to call apis,
as per react documentation:
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
This method is a good place to set up any subscriptions. If you do
that, don’t forget to unsubscribe in componentWillUnmount().
Here is the link: https://reactjs.org/docs/react-component.html#componentdidmount
Here is what you can do till data is loaded:
get one flag into redux state called dataLoaded:false
Once the data is loaded you performed an operations you can easily change it.
And in render()
if(this.props.dataLoaded){
<loadingComponent></loadingComponent>
} else {
//operation is performed
}
Since you're using Redux, you can set the default Redux state in Reducers using ES6 default parameters. Something like this:
const apiStatus = (state='fetching', action) => {
// handle dispatch action
}
And in React, you can use Conditional Rendering to render a loading icon, for instance, while state is fetching. Then when state is finished, take the result and render the real elements.
To work with Async in Redux, see this link:
Async Action

`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.

How does a redux connected component know when to re-render?

I'm probably missing something very obvious and would like to clear myself.
Here's my understanding.
In a naive react component, we have states & props. Updating state with setState re-renders the entire component. props are mostly read only and updating them doesn't make sense.
In a react component that subscribes to a redux store, via something like store.subscribe(render), it obviously re-renders for every time store is updated.
react-redux has a helper connect() that injects part of the state tree (that is of interest to the component) and actionCreators as props to the component, usually via something like
const TodoListComponent = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
But with the understanding that a setState is essential for the TodoListComponent to react to redux state tree change(re-render), I can't find any state or setState related code in the TodoList component file. It reads something like this:
const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
Can someone point me in the right direction as to what I am missing?
P.S I'm following the todo list example bundled with the redux package.
The connect function generates a wrapper component that subscribes to the store. When an action is dispatched, the wrapper component's callback is notified. It then runs your mapState function, and shallow-compares the result object from this time vs the result object from last time (so if you were to rewrite a redux store field with its same value, it would not trigger a re-render). If the results are different, then it passes the results to your "real" component" as props.
Dan Abramov wrote a great simplified version of connect at (connect.js) that illustrates the basic idea, although it doesn't show any of the optimization work. I also have links to a number of articles on Redux performance that discuss some related ideas.
update
React-Redux v6.0.0 made some major internal changes to how connected components receive their data from the store.
As part of that, I wrote a post that explains how the connect API and its internals work, and how they've changed over time:
Idiomatic Redux: The History and Implementation of React-Redux
My answer is a little out of left field. It sheds light on a problem that led me to this post. In my case it seemed the app was Not re-rendering, even though it received new props.
React devs had an answer to this often asked question something to the tune that if the (store) was mutated, 99% of the time that's the reason react won't re-render.
Yet nothing about the other 1%. Mutation was not the case here.
TLDR;
componentWillReceiveProps is how the state can be kept synced with the new props.
Edge Case: Once state updates, then the app does re-render !
It turn out that if your app is using only state to display its elements, props can update, but state won't, so no re-render.
I had state that was dependent on props received from redux store. The data I needed wasn't in the store yet, so I fetched it from componentDidMount, as is proper. I got the props back, when my reducer updated store, because my component is connected via mapStateToProps. But the page didn't render, and state was still full of empty strings.
An example of this is say a user loaded an "edit post" page from a saved url. You have access to the postId from the url, but the info isn't in store yet, so you fetch it. The items on your page are controlled components - so all the data you're displaying is in state.
Using redux, the data was fetched, store was updated, and the component is connected, but the app didn't reflect the changes. On closer look, props were received, but app didn't update. state didn't update.
Well, props will update and propagate, but state won't.
You need to specifically tell state to update.
You can't do this in render(), and componentDidMount already finished it's cycles.
componentWillReceiveProps is where you update state properties that depend on a changed prop value.
Example Usage:
componentWillReceiveProps(nextProps){
if (this.props.post.category !== nextProps.post.category){
this.setState({
title: nextProps.post.title,
body: nextProps.post.body,
category: nextProps.post.category,
})
}
}
I must give a shout out to this article that enlightened me on the solution that dozens of other posts, blogs, and repos failed to mention. Anyone else who has had trouble finding an answer to this evidently obscure problem, Here it is:
ReactJs component lifecycle methods — A deep dive
componentWillReceiveProps is where you'll update state to keep in sync with props updates.
Once state updates, then fields depending on state do re-render !
This answer is a summary of Brian Vaughn's article entitled You Probably Don't Need Derived State (June 07, 2018).
Deriving state from props is an anti-pattern in all its forms. Including using the older componentWillReceiveProps and the newer getDerivedStateFromProps.
Instead of deriving state from props, consider the following solutions.
Two best practice recommendations
Recommendation 1. Fully controlled component
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
Recommendation 2. Fully uncontrolled component with a key
// parent class
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
// child instance
<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>
Two alternatives if, for whatever reason, the recommendations don't work for your situation.
Alternative 1: Reset uncontrolled component with an ID prop
class EmailInput extends Component {
state = {
email: this.props.defaultEmail,
prevPropsUserID: this.props.userID
};
static getDerivedStateFromProps(props, state) {
// Any time the current user changes,
// Reset any parts of state that are tied to that user.
// In this simple example, that's just the email.
if (props.userID !== state.prevPropsUserID) {
return {
prevPropsUserID: props.userID,
email: props.defaultEmail
};
}
return null;
}
// ...
}
Alternative 2: Reset uncontrolled component with an instance method
class EmailInput extends Component {
state = {
email: this.props.defaultEmail
};
resetEmailForNewUser(newEmail) {
this.setState({ email: newEmail });
}
// ...
}
As I know only thing redux does, on change of store's state is calling componentWillRecieveProps if your component was dependent on mutated state and then you should force your component to update
it is like this
1-store State change-2-call(componentWillRecieveProps(()=>{3-component state change}))

React reusable stateful component

Let's say I created a component which can be turned on/off based on state.
var onOff = React.createElement(<OnOff />, mountElement);
onOff.setState({ on: false });
Later I'm creating a new component called Parent, which will use OnOff inside it.
render() { return <div><OnOff /></div> }
Now how can I change the OnOff state? There is no way I can call setState on it. And I should not according to React doc. So I have to add initial state to OnOff's props:
constructor(props) {
super(props);
this.state = { on: props.initialOn };
}
then in Parent's render method, set the initialOn prop with its state:
render() { return <div><OnOff initialOn={false} /></div> }
But it's still not working, because whenever I change Parent's state, the OnOff component inside it is not re-created with new initial state. Instead, it is only re-rendered with old state. I have a CodePen to prove it: http://codepen.io/anon/pen/QjMwjO?editors=101
You can update the state of the OnOff component by declaring the update also inside a componentWillReceiveProps function, something like:
componentWillReceiveProps:
function(nextProps) {
this.setState({
on : nextProps.initialOn
});
}
This allows you to update state, when new props arrive. And it is valid react.
You should however consider if you need state in OnOff at all: if the only initial setting and all updates ONLY come from its parent component, then a stateless component would be better.
One of the important things to understand when "Thinking in React" is to figure out which component does State belong to.
Read this in React docs
What Components Should Have State?
Most of your components should simply take some data from props and render it. However, sometimes you
need to respond to user input, a server request or the passage of
time. For this you use state.
Try to keep as many of your components as possible stateless. By doing
this you'll isolate the state to its most logical place and minimize
redundancy, making it easier to reason about your application.
A common pattern is to create several stateless components that just
render data, and have a stateful component above them in the hierarchy
that passes its state to its children via props. The stateful
component encapsulates all of the interaction logic, while the
stateless components take care of rendering data in a declarative way.
Thus, your OnOff should not have state but use properties passed down from the parent instead. I have illustrated this at http://codepen.io/anon/pen/gaxbGm?editors=101
render() {
writeLog("OnOff render called!")
writeLog("Child: " + this.props.initialOn)
return <span>{this.props.initialOn ? "On" : "Off"}</span>;
}
I would also recommend reading "Thinking in React" to get further clarity.

Resources