API calls in componentDidUpdate and componentWillReceiveProps - reactjs

I am making API calls in the componentDidMount() method as recommended by React.
Inside the component I have a <Link> which points to the same component with different URL params.
However when I click on the <Link> the componentDidMount() is not called and I can't make API calls with updated URL params.
So I tried putting the API calls inside componentDidUpdate() and componentWillReceiveProps()
This results in stack overflow as the API calls set state triggering componentDidUpdate() and componentWillReceiveProps() which again make the API calls leading to infinite loop.
How do I overcome this?
What is the recommended solution which fits into React philosophy?

Inside the component I have a which points to the same component with different URL params
This means the component is not unmounted/remounted as it's the same component, so componentDidMount will not be called.
API calls set state triggering componentDidUpdate() and componentWillReceiveProps() which again make the API calls leading to infinite loop
You should make the API call in componentDidUpdate (componentWillReceiveProps will be deprecated and async calls should not be executed here anyway), but check if it's already been executed by checking if the data returned by the API call is there or changed, to avoid infinite re-rendering:
componentDidUpdate(prevProps, prevState) {
if (this.state.data.length === 0) { // or compare with `prevState`
// make the api call
}
}

The componentDidUpdate() receives previous props by using it you can if the props are updated or not... if updated then call API. In my project I am doing it as follows:
componentDidUpdate(prevProps) {
if (prevProps.match.params.category !== this.props.match.params.category) {
this.getBlogList(this.props.match.params.category);
}
}

remove componentDidUpdate and use only componentWillReceiveProps(){ <--- and call api here}

Related

How to use setState from .then in another function as a parameter in ReactJS

I am new to ReactJS. I have:
this.service
.getData({})
.then((response: any) => {
this.setState({
id: response.id
});
})
getStats(this.state.id) {
...
}
I am not able to get the value for id outside .then. I want to use id in multiple functions. How do I get the value in state so that I don't have to call the function several times? Please suggest.
on class base components, given the async nature of state update you use more often componentDidUpdate lifecycle. this lifecycle provides prevProps, prevState, snapshot as arguments, which are usefull to you do some validation with current state before triggering your actions.
fwiw, this.setState can receive a function as second parameter also. that function will be executed once state is updated, enabling you to have access to the most current state.
Once the state is updated, it will call the render method again. In the next render call you will have the state updated.
Calls to setState are asynchronous - don’t rely on this.state to reflect the new value immediately after calling setState.
So in order to use the state after the state is set, either you can use some effects or component life cycle to get the updated state.

Fetch data on Async ComponentDidMount before rendering

In my componentWillMount function, I have an async function getResults that fetches data from a server. I want to pass the response from getResults to the same component on initial load, but I noticed that my component renders before waiting for the data to come back from getResults. How do I make the component wait for the response before having it render? I am using Redux to handle my state.
async componentWillMount() {
const response = await getResults(this.props.category)
}
Async/Await only makes your code look synchronous.
async componentWillMount() {
const response = await getResults(this.props.category)
this.setState({
// use response to set your state.
});
}
Set state is what causes the re-render.
You then use the state to determine if you are in a loading state. If so it may be good to show the user. You also want to handle the default data in the first render. Once you retrieve your response you set the data to the second state.
It's idiomatic React to render your component asynchronously. You'll fetch your data, and set your state accordingly... with a new state, your component will re-render. A common practice it to render the component with a spinner, and when the state is set, re-render with the actual data. At the very least, you can render null until your async request completes and sets your state.
componentWillMount() is invoked just before mounting occurs. It is
called before render(), therefore calling setState() synchronously in
this method will not trigger an extra rendering. Generally, we
recommend using the constructor() instead.
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
This is the only lifecycle hook called on server rendering.
source
You can use componentDidMount() and make the rendering conditional and waiting for the result from the API, and then render it with the result.

`componentWillReceiveProps` explanation

