Why I cannot reset state of react component? - reactjs

I try to reset react component state by using preserved initial variable but this is not worked. What is the problem here?
First have I created a base state variable "baseState" then I will used to reset react state. but it not worked,
Here I create a sample program below,
class Product extends Component {
constructor(props){
super(props);
this.baseState = {isLoggedIn: true, loader:false, inputs : { name :null, category_id:null }};
this.state=this.baseState;
}
render {
(<Form>{/* form inputs */}</Form>)
}
// this is a onChange select event for update input values.
selectInputChange(e){
let stateInputs=this.state.inputs;
stateInputs[e.target.name]=e.target.value;
let inputErrors=this.state.errors;
let error=this.validateFormInput(e.target.name,e.target.value);
inputErrors[e.target.name]=error?error:null;
this.setState( prev =>{
return{
...prev, inputs: stateInputs, errors:inputErrors
}
});
console.log(this.baseState); // here value is updated.
}
handleFormSubmit(){
// after insert record through axios. then i try to reset like
this.setState(this.baseState);
// but state is not updated here.
}
}
export default withRouter(Product);
I am beginner to reactjs so help me to solve the problem, thank you!

You're making this.state point to the same object as this.baseState, so a change to one is a change the other because there is no difference: they're literally the same object. As you're discovering, this.setState(this.baseState) will do nothing, because you're updating the current state with itself, and is the same as saying this.setState(this.state).
If you want a state that you can truly reset to, use a function that generates a new object each time you call it:
class Product extends Component {
constructor(props){
super(props);
this.state = this.getInitialState();
}
getInitialState() {
return {
isLoggedIn: true,
loader: false,
inputs: {
name: null,
category_id: null
}
};
}
...
And then whenever you want to reset your component, you can call this.setState(this.getInitialState()), which will reset all the properties that are relevant to your initial state (while leaving anything else entirely untouched - you won't be removing any state values not covered by the initial state object).

Related

React Native state variables not getting updated from props

I'm passing 3 props - numLikes, id and userLiked to my class and I want to set my state variables initially before any render occurs.
Ideally, the values of state variables should be equal to their props counterparts but this isn't the case.
This is my code:
export default class LikeButton2 extends Component {
constructor(props) {
super(props);
this.state = {
numLikes: props.numLikes,
id: props.id,
userLiked: props.userLiked,
isloading: true,
};
}
//....
}
I used React Native Debugger to check for the variable values and the variables "numLikes" and "userLiked" were not getting updated. Attached is the proof for the same:
I also tried using spread syntax.
This is my code:
export default class LikeButton2 extends Component {
constructor(props) {
super(props);
this.state = {
...props,
isLoading: true,
};
}
//....
}
Although this also resulted in undesired values for the state variables. Proof for this in RN Debugger:
How can I correctly update the values?
In this exemple, when the component is mounted to be displayed, the state is set to be equal to the props. The constructor is only called at that time.
After that, if the props change, the state won't be updated to match them. If you want your state to be updated when the props change, you need to use the method ComponentDidUpdate:ComponentDidUpdate(prevProps,prevState).
It will be called every time the props or the state change. You can then compare the current props (this.props) and the previous props (prevProps) to set your state accordingly.
If you just want to use your props as they are you can just use this.props, it will always reflect the value you give to the component.
If I have <Component number={myNumber}/> somewhere in the code, Component will rerender every time myNumber changes, and the value this.props.myNumber in Component will be accurate.
export default class ComponentTest extends Component {
constructor(props) {
super(props);
}
render() {
return <div>{this.props.number}</div>
}
}

AsyncStorage.getItem with state values

I new to react native, I trying to set AsyncStorage.getItem and AsyncStorage.setItem to save my state values,I setup the AsyncStorage.setItem in componentWillMount() function, in the first time my app is running I have state that my component use and he have some keys with specific values, if I setup the AsyncStorage.setItem to get the last item from my state, my component fails.
constructor(props) {
super(props);
this.state = { userAnswer: '', count: 0};
}
componentWillMount(){
AsyncStorage.getItem("userAnswer").then((value) => {
this.setState({userAnswer: value})}).done();
AsyncStorage.getItem("count").then((value) => {
this.setState({count: value})}).done();
}
saveData(){
AsyncStorage.setItem("userAnswer",this.state.userAnswer);
AsyncStorage.setItem("count",this.state.count);
};
I need that in the fist time the app is running the state should be the same meaning the count should stay 0, in my error the count appear as null
every help really appreciated! thanks.
Change it to this:
AsyncStorage.getItem("count").then((value) => {
this.setState({count: value || 0})}).done();
}

Unable to setState to React component

I'm trying to set the state of my PlayerKey component here however the state won't update on a onClick action:
class PlayerKey extends Component {
constructor(props) {
super(props);
this.state = {
activeKeys:[]
}
}
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
})
}
render() {
return (
<div className="key" data-row-number={this.props.rowKey} data-key-number={this.props.dataKeyNumber} onClick={this.activateKey}></div>
)
}
}
I've tried console logging this.state in activateKey and it gives me the state of the component no problem (the blank array) so not sure why I can't update it?
setState method in react is asynchronous and doesn't reflect the updated state value immediately.
React may batch multiple setState() calls into a single update for performance.
So accessing the recently set state value might return the older value. To see whether the state has really been set or not, You can actually pass a function as callback in setState and see the updated state value. React Docs
As in your case, you can pass a function as callback as follows.
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
}, () => {
console.log(this.state.activeKeys); // This is guaranteed to return the updated state.
});
}
See this fiddle: JSFiddle

