setState updating but values not being retained [duplicate] - reactjs

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 2 years ago.
I have a component fetching a API service that returns JSON. The service works, but the state on returns the json in the fetch after the second alert(this.state.questions); below.
My understanding of the execution stack of React is it shouldn't do that (I'm reasonable new to React). Or is it that state has a limited lifestyle time?
import React from 'react';
import { ConversationalForm } from 'conversational-form';
export default class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
this.submitCallback = this.submitCallback.bind(this);
}
componentDidMount() {
fetch('http://localhost:3000/api/questions')
.then((response) => response.json())
.then((responseJson) => {
this.setState({questions:JSON.stringify(responseJson)}, () => console.log(this.state.questions)); // Works fine, log has the json response
alert(this.state.questions); // Returns expected JSON - runs after the next alert
})
alert(this.state.questions); // returns undefined - runs first
this.cf = ConversationalForm.startTheConversation({
options: {
submitCallback: this.submitCallback,
showProgressBar: true,
preventAutoFocus: false,
},
tags: this.state.questions // returns undefined
});
this.elem.appendChild(this.cf.el);
}
submitCallback() {
var formDataSerialized = this.cf.getFormData(true);
console.log("Formdata, obj:", formDataSerialized);
this.cf.addRobotChatResponse("Your are done. Grab a well deserved coffee.")
}
render() {
return (
<div>
<div
ref={ref => this.elem = ref}
/>
</div>
);
}
}

i don't understand what's the problem, u expect the JSON to be at the second alert where there's undefined? which calls first ofc, because of the behavior of the async request, and it has nothing to do with react, it's a javascript thing.
if u need to run any code after there's a "questions" in the state, then u should use the componentDidUpdate and check if there's a "questions" in the state and then run the code.
if u would have used the React-Hooks it would be even easer :)

fetch(url) returns Promise.
In other words, it sends request to the url, it doesn't wait until receiving response and run next 'now' chunks - in this case, second alert command.
So if you are going to process response, you should implement the processing code in the second 'then'.
.then(responseJson) {
...
//process response
}
Or you can use async-await.
'Async-await' waits until coming response.
If you don't understand, please feel free to ask me.

You can't depend on a state value in the same function in which it was set. There is no guarantee that the state (which updates async) will have finished updating before you use it at the bottom of componentDidMount. You should either:
1: Use the raw value and not the state value.
2: Use the state value after you are certain that it exists.
Also Note: You are not waiting for the promise to resolve before trying to use the state.

Related

How read array/object from get Axios in React function [duplicate]

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

componentDidMount is being fired multiple times in ReactJs [duplicate]

This question already has answers here:
ReactJs componentDidMount executes twice
(2 answers)
Closed 3 months ago.
I am calling an api using axios inside componentDidMount and the api is being called multiple times.
Here is my code
export default class Listings extends Component{
state = {
listings: null
}
//when the component is mounted to dom first
componentDidMount() {
this.getAllCryptoListings();
}
getAllCryptoListings() {
let response = null;
axios.get('http://localhost:8000/')
.then( (res) => {
response = res;
console.log(response.data.data);
})
.catch( (error) => console.log(error) )
.finally( () => { this.setState({listings: response.data.data}) } )
}
}
Normally I expected the code to run one time only as per the description of the function here componentDidMount.
The documentation says
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance.
How to make the code inside componentDidMount run only once?
componentDidMount() is called twice if you setState in it, you won't be able to disable this.
You can make your call in componentWillMount, but this is UNSAFE so be careful using it! (not recommended)
If this does not work you should check where your component is called. Maybe his parents are re-rendering and so calling the render to verify twice with the initial state.
You can read more about it here
In React version 18, a change was made to strict mode so that components will mount, then unmount, then mount again. This was added to help us all start catching issues that will affect an upcoming feature. In a future version of react, the state will be able to be preserved between unmounts, and as a result, components may mount multiple times.

