When should React call AJAX request after props change? - reactjs

I used to call AJAX after props change by ComponentWillReceiveProps()
componentWillReceiveProps(nextProps) {
if(nextProps.foo !== this.props.foo){
//fetch & call this.setState() asynchronously
}
}
After React 16.3 ComponentWillReceiveProps() is going to be deprecated in the future. Instead of ComponentWillReceiveProps() there is a new function getDerivedStateFromProps, but I can't update state async.
static getDerivedStateFromProps(nextProps, prevState) {
// I can't get access to this.props but only state
if(nextProps.foo !== this.props.foo){
//fetch & call this.setState() asynchronously
}
// I can only return a state but update it after an AJAX request
return {}
}
What's the best practice to do it.

You should not use getDerivedStateFromProps lifecycle for making api calls. Instead, use componentDidUpdate to make api call and once you get the api response do this.setState. Also as pointed in another answer, you cannot use this in static method.
componentDidUpdate(prevProps) {
if (this.props.myData !== prevProps.myData) {
this.callMyApi();
}
}
callMyApi() {
fetch("/api")
.then(response => {
this.setState({ ... });
})
}
If you are writing new component, you can also consider to write a Functional component and use useState and useEffect to trigger api call when a propis updated.
Like this:
...
const {page} = this.props;
const [images, setImages] = useState([]);
useEffect(() => {
fetch(`/myfavApi`)
.then(data => data.json())
.then(images => {
setImages(images.concat(images));
});
}, [page]); // provide page(prop) as dependency.
...

The best place for do asynchronous calls is componentDidUpdate(prevProps, prevState, snapshot) {}. getDerivedStateFromProps is static method so it hasn’t access to component instance (hasn’t access to this)

Related

Component did update works only after second click

