Reputation: 532
Say I have a redux module that looks like this:
import fetch from 'isomorphic-fetch';
// CONSTANTS
const LOAD = 'LOAD';
const LOAD_SUCCESS = 'LOAD_SUCCESS';
// REDUCER
const initialState = { loading: false, data: [] };
export default function reducer(state = initialState, action) {
switch (action.type) {
case LOAD: return { ...state, loading: true };
case LOAD_SUCCESS: return { ...state, loading: false, data: action.data };
default: return state;
}
}
// ACTION CREATORS
function requestLocations() {
return { type: LOAD };
}
function receiveLocations(json) {
return { type: LOAD_SUCCESS, data: json };
}
export function fetchLocations() {
return function (dispatch) {
dispatch(requestLocations());
return fetch('http://myurl')
.then(response => response.json())
.then(json => dispatch(receiveLocations(json)));
};
}
I'm struggling with the loading
state on the first render if I make the async call in componentWillMount. Imagine my component looks like this (simplified for brevity):
export default class LocationContainer extends React.Component {
componentWillMount() {
fetchLocations(); // already bound and injected via connect.
}
render() {
const { loading, data } = this.props; // injected via connect from reducer above.
if (loading) {
return <Spinner />;
} else {
return <LocationExplorer locations={ data } />;
}
}
}
The problem I run into is that on the first render of LocationContainer
, loading
is false and data
hasn't been fetched yet. In componentWillMount
, the LOAD
action is fired and a props change of loading
being set to true is queued up to happen on the subsequent render. In the meantime, during my first render, LocationExplorer
is rendered when I really wanted Spinner
instead because loading
is still false. I'm wondering how you deal with this without setting a firstRender = true
state variable hack.
Upvotes: 5
Views: 6758
Reputation: 532
Besides the accepted answer, one other solution we've been using to great success is adding a complete
boolean flag to the reducer and we mark it true if a response has come back at some point. This makes things a bit more explicit in the event the response actually comes back with an empty array. The complete
flag lets me know that the response is from the server and not just my initial state.
if(loading && !complete) {
return <Spinner />;
} else {
return <Component data={data} />;
}
I figured I would add this just in case it's helpful to others.
Upvotes: 0
Reputation: 13818
One option could be extend your loading
condition with your data
initial state:
const initialState = { loading: false, data: [] };
When you are loading
and your data
is empty it means you are in this exact state of waiting for new data to come:
if (loading && data.length === 0) {
return <Spinner />;
} else {
Also, I usually put my asynchronous calls in componentDidMount
instead of componentWillMount
.
Upvotes: 7
Reputation: 24815
Don't make the request in componentWillMount
. Instead, do it before you mount the component. e.g.
store.dispatch(fetchLocations());
ReactDOM.render(<App store={store} />, mountpoint);
Upvotes: 0