I'm trying to use this.setState within handleFormSubmit however this.setState isn't updating and I'm not sure why. If I run console.log(updatePosition) before this.setState I can that all the data is there. What am I missing? I use similar code for handleChange and I don't have problems.
constructor(props) {
super(props);
let uniqueId = moment().valueOf();
this.state = {
careerHistoryPositions: [{company: '', uniqueId: uniqueId, errors: {} }],
};
this.handleFormSubmit = this.handleFormSubmit.bind(this);
}
handleFormSubmit(event) {
event.preventDefault();
const { careerHistoryPositions } = this.state;
const updatePosition = this.state.careerHistoryPositions.map((careerHistoryPosition) => {
const errors = careerHistoryValidation(careerHistoryPosition);
return { ...careerHistoryPosition, errors: errors };
});
console.log(updatePosition)
this.setState({ careerHistoryPositions: updatePosition });
}
Keep in mind that the state isn't updated immediately. If you want to check if it's updated use callback function. Something as follows:
this.setState({ careerHistoryPositions: updatePosition }, () => console.log(this.state.careerHistoryPositions);
From the docs :
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Hope this helps.
You should show how you are calling handleFormSubmit chances are that it's bound to a Dom event. So this is not the class/component, instead if you console.log(this); you'll see that it's the Dom element, the form element.
To make your code work as intended, in your component constructor() method, add this to rebind the handler function to the react component's class method, and you'll have access to this.state and this.setState()
this.handleFormSubmit = this.handleFormSubmit.bind(this);
Related
For a React app that I inherited from another developer, one of the pages includes:
import { getLogUser } from "../../appRedux/actions/authAction";
constructor(props) {
super(props);
this.state = {
user: null,
};
}
UNSAFE_componentWillMount() {
let user = getLogUser();
this.setState({ user });
// user state is used inside the render part
}
componentDidMount = () => {
let { username } = getLogUser();
// ... username is used inside some logic within the componentDidMount method.
I would like to get rid of the UNSAFE_componentWillMount method.
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
If that is indeed the correct way to do it, shouldn't I then also
replace let { username } = getLogUser(); inside
componentDidMount with let { username } = this.state.user?
To start, let me explain what is UNSAFE_componentWillMount first
By defination
UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering.
So it means UNSAFE_componentWillMount() will be called before render() (the component has not been on UI yet). This is totally opposite of componentDidMount() which is called after render()
To go deeper into why React's team wanted to make it UNSAFE as for a deprecated function, you can check this RFC.
Following up on your questions
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
The benefit to having your function calls in the constructor is similar to UNSAFE_componentWillMount which makes sure your data available before rendering trigger.
So I'd say yes for your case, you can do it as long as it's not an asynchronous function (like async/await)
constructor(props) {
super(props);
this.state = {
user: await getLogUser(), //YOU CANNOT DO THIS WAY
};
}
This is the correct way
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
So what if getLogUser() is asynchronous? componentDidMount comes in handy. It will be triggered after first rendering but you can wait for your data as much as you want and beyond that, it won't block your UI's interactions (or you can show a loading UI instead)
componentDidMount = async () => {
const user = await getLogUser()
setState({ user })
}
render() {
//show loading if `user` data is not populated yet
const { user } = this.state
if(!user) {
return <div>Loading</div>
}
}
If that is indeed the correct way to do it, shouldn't I then also replace let { username } = getLogUser(); inside componentDidMount with let { username } = this.state.user?
Yes, indeed. You can do it if you already populate user state in constructor, but you need to ensure your function will be executed in a small amount of time. If your function call takes too long, that will cause UI problems due to the blocked rendering.
//trigger before first rendering
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
//trigger after first rendering
componentDidMount = () => {
const { username } = this.state.user;
}
React docs:
Never mutate this.state directly, as calling setState() afterwards
may replace the mutation you made. Treat this.state as if it were
immutable.
That's clear.
class App extends React.Component {
state = {
data: []
}
the following I understand
updateState(event) {
const {name, value} = event.target;
let user = this.state.user; // this is a reference, not a copy...
user[name] = value; //
return this.setState({user}); // so this could replace the previous mutation
}
this following I don't understand
updateState(event) {
const {name, value} = event.target;
let user = {...this.state.user, [name]: value};
this.setState({user});
}
I understand (as in previous example), that I should not either only:
mutate state directly without calling setState; or
mutate it and then use setState afterwards.
However, why can't I just (without direct mutation) call setState without creating a new copy of state (no spread operator/Object.assign)? What would be wrong with the following:
getData = () => {
axios.get("example.com") ...
this.setState({
data:response.data
})
}
Why should it be:
getData = () => {
axios.get("example.com") ...
this.setState({
data:[...data, response.data]
})
}
render (){
...
}
}
What would be wrong with the following:
this.setState({
data: response.data,
});
Absolutely nothing, unless you don't want to replace the contents of this.state.data with response.data.
Why should it be:
this.setState({
data: [...data, response.data],
});
Because with spread you are not loosing the contents of this.state.data - you are basically pushing new response into the data array.
Note: You should use callback inside setState to get access to current data from this.state.
this.setState((prevState) => ({
data: [...prevState.data, response.data],
}));
I am trying to update the state of the variable "selectedPlayer" however when I setState function the state of this variable does not update, I have tested this through console logs as you can see below.
Below is how I am trying to set the state of the variable and the some of this class file for your knowledge
this.setState({ selectPlayer: player });
this is some of the Component file and a function which i am using to update the state.
class StatApp extends Component {
constructor(props) {
super(props);
this.state = {
selectedPlayer: 'dassdasda',
};
this.selectPlayer = this.selectPlayer.bind(this);
}
selectPlayer = e => {
e.preventDefault();
// console.log(e.target.value); //will give you the value continue
// Store Value
const selectPlayer = this.state.selectedPlayer;
console.log(this.state.selectedPlayer);
console.log(selectPlayer);
// Test to see if we are getting the value from the playerButton
const player = e.target.value;
console.log(player);
this.setState({ selectPlayer: player });
console.log(selectPlayer);
};
setState is asynchronous, so when you are console logging it, state has not yet been updated. You can use the callback form of setState to get the updated state:
this.setState(
{ selectPlayer: player },
() => console.log(this.state.selectPlayer)
);
I guess it'a typo.
try changing
this.setState({ selectPlayer: player });
to
this.setState({ selectedPlayer: player });
Find the other answer for the updated state not being printed on console.
As we've known, setState is async. I've read few questions about setState, on how to use the value right after setState, but those aren't what I need right now.
I'm trying to set value for array List, and then use that List to do a function to get the value for Result. If setState isn't async, then it would be like this
`
handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
`
Is there anyway to achieve this? A documentation or keyword to research would be awesome.
Many thanks
There is a callback parameter to setState which is called after the state has been updated
this.setState({
list: resultList,
result: this.doSomething(resultList)
}, () => {
//do something with the updated this.state
});
You can use async await like
async handleChange(e) {
const resultList = this.state.list.slice();
resultList[e.target.id] = e.target.value;
this.setState({
list: resultList,
result: await this.doSomething(resultList) // this.doSomething(this.state.list)
});
}
The use of this.state together with this.setState is discouraged, exactly because state updates are asynchronous and may result in race conditions.
In case updated state derives from previous state, state updater function should be used. Because it's asynchronous, event object should be treated beforehand, due to how synthetic events work in React:
const { id, value } = e.target;
this.setState(state => {
const list = [...state.list];
list[id] = value;
return {
list,
result: this.doSomething(list)
};
});
I have this component (simplified version):
export default class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
data: {}
};
}
componentDidUpdate(prevProps, prevState) {
if(this.props.time && this.props.time !== prevProps.time){
this.setState({
isLoading: true
})
fetch(...).then(data => {
this.setState({
data: data
isLoading:false
}
}
}
render(){
{isLoading, data} = this.state;
return (isLoading ? /*show spinner*/ : /* show data*/);
}
}
This component works: it shows a spinner while fetching data, then it shows the data.
I'm trying to test it using jest and enzyme:
test('Mounted correctly', async() => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
myComponent.setProps({time: '02-01-18'}); //necessary to call componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}
From my knowledge, in order to call componentDidUpdate you have to call setPros (link). However, following the debugger, the call end when hitting:
this.setState({
isLoading: true
})
Which is kinda of expected, the problem is that the snapshot is:
Object {
"isLoading": true
"data": {}
}
Which is, of course, something that I don't want. How can I solve this?
UPDATE: I found a(n ugly) solution!
The problem is that what we want to test is this setState is completed:
this.setState({
data: data
isLoading:false
}
Now, this doesn't happen even by setting await myComponent.setProps({time: '02-01-18'}); (as suggested in one of the answers), because it doesn't wait for the new asynchronous call created by the setState described above.
The only solution that I found is to pass a callback function to props and call it after setState is completed. The callback function contains the expect that we want!
So this is the final result:
test('Mounted correctly', async() => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
const callBackAfterLastSetStateIsCompleted = () => {
expect(topAsins.state()).toMatchSnapshot();
}
myComponent.setProps({time: '02-01-18', testCallBack: callBackAfterLastSetStateIsCompleted}); //necessary to call componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}
And modify the component code as:
this.setState({
data: data
isLoading:false
},this.props.testCallBack);
However, as you can see, I'm modifying a component in production only for testing purpose, which is something very ugly.
Now, my question is: how can I solve this?
All you need to do here to test is make use of async/await like
test('Mounted correctly', async () => {
let myComponent = mount(<MyComponent time='01-01-18'/>);
await myComponent.setProps({time: '02-01-18'}); //necessary to call componentDidUpdate, await used to wait for async action in componentDidUpdate
expect(myComponent.state()).toMatchSnapshot();
}