Setting the state in a component from localStorage - reactjs

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

Related

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

React - using state value to filter API fetch result

I am using a fetch request to get some fx rates from an API. However, I only need to store the rate for a currency that's been previously selected by the user and saved as state (state.currency).
The example below works well for predefined values (eg. json.rates.GBP) but I can't find a way to link it with this.state.currency.
Here is my current code:
var fxRates;
class FxRateCheck extends Component {
constructor() {
super();
this.state = {
currency: "",
isLoading: true
};
}
handleSubmit = () => {
fetch("https://api.exchangeratesapi.io/latest?base=USD")
.then((response) => response.json())
.then((json) => {
fxRates = json.rates.GBP;
// I need to replace GBP with the value of this.state.currency
})
.catch((error) => console.error(error))
.finally(() => {
this.setState({
isLoading: false
});
});
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
The user sets the currency base in their state, you then need to use the same value as the key when you access a property in a different object.
fxRates = json.rates[this.state.currency];
There are a few other things you need to change though. the global variable fxRates is generally a bad practice, if you need to use the result in other places in your component consider putting it on the component state.
You should also let the user choose what to compare with. ?base=USD is hardcoded currently. Maybe consider letting them change that in a future change :)
Heres an example of what I was describing :)
You can use dot and bracket notation to access properties in an object
const data = {
id: 1,
value: "one"
}
can be accessed like
data.value
or
data["value"]
or
var property = "value";
data[property]
So, in your case you can do it like
fxRates = json.rates[this.state.currency];

Accessing JSON Array Data in React.js/Next.js Combo

Given an API which returns JSON data like:
["posts":
{"id":1,
"name":"example",
"date":"exampledate",
"content":"examplecontent",
"author":"exampleauthor"},
{"id":2,
..]
The length of the array is unknown.
I am fetching data via isomorphic-fetch like this:
displayPosts.getInitialProps = async function() {
const res = await fetch('.../post');
const data = await res.json();
return{
posts: data.posts
}
}
which is working (console.log.stringify(data)).
Now i want to display such posts on my displayPosts page.
Therefore i am using the following React Component.
class Posts extends React.Component {
stat = {
// here i don't know how to set the state
}
render() {
return (
// i want to render the data here
);
}
}
export default Posts;
Question: How do i set a state, so that i can neatly display every post in my displayPosts.js page with
<Posts posts={props.Posts}/>
?
class Posts extends React.Component {
state = {
posts: []
}
componentDidMount() {
this.savePosts();
}
componentDidUpdate() {
this.savePosts();
}
savePosts = () => {
if(this.props.posts){
//do any other processing here first if you like
this.setState({
posts: this.props.posts
});
}
}
You probably don't need to save the posts in state, since you could just pull them from props directly. But if you need to process or transform them somehow, it might make sense.
In either case, you just hook into the lifecycle methods to do this.
Note: This will set the state every time the component updates. Often you only want to update the state when that specific prop changes. If so, you can first compare the old and new props to see if it has changed in a way that means you want to update your state.

Updating state - why creating a new copy of state when calling setState?

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

Reset the state before render in reactjs

I am using react with Meteor and my data is coming from createContainer
export default createContainer((props) => {
const { search } = props.dataTables.favorites;
let data = [];
data = Meteor.user().favorites;
return { data };
}, StarredListView);
Now I wanted to do some processing on the data after state intialization
constructor(props) {
super(props);
this.state = {
data: props.data
};
this.resetOldData = this.resetOldData.bind(this);
}
Now how can I make sure that before render the data my resetOldData called and reset the state. Where can I call this this function to reset my state.
resetOldData() {
Meteor.call('reset.old.favorite.data', (err, res) => {
if (err) { this.props.renderAlert(err.reason, null, true); }
if (res) {
this.props.renderAlert(res.message);
this.setState({ data: res });
}
});
}
You need to take a look at React lifecycle documentation: https://facebook.github.io/react/docs/react-component.html
There are very specific places where you will want to respond to data coming in and how that should effect the state of your component.
Certain changes to the state only belong in very specific places in the lifecycle of a component.

Resources