React Child Component Not Updating After Parent State Change

I'm attempting to make a nice ApiWrapper component to populate data in various child components. From everything I've read, this should work: https://jsfiddle.net/vinniejames/m1mesp6z/1/
class ApiWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
response: {
"title": 'nothing fetched yet'
}
};
}
componentDidMount() {
this._makeApiCall(this.props.endpoint);
}
_makeApiCall(endpoint) {
fetch(endpoint).then(function(response) {
this.setState({
response: response
});
}.bind(this))
}
render() {
return <Child data = {
this.state.response
}
/>;
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
}
render() {
console.log(this.state.data, 'new data');
return ( < span > {
this.state.data.title
} < /span>);
};
}
var element = < ApiWrapper endpoint = "https://jsonplaceholder.typicode.com/posts/1" / > ;
ReactDOM.render(
element,
document.getElementById('container')
);
But for some reason, it seems the child component is not updating when the parent state changes.
Am I missing something here?
There are two issues with your code.
Your child component's initial state is set from props.
this.state = {
data: props.data
};
Quoting from this SO Answer:
Passing the intial state to a component as a prop is an anti-pattern
because the getInitialState (in our case the constuctor) method is only called the first time the
component renders. Never more. Meaning that, if you re-render that
component passing a different value as a prop, the component
will not react accordingly, because the component will keep the state
from the first time it was rendered. It's very error prone.
So if you can't avoid such a situation the ideal solution is to use the method componentWillReceiveProps to listen for new props.
Adding the below code to your child component will solve your problem with Child component re-rendering.
componentWillReceiveProps(nextProps) {
this.setState({ data: nextProps.data });
}
The second issue is with the fetch.
_makeApiCall(endpoint) {
fetch(endpoint)
.then((response) => response.json()) // ----> you missed this part
.then((response) => this.setState({ response }));
}
And here is a working fiddle: https://jsfiddle.net/o8b04mLy/
If the above solution has still not solved your problem I'll suggest you see once how you're changing the state, if you're not returning a new object then sometimes react sees no difference in the new previous and the changed state, it's a good practice to always pass a new object when changing the state, seeing the new object react will definitely re-render all the components needing that have access to that changed state.
For example: -
Here I'll change one property of an array of objects in my state, look at how I spread all the data in a new object. Also, the code below might look a bit alien to you, it's a redux reducer function BUT don't worry it's just a method to change the state.
export const addItemToCart = (cartItems,cartItemToBeAdded) => {
return cartItems.map(item => {
if(item.id===existingItem.id){
++item.quantity;
}
// I can simply return item but instead I spread the item and return a new object
return {...item}
})
}
Just make sure you're changing the state with a new object, even if you make a minor change in the state just spread it in a new object and then return, this will trigger rendering in all the appropriate places.
Hope this helped. Let me know if I'm wrong somewhere :)
There are some things you need to change.
When fetch get the response, it is not a json.
I was looking for how can I get this json and I discovered this link.
By the other side, you need to think that constructor function is called only once.
So, you need to change the way that you retrieve the data in <Child> component.
Here, I left an example code: https://jsfiddle.net/emq1ztqj/
I hope that helps.
Accepted answer and componentWillReceiveProps
The componentWillReceiveProps call in accepted answer is deprecated and will be removed from React with version 17 React Docs: UNSAFE_componentWillReceiveProps()
Using derived state logic in React
As the React docs is pointing, using derived state (meaning: a component reflecting a change that is happened in its props) can make your components harder to think, and could be an anti-pattern. React Docs: You Probably Don't Need Derived State
Current solution: getDerivedStateFromProps
If you choose to use derived state, current solution is using getDerivedStateFromProps call as #DiogoSanto said.
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing. React Docs: static getDerivedStateFromProps()
How to use componentWillReceiveProps
This method can not access instance properties. All it does describing React how to compute new state from a given props. Whenever props are changed, React will call this method and will use the object returned by this method as the new state.
class Child extends React.Component {
constructor() {
super(props);
// nothing changed, assign the state for the
// first time to teach its initial shape.
// (it will work without this, but will throw
// a warning)
this.state = {
data: props.data
};
}
componentWillReceiveProps(props) {
// return the new state as object, do not call .setState()
return {
data: props.data
};
}
render() {
// nothing changed, will be called after
// componentWillReceiveProps returned the new state,
// each time props are updated.
return (
<span>{this.state.data.title}</span>
);
}
}
Caution
Re-rendering a component according to a change happened in parent component can be annoying for user because of losing the user input on that component.
Derived state logic can make components harder to understand, think on. Use wisely.

