Why won't my changes to state show in my componentDidMount lifecycle? - reactjs

I am building an app using React and for my homepage, I set state in the componentDidMount lifecycle:
export default class HomePage extends Component {
state = {
posts: [],
token: '',
};
//Display posts when homepage renders
componentDidMount() {
//If token exists, run lifecycle event
if (this.props.location.state.token) {
this.setState({ token: this.props.location.state.token });
}
Axios.get('http://localhost:3000/api/posts/all')
.then((req) => {
this.setState({ posts: req.data });
})
.catch((err) => {
console.log(err.message);
throw err;
});
console.log(this.state);
}
However when I run the console log at the end of the lifecycle method, it shows posts and token as still being empty. I know they are being populated because the posts from the req.data show up in my JSX. Why does it show state being empty when I console log inside the method?

React setState is asynchronous!
React does not guarantee that the state changes are applied immediately.
setState() does not always immediately update the component.
Think of setState() as a request rather than an immediate command to update the component.
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});

Related

Cann't set the state data from response data

I'm doing in the axios to get data from webAPI . The problem is state is empty
componentDidMount() {
uploadFilesService.getFiles().then((response) => {
this.setState({
fileInfos: response.data,
});
console.log('check 1', response.data)
console.log('check 2', this.state.fileInfos)
});
}
The other project with the same method is OK .
You can the use second argument of setState
this.setState({
fileInfos: response.data,
}, () => {
// do something with the new state
});
From React doc:
The second parameter to setState() is an optional callback function
that will be executed once setState is completed and the component is
re-rendered. Generally we recommend using componentDidUpdate() for
such logic instead.

Component did update works only after second click