Trying to understand console.log behavior and state in react [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 7 months ago.
I'm trying to learn react and in doing so i need to do a lot of console.log(something). In the example below, could you please explain why the second console.log(this.state.foo);, the one within componentDidMount(), is not true? Thanks in advance.
Note: please don't suggest using functional components or hooks, the repo i will be working on is all class based components, so that's what i'm focusing on for now.
class App extends React.Component {
constructor (props){
super(props);
this.state = {
foo: false
}
console.log(this.state.foo);
}
componentDidMount() {
console.log('componentDidMount() lifecycle');
// Trigger update
this.setState({ foo: true });
// Why won't this one show in the console?
console.log(this.state.foo);
}
render() {
console.log('Render lifecycle')
return(
<h1>Hellos</h1>
)
}
}
ReactDOM.render( < App />, document.getElementById('container'));
That console.log is logging, but it's still logging false for the value this.state.foo. The reason for this is that setState is actually an asynchronous function (in other words, it takes time to execute completely), and you cannot expect the state update operation to have succeeded before you try to access the new value for state.
componentDidMount() {
console.log("componentDidMount() lifecycle");
// Updating state is asynchronous
this.setState({ foo: true });
// The line below is running, but the previous line updating state
// has not finished executing, which is why it's still logging false
console.log("component did mount", this.state.foo);
// => "component did mount", false
}
Here are the React docs on the topic.
As per docs: https://reactjs.org/docs/react-component.html#setstate
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.
Your
// Why won't this one show in the console?
console.log(this.state.foo);
Works and shows initial false value, that is actually expected. It will be updated on next shouldComponentUpdate call.

Firebase data not saving to my react state [duplicate]

This question already has answers here:
Why is setState in reactjs Async instead of Sync?
(8 answers)
Closed 2 years ago.
I have this block of code
class PaintingPage extends Component {
constructor(props){
super(props)
this.state = {
paintings: []
}
this.database = this.database.bind(this);
}
database = () => {
let db = fire.firestore();
db.collection('PaintingProjects')
.get()
.then( snapshot => {
let paintingData = [];
snapshot.forEach( doc => {
const data = doc.data()
paintingData.push(data);
console.log(paintingData, '1st')
this.setState({
paintings: paintingData,
})
console.log(this.paintings, '2nd')
})
})
}
componentWillMount(){
this.database()
}
I'm trying to save my PaintingProjects collection to my state. When I log the paintingData array all of my data is there but after I save it to my state and log my state its comes back as undefinied what am I doing wrong?
Use componentDidMount instead of willMount.
"componentWillMount() is invoked immediately before mounting occurs. It is called before render() , therefore setting state in this method will not trigger a re-render. Avoid introducing any side-effects or subscriptions in this method"
console.log(this.paintings, '2nd') won't log the new state, but undefined, you should use this.state.paintings, but mind that it will log the previous state and not the new one.
You also shouldn't introduce side effects in componentWillMount. ComponentWillMount isn't awaited so your component will render without data at least once the same happens with componentDidMount though. But with componentDidMount, you can ensure that your componenet has support for an empty state.
Sources:
When value is assigned to components state, why console.log prints the previous state?
https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/

TypeError: this.state.posts.map is not a function

I Get some problem when i am use react and laravel-api this my code
import React from 'react'
import axios from 'axios'
class Post extends React.Component{
state = {
posts: []
}
componentDidMount() {
axios.get(`http://192.168.0.42/blog/laravel/blog/public/api/posts/11`)
.then(res => {
const posts = res.data;
this.setState({ posts });
})
}
render() {
return (
<ul>
{ this.state.posts.map(posts => <li>{posts.id}</li>)}
</ul>
)
}
}
export default Post
I Get Error
Image : http://prntscr.com/oug5v3
Text
TypeError: this.state.posts.map is not a function
someone Help
My guess without actually seeing the contents of the posts state member is that you actually retrieve an object from that HTTP request, instead of an array.
So what happens is the first render works well, because an empty array is still an array (thus having the map method), and then, when the request finishes, you replace the posts state variable with an object (not having the map method).
As I pointed out in a comment to your question, you should inspect the actual value of what comes out from that HTTP request.
If you're using the in-browser debugger, you should place a breakpoint on this line:
this.setState({ posts });
then check the value of posts.
If you don't want to use a debugger (however I don't recommend that), you can simply place a console.log(posts) before the line mentioned above.
Tip
If you're developing a REST API, then the http://192.168.0.42/blog/laravel/blog/public/api/posts/11 URL should actually return one post, not a list.
My guess is that you should actually make the request to http://192.168.0.42/blog/laravel/blog/public/api/posts (notice the /11 part gone).

Resources