How to setState with a promise before render? - reactjs

I have promise in props: this.props.getProfile() and i want to set response value in promise into state before render()
i have tried with UNSAFE_componentWillMount and getDerivedStateFromProps but it always response a promise pending.
Here is my try with UNSAFE_componentWillMount:
UNSAFE_componentWillMount(){
this.setState({profile: this.getProfile()})
}
getProfile=()=>{
return this.props.getProfile()
.then(res =>{
if (res.type === 'ERROR_MESSAGE') {
return null;
}
return res.payload
});
}
Here is my try with getDerivedStateFromProps:
static getDerivedStateFromProps (props, state){
let a = props.getProfile()
.then(res =>{
if (res.type === 'ERROR_MESSAGE') {
return null;
}
return res.payload
});
console.log(a);
if(a.profile !== state.profile)
return {profile: a};
}

you might think that at a given promise once you return something inside then method you would get that value right away back, when actually you are returning other promise. to solve that you have 2 standard approaches, use async/await or chain another then to the promise.
getDerivedStateFromProps is not meant for data fetch as you can see a good discussion here
async\await
async componentDidMount() {
const profile = await this.props.getProfile()
this.setState({ profile })
}
promise chaining
componentDidMount() {
this.props
.getProfile()
.then(profile => this.setState({ profile }))
}

Related

How to get response from get request (React)?

I am new guy in react, so how to put value that i receive from jsonHandler function into render return statement?
I've tried a lot, but i always have the same result. Console.log(jsonData) in jsonHandler function returns value that i need, but function jsonHandler returns promise and idk how to handle it. It doesn't matter to use axios.get or fetch().
async function jsonHandler () {
let jsonData;
const url = "http://localhost/index.php";
await axios.get(url)
.then(({data}) => {
jsonData = data.data;
});
console.log(jsonData); //returns that i need
return jsonData;
}
class Menu extends Component {
...
render() {
console.log(jsonHandler()); //returns promise
return <div className="MenuWrap">{this.mainHandler(Here Must Be Data From jsonHandler)}</div>;
}
}
export default Menu;
You can do it this way. Make use of states for reactive updates. I referred to https://stackoverflow.com/a/45744345/13965360 for the setStateAsync function, which sets the state value asynchronously and means it will wait till the API call is done. You can use try and catch with await if you are using async instead of then and catch blocks.
const url = "http://localhost/index.php";
class Menu extends Component {
state = {
jsonData: {},
};
//creating a function that sets the state asynchronously
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve);
});
}
// Fetch the data
async jsonHandler() {
try {
const response = await axios.get(url);
this.setStateAsync({ jsonData: response.data });
console.log(this.state.jsonData); //returns that i need
} catch (error) {
throw new Error(error);
}
}
render() {
return (
<div className="MenuWrap">
{Object.keys(this.state.jsonData).length &&
JSON.stringify(this.state.jsonData)}
</div>
);
}
}
export default Menu;
If you want to do the API call instantly once the component renders, you need to put it in the componentDidMount lifecycle.
Like, async componentDidMount() { await this.jsonHandler(); }
Or, if you want to make the API call upon clicking a button, you need to bind the method to the listener like <button onClick={this.jsonHandler}>.

React and promise issues with fetch method

i'm new in react and i've got some issues with asynchronous fetch data :
i want to fetch github users
function fetchUser(username) {
return fetch(`https://api.github.com/users/${username}`)
.then(response => response.json())
.then(data => data)
}
export function getUserData(username) {
const object = {
profile: fetchUser(username),
}
console.log(object)
return object
}
and this is my method in my component
componentDidMount() {
getUserData(this.props.playerOne)
}
but this is what i got in my console
{profile: Promise}
i'm sure that i dont understand well this promise so could you help me to have not a Promise in my object but the data i'm fetching ? (if i log data in my fetch i got what i want)
You can make this function async and wait for the promise resolution.
export async function getUserData(username) {
const object = {
profile: await fetchUser(username),
}
console.log(object)
return object
}
then in your componentDidMount
componentDidMount() {
getUserData(this.props.playerOne)
.then((profile) => { this.setState({ profile }) })
}

Race condition in React setState and Promise

In my Context I have a LocalFunction that returns a promise.
LocalFunction: () => Promise<void>
LocalFunction: () => {
return externalCall.getBooks().then((books) => {
this.setState({ Books: books })
})
}
I can call this function in another component based on the updated Books object in the Context state like:
this.props.LocalFunction().then(() => {
// do something with this.props.Context.Books
})
But I know React updates states in batches. So could I run into a race condition when calling LocalFunction without the Books state being updated with the new books?
I know a way to avoid it is to wrap LocalFunction in a new Promise and resolve it in this.setState({ Books: books }, resolve), but I wanna avoid doing that if possible.
How about to use async/await?
LocalFunction: async (needUpdate = false) => {
const result = await externalCall.getBooks();
if(needUpdate){
this.setState({ Books: result })
}
return result;
}
this.props.LocalFunction().then((res) => {
console.log(res)
// do something with this.props.Context.Books
})
When you need to update state
LocalFunction(true)

React accessing state before ComponentDidMount

When I try to access a state variable which is set in ComponentDidMount, react throws an undefined error. This is because I believe when I'm calling the fetch api and setState in ComponentDidMount, the value isn't ready yet (async stuff). Is there a proper way to either delay the render until the setState call is done or some other way to get the state updated fully before render is called?
I think the code below will give you a basic idea how fetch data and render work.
class App extends Component {
state = {
data:{},
loading:true,
error:null,
}
componentDidMount = () => {
fetch('https://example.com/api/article')
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({
data:json,
loading:false,
})
.catch(error => {
this.setState({
error,
loading:false,
})
});
});
}
render() {
const {data,error,loading} = this.state;
if(loading){
return "Loading ..."
}
if(error){
return "Something went wrong."
}
return 'your actual render component or data';
}
}
export default App;

How to use setState in an asynchronous function

I am running this code:
.then((url) => {
if (url == null || undefined) {
return this.props.image;
} else {
const { image } = this.props;
//entryUpdate is an action creator in redux.
this.props.entryUpdate({ prop: 'image', value: url })
.then(() => {
this.setState({ loading: false });
});
but I get the following error:
How do I format setState() inside an asynchronous function that's called after an action creator?
Any help would be much appreciated!
In order for this to work, your action creator this.props.entryUpdate would need to return a promise for the async work it's doing. Looking at the error message, that does currently not appear to be the case.
You also need to be aware that calling setState() in the asynchronous callback can lead to errors when the component has already unmounted when the promise resolves.
Generally a better way is probably to use componentWillReceiveProps to wait for the new value to flow into the component and trigger setState then.
I placed the .then() function inside of the the if statement. But it should be like this:
.then((url) => {
if (url == null || undefined) {
return this.props.image;
} else {
const { image } = this.props;
this.props.entryUpdate({ prop: 'image', value: url })
}
})
.then(() => {
this.setState({ loading: false });
});

Resources