My code adds a new item in the firebase databse when i click a button, then i want the list of objects in my page to automatically update, because i don't want to manualy reload the page. So i came up with this code
constructor(props){
super(props);
this.state = {
groups: [],
code:'',
name:'',
update:true
}
}
async fetchGroups (id){
fetchGroupsFirebase(id).then((res) => {this.setState({groups:res})})
};
async componentDidUpdate(prevProps,prevState){
if(this.state.update !== prevState.update){
await this.fetchGroups(this.props.user.id);
}
}
handleCreateSubmit = async event => {
event.preventDefault();
const{name} = this.state;
try{
firestore.collection("groups").add({
title:name,
owner:this.props.user.id
})
.then((ref) => {
firestore.collection("user-group").add({
idGroup:ref.id,
idUser:this.props.user.id
});
});
this.setState({update: !this.state.update});
}catch(error){
console.error(error);
}
What i was thinking, after i add the new item in firebase, i change the state.update variable, which triggers componentDidUpdate, which calls the new fetching.
I tried calling the fetchGroups function in the submit function, but that didn't work either.
What am i doing wrong and how could i fix it?
ComponentDidUpdate will not be called on initial render. You can either additionally use componentDidMount or replace the class component with a functional component and use the hook useEffect instead.
Regarding useEffect, this could be your effect:
useEffect(() => {
await this.fetchGroups(this.props.user.id);
}, [update]);
Since you can't use useEffect in class components so you would need to rewrite it as functional and replace your this.state with useState.

Can't set state in react to array of strings

I'm new to react and having a super hard time. My most recent problem is trying to set the state of 'favMovies' to an array of strings (movie IDs).
States:
export class MainView extends React.Component {
constructor() {
super();
this.state = {
movies: [],
favMovies: [],
user: null,
};
}
Setting states:
onLoggedIn(authData) {
console.log(authData);
console.log(authData.user.FavoriteMovies);
this.setState({
favMovies: authData.user.FavoriteMovies,
});
this.setState({ user: authData.user });
localStorage.setItem('token', authData.token);
localStorage.setItem('user', JSON.stringify(authData.user));
this.getMovies(authData.token);
this.getUsers();
}
I kind of understand that set state is async and doesn't happen until the next render. The part that I'm confused by is the 'user' that get's set after 'favMovies' works as expected, but 'favMovies' is undefined.
I know this is probly a dumb question, but I'm absolutely lost in react right now and struggling. Any help would be appreciated.
It's alaways better to use single setState if possible, because everytime you call setState it will re-render the view. One more tip for you, when you are dealing with object and arrays try to use spread operstor to assign to the state, instead of direct assignment.
onLoggedIn(authData) {
console.log(authData);
console.log(authData.user.FavoriteMovies);
this.setState({
favMovies: [...authData.user.FavoriteMovies],
user: authData.user
});
//this.setState({ user: authData.user });
localStorage.setItem('token', authData.token);
localStorage.setItem('user', JSON.stringify(authData.user));
this.getMovies(authData.token);
this.getUsers();
}
As we know react setState is asynchronous, state won't reflect immediately. We can use callback with setState where we can access updated state.
this.setState(
{
favMovies: [...authData.user.FavoriteMovies],
user: authData.user
// ...
},
() => {
this.doSomethingAfterStateUpdate();
}
);

Issues with state-changes in submit method of AtlasKit Form

In the submit method of an Atlaskit Form, I want to change a value of a state property that results in the form being hidden:
<Form onSubmit={data => {
return new Promise(resolve => {
setShowForm(false);
resolve();
})
}}>
</Form>
However, this results in a React error:
Can't perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in the
componentWillUnmount method.
The error disappears when i set that value a little later:
setTimeout(() => setShowForm(false));
So apparently the form is still unmounting while i change state (although i don't know why that should affect on the form, but i am not too familiar with React yet). What is the approach i should be taking here?
This is because you made an asynchronous request to an API, the request (e.g. Promise) isn’t resolved yet, but you unmount the component.
You can resolve this issue by maintaining a flag say _isMounted to see if component is unmounted or not and change the flag value based on promise resolution.
// Example code
class Form extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this._isMounted = true;
axios
.get('my_api_url')
.then(result => {
if (this._isMounted) {
this.setState({
data: result.data.data,
});
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
...
}
}

re-render triggered in componentDidMount

In the life cycle of a component, if a re-render is triggered by some synchronous operation in componentDidMount(), would the user have a chance to see the first render content on browser?
e.g. If I toggle a start downloading boolean flag in componentDidMount() through redux, which then causes the re-render because the flag is mapped to redux for the component.
-------Update Info-----
The sync operation is just changing the start downloading flag to true, and the flag is mapped to the component, where the flag is checked to determine the JSX contents in render(). In redux, right after the flag is set to true, then the downloading operation begins. When downloading is completed, redux sets the flag to false.
Consider the following lifecycle sequence:
render() //JSX A
componentDidMount() // the flag is set
render() // re-render JSX B
Will JSX A be displayed in the browser, regardless of how quick it is?
the action creator called in componentDidMount():
export const downloadArticleList = () => {
return (dispatch, getState) => {
// set start flag to true synchronously, before axios.get
dispatch(listDownloadStart());
axios.get('/articles')
.then(response => {
//set the flag to false and update the data
dispatch(saveArticleList(response.data))
})
.catch(err => {
dispatch(serverFail(err))
console.log("[downloadArticleList]] axios", err);
})
}
}
It is a SPA, no SSR.
It depends on a few things:
How long sync operation takes
Are you doing SSR (thus there will be time dedicated for DOM rehydrating)
Generally, I'd consider this as an antipattern
As we discuss in the comment here is the example :
interface ExampleComponentProps {
}
interface ExampleComponentState {
loading: boolean;
}
export class ExampleComponent extends React.Component<ExampleComponentProps, ExampleComponentState>{
constructor(props, context) {
super(props, context);
this.state = { loading: true };
}
componentDidMount() {
//some method {}
//after get result
this.setState({
loading: false
})
}
render() {
return (
<div>
<Spin spinning={this.state.loading} >
//Your COmponent here
</Spin>
</div>
)
}
}
If your project is complicated, the easiest way is using
setTimeout(() => {
this.setState({
// your new flag here
})
}, 0);

Resources