Can't set state in react to array of strings - reactjs

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();
}
);

Related

Setting the state in a component from localStorage

I'm building a multi-step form in React and one of my objectives is to save the user's input if they haven't finished filling in the form. I have saved the user's input in the browser's localStorage by using setItem().
The input fields set the local state which in turn get saved to the localStorage.
However, when the page is refreshed, I want to retrieve the data from localStorage and set the state variables from there so that it pre-fills the input fields with the saved data (if that makes sense)
I'm using setState() in componentDidMount() to do this, although I think that's creating an anti-pattern and I'm not entirely sure what that is. It works fine when I use UNSAFE_componentWillMount but I don't want to use a deprecated lifecycle method.
This is my code :
componentDidMount() {
this.formData = JSON.parse(localStorage.getItem('form'));
this.setState({
type: this.formData.type,
subtype: this.formData.subtype,
brand: this.formData.brand
})
}
the idea to use componentDidMount is correct. There is another anti-pattern.
Don't use this.formData = ... outside of component's constructor - https://reactjs.org/docs/react-component.html
Whole working example would look like this. I added callback after setState to show that loading & saving to localStorage actually works.
export default class Hello extends React.Component {
state = {
type: undefined,
subtype: undefined,
brand: 0,
}
componentDidMount() {
const formData = JSON.parse(localStorage.getItem('form')) ?? {};
if (formData) {
formData.brand += 5
this.setState({
type: formData.type,
subtype: formData.subtype,
brand: formData.brand,
}, () => {
console.log('newState', this.state)
localStorage.setItem('form', JSON.stringify(this.state))
})
}
}
render() {
return <h1>Hello {this.state.brand} </h1>
}
}
you can use constructor function if you do not want to retrieve local storage data in componentDidMount()
constructor(){
const formData = JSON.parse(localStorage.getItem('form'));
const { type, subtype, brand } = formdata;
this.setState({ type, subtype, brand });
}
Though I'd suggest to go with didMount.
componentDidMount() {
const formData = JSON.parse(localStorage.getItem('form'));
const { type, subtype, brand } = formdata;
this.setState({ type, subtype, brand });
}

Best Way to Handle Multiple State Changes

I'm pretty new to react. I'm building a web app that is working pretty well but I'm not sure I'm handling state changes correctly. For instance, I have a method that gets called in componentDidMount.
It's seems inefficient and bad practice to keep calling setState but I'm not really sure of a better way to do it.
I would love some feedback.
class AuditScreen extends Component {
constructor(props) {
super(props);
this.state = {
currentVehicle: null,
currentVehicleIndex: 0,
vehicles: [],
activePictureIndex: 0,
show: false,
loading: false,
next_page: `${props.backend.id}/images`,
};
}
componentDidMount() {
this.getImages()
}
getImages() {
if (!this.state.loading) {
this.setState({
loading: true,
});
this.registerScrollEvent();
axios.get(this.state.next_page)
.then((response) => {
const paginator = response.data;
const vehicles = paginator.data.filter((vehicle) => {
if (vehicle.enhanced && vehicle.enhanced.length) {
return vehicle;
}
});
if (vehicles && vehicles.length) {
this.setState({
vehicles: [...this.state.vehicles, ...vehicles],
next_page: paginator.next_page_url,
});
if (this.state.currentVehicle === null) {
this.setState({
currentVehicle: vehicles[0]
});
}
}
// remove scroll event if next_page_url is null
if (!paginator.next_page_url) {
this.removeScrollEvent();
}
})
.finally((response) => {
this.setState({
loading: false,
});
});
}
}
}
.....
}
Thanks!
Like #Dave Newton says, I don't think this is bad practice. Updating the state doesn't immediately trigger an update. Instead, state updates often (but not always) batch up and then trigger a single update.
This article explains the batching mechanism in detail. Up to React 17, state-update batching only occurs within event handlers and within componentDidMount. They give an explicit example where "setState is called inside componentDidMount which causes only one extra update (and not three)".
So React already does what you want. React 18 is going to give you more control over the batching behavior, and more automatic batching. I found this description helpful for understanding what's coming and also how React 17 and lower currently work.
if you don't want to stop calling the setState method in react, use context for small project or redux to keep states and reducers with functional components. Best regards.

React setState {} not setting variable

I have the following code for a main view and user login:
export class MainView extends React.Component {
constructor(){
super();
this.state = {
array: [],
user: null,
userData: {},
};
}
setUserData(user) {
if (user) {
this.setState({ userData: user.user });
console.log(user.user);
console.log (userData); /* errors in console */
} else {
console.log('userData not set');
}
}
onLoggedIn(authData) {
this.setState({ user: authData.user.Username });
/* setting localstorage key:item */
// console.log(authData);
localStorage.setItem('token', authData.token);
localStorage.setItem('user', authData.user.Username);
this.getArrayObjects(authData.token);
this.setUserData(authData);
}
using Passport to get the auth data and token. I don't get why it will log user.user in the setUserData function but will log an "undefined" error for the userData variable in the same function. Any ideas?
You are trying to access an undefined variable.
userData is present inside the state.
So you should access it with this.state.userData.
But even it you write console.log(userData);, it will print {} because in React, setState is asynchronous. Meaning, the state is not updated immediately.
If you want to check whether state has been update or not, check it like this.
this.setState({ userData: user.user }, () => {
console.log(this.state.userData);
});
The, second parameter to setState is a function that is called once state is successfully updated. So, inside there you can see the value in console.
You can't access the state directly as a var. You need to access the state, and the the properties:
console.log(state.userData);
Also, in your code, when you print state.userData, probably you will see the old value, since the setState is an async function.

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

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 };
});

How to fix: setState does not work in gastby?

I'm using gatsby for server side rendering.
Here's my code:
class BookSearch extends Component {
state = {
search: '',
books: '',
};
componentDidMount() {
this.loadData()
}
loadData () {
axios.get('/books/list')
.then(response => {
this.setState({books: response.data.books});
console.dir(response.data.books);
})
.catch(error => {
this.setState({error: true});
});
}
Unfortunately, this.setState does not work in gatsby. componentDidMount is not being called when I load the page. What should I do?
I think the issue is of binding this to loadData method.
You can bind this in 2 ways.
Bind this in the constructor,
constructor(props){
super(props)
this.state = {
search: '',
books: '',
}
this.loadData = this.loadData.bind(this) //Bind this here
}
Or you can simply use arrow function,
loadData = () => { //Arrow function auto binds `this`
axios.get('/books/list')
.then(response => {
this.setState({
books: response.data.books
});
console.dir(response.data.books);
})
.catch(error => {
this.setState({error: true});
});
}
I think you should have got an error? It's because you have not initialized error state. You must initialize state before you can use them:
state = {
search: '',
books: '',
error: false
};
I hope this may fix the issue. Otherwise, I couldn't see any issue in your code.
You mentioned you're using SSR?
Try using componentWillMount in this case, since componentDidMount is not called in SSR.
In case you're using react version > 16.3:
When supporting server rendering, it’s currently necessary to provide the data synchronously – componentWillMount was often used for this purpose but the constructor can be used as a replacement. The upcoming suspense APIs will make async data fetching cleanly possible for both client and server rendering.
Reference: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data
In your case, I think it would make more sense to use the getInitialProps static method. (https://nextjs.org/learn/basics/fetching-data-for-pages/fetching-batman-shows)
If you're not very familiar with SSR, Next.js has great tutorials:
https://nextjs.org/learn/basics/getting-started
This may help you out!

Resources