redux check state at componentWillMount

in my react component, I have two attributes in the state, one in local react state and the other in Redux store.
componentWillMount() {
this.props.fetchExercise(this.props.params.id);
}
constructor(props) {
super(props);
this.state = {editeMode: false}
}
function mapStateToProps(state) {
return {currentExercise: state.currentExercise}
}
export default connect(mapStateToProps, {fetchExercise})(createNewExercisePage);
so according to the path; /new-exe/:id currentExercise in Redux is either empty or something is fetched. editeMode is in React. now I want to check if I have something in currentExercise editemode:true else it should be false (according to false and true I am showing different buttons).
I tried it (with lodash) in componentWillMount(){... this.setState({editeMode:_.isNull(this.props.currentExercise)})}
but it does not work, it reamins false.
generaly in these cases that first should fetch something then check it, what should be the approach.
You should avoid introducing any side-effects or subscriptions in componentWillMount (docs). The documentation also says that "setting state in this method will not trigger a re-rendering", so I guess that means that the setted value will be ignored.
You are not going to change the value of the editeMode entry in the store unless the value of this.props.currentExercise changes, and so it does not serve much purpose to keep track of the changes in order to update the store. Just use the value directly. In your particular case, I would do the following:
componentWillMount() {
this.props.fetchExercise(this.props.params.id);
}
constructor(props) {
super(props);
this.state = {}
}
render(){
const editeMode = _.isNull(this.props.currentExercise);
// The rest of your render logic, using editeMode instead of this.state.editeMode
}
function mapStateToProps(state) {
return {currentExercise: state.currentExercise}
}
export default connect(mapStateToProps, {fetchExercise})(createNewExercisePage);
Put the code in
componentWillReceiveProps.
componentWillReceiveProps(nextProps) {
this.setState({ editeMode: !nextProps.currentExercise) });
}
Redux will make sure the props get updated.
You should also consider putting the editMode state in Redux instead.

Resources