I recently wanted to upgrade my knowledge of React, so I started from the component lifecycle methods. The first thing that got me curious, is this componentWillReceiveProps. So, the docs are saying that it's fired when component is receiving new (not necessarily updated) props. Inside that method we can compare them and save into the state if needed.
My question is: Why do we need that method, if changes in props of that component (inside parent render) will trigger the re-render of this child component?
One common use case are state (this.state) updates that may be necessary in response to the updated props.
Since you should not try to update the component's state via this.setState() in the render function, this needs to happen in componentWillReceiveProps.
Additionally, if some prop is used as a parameter to some fetch function you should watch this prop in componentWillReceiveProps to re-fetch data using the new parameter.
Usually componentDidMount is used as a place where you trigger a method to fetch some data. But if your container, for example, UserData is not unmounted and you change userId prop, the container needs to fetch data of a user for corresponding userId.
class UserData extends Component {
componentDidMount() {
this.props.getUser(this.props.userId);
}
componentWillReceiveProps(nextProps) {
if (this.props.userId !== nextProps.userid) {
this.props.getUser(nextProps.userId);
}
}
render() {
if (this.props.loading) {
return <div>Loading...</div>
}
return <div>{this.user.firstName}</div>
}
}
It is not a full working example. Let's imagine that getUser dispatch Redux action and Redux assign to the component user, loading and getUser props.
It 'serves' as an opportunity to react to the incoming props to set the state of your application before render. If your call setState after render you will re-render infinitely and that's why you're not allowed to do that, so you can use componentWillReceiveProps instead.
But... you are beyond CORRECT in your confusion, so correct in fact that they are deprecating it and other Will-lifecycle hooks Discussion Deprecation.
There are other ways to accomplish what you want to do without most of those Will-lifecycle methods, one way being don't call setState after render, just use the new incoming props directly in render (or wherever) to create the stateful value you need and then just use the new value directly, you can then later set state to keep a reference for the next iteration ex: this.state.someState = someValue, this will accomplish everything and not re-render the component in an infinite loop.
Use this as an opportunity to react to a prop transition before render() is called by updating the state using this.setState(). The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render.
Look at this article
the componentWillReceiveProps will always receive as param "NxtProps", componentWillReceiveProps is called after render().
some people use this method use this to compare nxtProps and this.props to check, if something should happen before the component call render, and to some validations.
check the react's documentation to know more about react lifecycle!
hope this could help you!
changes in props of that component (inside parent render) will trigger the re-render of this child component
You are absolutely right. You only need to use this method if you need to react to those changes. For instance, you might have a piece of state in a child component that is calculated using multiple props.
Small Example:
class Test extends Component {
state = {
modified: "blank"
};
componentDidMount(){
this.setState({
modified: `${this.props.myProp} isModified`
});
}
componentWillReceiveProps(nextProps) {
this.setState({
modified: `${nextProps.myProp} isModified`
});
}
render() {
return <div className="displayed">{this.state.modified}</div>
}
}
In this example, componentDidMount sets the state using this.props. When this component receives new props, without componentWillReceiveProps, this.state.modified would never be updated again.
Of course, you could just do {this.props.myProp + "IsModified"} in the render method, but componentWillReceiveProps is useful when you need to update this.state on prop changes.

Access props.location object from redux-saga

My end goal is to access this.props.location.pathname inside redux-saga when making API calls. Here's my current working solution, albeit with react raising an error. I'm using mxstbr/react-boilerplate-brand as my codebase.
In my wrapping component, App, I have the following line in my render method.
render() {
this.props.onUpdateLocation(this.props.location)
}
In my mapDispatchToProps I have following. Basically I'm just saving this.props.location into the React store:
function mapDispatchToProps(dispatch) {
return {
onUpdateLocation: (location) => {
dispatch(updateLocation(location));
},
dispatch,
};
}
Inside my redux-saga I access the location from state and just use it as need be; however, here's the error React raises.
warning.js:44 Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
I can't put it in componentWillMount because that only gets fired once when the app starts, and I can't put it in componentWillUpdate because this.props.location gets updated in the render method. I can't put it in componentDidUpdate because that's too late.
Am I just missing some easy obvious way to access the react-router location inside my redux-saga?
if you have <Route path='profile' component={ Profile } />
the Profile component can access the react-router props in the second argument ownProps of:
mapStateToProps(state, [ownProps]) and
mapDispatchToProps(dispatch, [ownProps])
TL;DR:
export class App extends React.Component {
componentWillMount() {
this.props.onUpdateLocation(this.props.location.pathname);
}
componentWillReceiveProps(nextProps) {
this.props.onUpdateLocation(nextProps.location.pathname);
}
render() {
// Render stuff here
}
}
function mapDispatchToProps(dispatch) {
return {
onUpdateLocation: (location) => {
dispatch(updateLocation(location));
},
dispatch,
};
}
My reducer receives the action and updates location in the state. Now I can access the current pathname using a selector that grabs location from the state.
Long Answer:
IGL's answer is pretty good info, especially the wildcard naming info that you can use in routes and is returned by the ownProps param of mapDispatchToProps. Here is how I solved the problem...
Initially I thought the warning was about be accessing this.props.location or something else; however, it was a much simpler issue. React doesn't like it when you call a function without it being because of an action like a click. The warning message, which suggest a solution, led me on the right track.
First to see what fired when and where, I placed each of the React lifecycle functions in my code, console logging when they were hit. componentWillMount, componentDidMount, componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, componentDidUpdate, componentWillUnmount.
I discovered that componentWillMount is fired on initial page load, and componentWillReceiveProps is fired on each navigation. With that in mind, I console logged this.props.location and discovered that componentWillReceiveProps still had the old location; however, it's takes one param, nextProps that has the new location. so, nextProps.location is what I want. I placed this in my App container, which takes other containers as its children, and now I can access the current location in my sagas where I use it to make API calls.

Where can I redirect to a different page in React-router 1.0

render(){
if (!this.readyForStep()){
this.props.history.pushState(null, `step2`);
}
// Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
}
So I can't redirect in render()... where in a component lifecycle can/should I?
Depending on the specific semantics you want redirecting before or after render, you can make this check in any of componentDidMount, componentWillReceiveProps, or componentDidUpdate.
Synchronous transitions lead to state updates in the <Router> component, so you can't make them in any place where you normally couldn't call setState.

Resources