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.
Related
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 });
}
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.
I'm trying to update the state of a react app with data from a websocket and it all works but the issue is this:
In the view where I monitor the users currently logged in, the state is only reflecting the second to last update and not the last. If I refresh the page the data (I'm passing a current_url) gets updated but if I navigate to the page from another page, the previous url is shown instead of the current. I'm assuming that the state array is being updated only after the component renders and showing an old state.
I've tried using the setState callback to forceUpdate() but this doesn't seem to work. Any help would be very much appreciated. Thanks.
class UsersLoggedIn extends Component {
constructor(props) {
super(props);
this.state = {
usersLoggedIn: []
}
}
getUsersLogged() {
const socketIo = SocketIO();
if (typeof socketIo === 'object') {
socketIo.emit('getUsersLogged', (data) => {
const usersLoggedIn = [];
// groups the data from the socket by session id.
for (let id in data) {
let temp = [];
for (let socketId in data[id]) {
temp.push(data[id][socketId]);
}
usersLoggedIn.push({
...temp[0],
children: temp
});
}
this.setState({usersLoggedIn}, () => {
this.forceUpdate();
});
});
}
}
componentDidMount() {
// we need to pass data to the socket here.
const socketIo = SocketIO();
if (typeof socketIo === 'object') {
socketIo.on('loadUserLogged', (data) => {
this.getUsersLogged();
});
}
}
I ran into a use case where I have to reset the current state to initial state. I tried preserving in an instance variable but forgot that it works through reference and when the state updates the instance variable will also update. When the user hits reset button the state should refer to initial state which is stored in this.reset. But I couldn't find a workaround.
class MyApp extends {Component} {
constructor(props){
super(props);
this.state = {
data: null
}
this.reset = null;
this.resetData = this.resetData.bind(this);
}
componentWillReceiveProps(nextProps) {
const {data} = nextProps;
this.reset = data;
this.setState({
data
});
}
resetData(){
this.setState({
data: this.reset
});
}
render() {
return(
<button onClick={this.resetData}> {this.state.data}</button>
);
}
}
Are you able to use a third party library?
Lodash provides the deepClone() method that would be useful to you for this, and would allow you to reset your component's state, regardless of the shape of data that is passed in.
You'll also want to make sure you use deepClone() each time you invoke resetData() to ensure that the data reference passed to .setState() is to a copy (clone) of your this.reset data, rather than a direct reference to the this.reset data:
import _ from 'loadash'
class MyApp extends Component {
...
componentWillReceiveProps(nextProps) {
const {data} = nextProps;
// Invoke cloneDeep to get a clone of data that is also a unique
// reference
this.reset = _.cloneDeep(data);
this.setState({
data
});
}
resetData(){
// Remember to clone reset data each time you reset data, so that
// data is a reference to a new copy of this.reset, rather than
// the reset data you stored when componentWillReceiveProps was
// called
this.setState({
data: _.cloneDeep(this.reset)
});
}
...
}
I don't know why the result of my axios promise doesn't show up in the render function. I'm using the create-react-app tools by the way.
_getPrice() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
return prices;
})
}
render() {
return(<div><h3> {this._getPrice()} </h3></div>);
}
React only re-renders components when either the state or props of the component change. If data changes during the render cycle, but doesn't interact with those variables, then the changes will not show up.
You can save the result of your promise to state as follows:
getInitialState() {
return {prices: undefined}
}
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
this.setState({prices: prices});
}.bind(this))
}
render() {
return(<div><h3> {this.state.prices} </h3></div>);
}
first you cant call a function in return in render function and if you want update your view you must update state or props...
When requesting data to the server, the request is async, this means it will take time for the server to respond and the browser will continue the execution, than been said, in your current implementation you are returning a promise in your _getPrice function and then when the server responds you are not doing anything with the data.
The second problem is that react will only re-render the component when there are changes on the state or on the props, and in your current implementation you are not changing any of that.
Here's a sample of how you need to do it in order to make it work.
class YourComponent extends Component {
state = {
prices: 0,
};
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then((response) => {
let prices = response.data.data.amount;
this.setState({ prices });
});
}
render() {
const { prices } = this.state;
return(
<div>
<h3> {prices} </h3>
</div>
);
}
}
Good luck!