My code adds a new item in the firebase databse when i click a button, then i want the list of objects in my page to automatically update, because i don't want to manualy reload the page. So i came up with this code
constructor(props){
super(props);
this.state = {
groups: [],
code:'',
name:'',
update:true
}
}
async fetchGroups (id){
fetchGroupsFirebase(id).then((res) => {this.setState({groups:res})})
};
async componentDidUpdate(prevProps,prevState){
if(this.state.update !== prevState.update){
await this.fetchGroups(this.props.user.id);
}
}
handleCreateSubmit = async event => {
event.preventDefault();
const{name} = this.state;
try{
firestore.collection("groups").add({
title:name,
owner:this.props.user.id
})
.then((ref) => {
firestore.collection("user-group").add({
idGroup:ref.id,
idUser:this.props.user.id
});
});
this.setState({update: !this.state.update});
}catch(error){
console.error(error);
}
What i was thinking, after i add the new item in firebase, i change the state.update variable, which triggers componentDidUpdate, which calls the new fetching.
I tried calling the fetchGroups function in the submit function, but that didn't work either.
What am i doing wrong and how could i fix it?
ComponentDidUpdate will not be called on initial render. You can either additionally use componentDidMount or replace the class component with a functional component and use the hook useEffect instead.
Regarding useEffect, this could be your effect:
useEffect(() => {
await this.fetchGroups(this.props.user.id);
}, [update]);
Since you can't use useEffect in class components so you would need to rewrite it as functional and replace your this.state with useState.

storing the response using useEffect to avoid re-rendering using react hooks

I am using React hooks and trying to figure out, how should I store the response of the api call response.data._embedded.assets in a state variable.
Using setAssets(response.data._embedded.assets); doesn't work because of re-rendering. So I decided to use useEffect as shown in the code below but this violates the rules of react
hooks -Hooks can only be called inside of the body of a function component. I understand that useEffect should be defined outside as per react hooks but then how would I store the response in a state variable ? Please advise.
const [selectedTabIndex, setselectedTabIndex] = useState(0);
const [assets,setAssets] = useState([]);
let companyCategory;
axios
.get(url, {
params: {
assetCategoryId: selectedTabIndex
}
}).then(response => {
if (sessionStorage.getItem('companyCategory') !== null) {
companyCategory = JSON.parse(sessionStorage.getItem('companyCategory') )
}
console.log("Inside response of web api call");
console.log(response.data._embedded.assets);
useEffect(() => {
// Should not ever set state during rendering, so do this in useEffect instead.
setAssets(response.data._embedded.assets);
}, []);
//setAssets(response.data._embedded.assets);
}).catch(err => console.log(err));
In a class component, the above state variable declaration would be like this inside the response:
this.setState({
companyCategory: companyCategory,
assets: response.data._embedded.assets
})
I would put the whole get request in useEffect.
const [selectedTabIndex, setselectedTabIndex] = useState(0);
const [assets,setAssets] = useState([]);
useEffect(() => {
// Should not ever set state during rendering, so do this in useEffect instead.
let companyCategory;
axios
.get(url, {
params: {
assetCategoryId: selectedTabIndex
}
}).then(response => {
if (sessionStorage.getItem('companyCategory') !== null) {
companyCategory = JSON.parse(sessionStorage.getItem('companyCategory') )
}
console.log("Inside response of web api call");
console.log(response.data._embedded.assets);
setAssets(response.data._embedded.assets);
}).catch(err => console.log(err));
}, []);
If a component doesn't need to render when it changes, don't put it in state. You can have a module-scope variable in your component and use that.
With class components, you can also put it on this
why do you fetch data if you don't want to use that,
also we can't use react hooks inside functions
Call Hooks from React function components
Call Hooks from custom Hooks

React + Redux: proper way to handle after-dispatch logic

I have a component with some internal state (e.g. isLoading) which has access to redux data. In this component I'd like to dispatch a thunk action (api request) resulting in redux data change. After the thunk is completed I need to change the state of my component. As I see it, there are two ways to do so:
Use the promise return by the thunk and do all I need there, e.g.
handleSaveClick = (id) => {
const { onSave } = this.props;
this.setState({ isLoading: true });
onSave(id).then(() => this.setState({ isLoading: false }));
};
Pass a callback to the thunk and fire it from the thunk itself, e.g.
handleSaveClick = (id) => {
const { onSave } = this.props;
this.setState({ isLoading: true });
onSave(id, this.onSaveSuccess);
};
Which one is the correct way to do so?
The safer way is to use the promise implementation, as you'll be sure that function will only run after the promise has been resolved. The second implementation has no inherent flaws, but if anything in your thunk is async, then it will not work correctly since it'll run once the code is reached, not when the code above it finishes executing. When handling anything that can be async (server requests/loading data/submitting data), it's always safer to use Promise implementations.
Probably the best practice for updating component level state or running a callback function based on changing redux state (or any props/state changes for that matter) is to use componentDidUpdate or useEffect:
componentDidUpdate(prevProps, prevState){
if(prevProps.someReduxState !== this.props.someReduxState && this.state.isLoading){
setState({isLoading:false})
}
}
With useEffect:
useEffect(()=>{
if(!props.someReduxState){
setLoading(true)
} else {
setLoading(false)
}
},[props.someReduxState])
However, I might recommend a different approach (depending on the goal especially on initial data fetching) that manages the loading of state within redux:
Initialize your redux state with a loading value instead:
export default someReduxState = (state = {notLoaded:true}, action) => {
switch (action.type) {
case actions.FETCH_SOME_REDUX_STATE:
return action.payload;
default:
return state;
}
}
then in your component you can check:
if (this.props.someReduxState.notLoaded ){
// do something or return loading components
} else {
// do something else or return loaded components
}

When using componentDidUpdate() how to avoid infinite loop when you're state is an array of objects?

I'm creating a react-native app and I need one of my components to use a axios get request when I do an action on another component. But the problem is that my component that I need an axios get request from is not being passed any props and the current state and new state is an array of 20+ objects with each at least 10 key value pairs. So I would need a component did update with a good if statement to not go into an infinite loop. I can't do an if statement with prevState to compare with current state because there is only a minor change happening in state. So I need to know how to stop the component Did Update from having an infinite loop.
state = {
favouriteData: []
}
componentDidMount () {
this.getFavouriteData()
}
componentDidUpdate (prevProps, prevState) {
if (this.state.favouriteData !== prevState.favouriteData){
this.getFavouriteData()
}
}
getFavouriteData = () => {
axios.get('http://5f46425d.ngrok.io')`enter code here`
.then(response => {
const data = response.data.filter(item => item.favourite === true)
this.setState({
favouriteData: data
})
})
}
The issue is that you are trying to compare 2 object references by doing the following. It will always return since the references are always different.
if (this.state.favouriteData !== prevState.favouriteData) {...}
To make life easier, we can use Lodash(_.isEqual) to deal with deep comparison of objects.
state = {
favouriteData: []
}
componentDidMount () {
this.getFavouriteData()
}
componentDidUpdate (prevProps, prevState) {
this.getFavouriteData(prevState.favouriteData)
}
getFavouriteData = (prevData) => {
axios.get('http://5f46425d.ngrok.io')
.then(response => {
const data = response.data.filter(item => item.favourite === true);
// compare favouriteData and make setState conditional
if (!prevState || !_.isEqual(prevData, data)) {
this.setState({
favouriteData: data
})
}
})
}
You should use react-redux to avoid this kind of issues. Assuming you are not using flux architecture, you can pass this.getFavouriteData() as props to the other component like:
<YourComponent triggerFavouriteData = {this.getFavouriteData}/>

ReactJs: Fetch, transform data with function, then setState

I have managed to fetch data from an API successfully. Data transformation of JSON format works too, but i'm having trouble integrating it to "componentDidMount" to set state with a transformed JSON format. I'm getting an undefined state when i console.log(this.state.races).
I'm also getting this error message:
Can't call setState (or forceUpdate) on an unmounted component.
class Races extends Component {
constructor(props) {
super(props);
this.state = {
races: []};
this.processResults = this.processResults.bind(this);
}
componentDidMount(){
fetch(RACE_SERVICE_URL)
.then(results => results.json())
.then(this.processResults)
}
processResults(data) {
const raceId_arr = data.map(d => d.raceId);
const season_arr = data.map(d => d.season);
const raceName_arr = data.map(d => d.raceName);
const url_arr = data.map(d => d.url);
const data_mapped = {'raceId': raceId_arr, 'season': season_arr, 'raceName': raceName_arr, 'url': url_arr};
this.setState({races:data_mapped});
console.log(data_mapped);
console.log(this.state.races);
}
render() {
const title = 'Race Tracks';
return (
<div>
<h2>{title}</h2>
<RacesViz data= {this.state.races.raceId} />
</div>
);
}
}
export default Races;
I have also tried:
.then(data => this.processResults(data))
What console.log(data_mapped) prints:
{raceId:[1, 2, 3]
raceName:["AGP", "BGP", "CGP"]
season: [2018, 2018, 2018]
url: ["http://en.wikipedia.org/wiki/AGP", "http://en.wikipedia.org/wiki/BGP", "http://en.wikipedia.org/wiki/CGP"]}
setState is async so you can't get immediate result with console.log like you did. Use a callback function instead:
this.setState({races:data_mapped}, () => console.log(this.state.races));
Or you can console.log your state in your render method.
Quote from official docs:
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
Important!
This makes reading this.state right after calling setState() a potential pitfall.
So you will not get state immediately after setState. You have 2 ways to solve it.
1) You should check in componentDidUpdate hook.
componentDidUpdate(prevProps, prevState) {
console.log(this.state.races);//your data updated here.
}
You can see here to use properly.
2) Or you use callback in setState like this setState(updater, callback):
this.setState({races:data_mapped}, () => {
console.log(this.state.races)//your data updated here.
})

Resources