9
votes

I am a newb at React/Redux. Any help would be great. I have a component that makes an API call on ComponentDidMount to get data and then update my Redux store. That component also uses connect to get the Redux state and pass it as props down to a dumb component.

componentDidMount() {
  this.props.dispatch(fetchSite()) //this triggers the api call and updates the redux store.
}

return ( 
  <div>
    <Child
        myprop={this.props.name}
    />
  </div>

export default connect((state) => ({name: state.name}))(Container);

Because componentDidMount triggers after the component renders, it will render, run the api, and then re-render because the api changed the redux state. This makes it so the component renders twice. The first time without any data, and then the second with good data from the api.

Is there a better way?

Since my Child component renders different things based on the props it is passed it briefly shows something different the first time it is rendered. Then when the api updates the store and the components re-render it shows something different again. It is a flicker of two different states.

Is there a way to make it only render once with the correct API data?

3

3 Answers

7
votes

Let's think about what's going on here.

The page and all its assets are loaded in the browser, the scripts are run and all the React components are rendered. Only after they're rendered do you fetch your data. So of course there will be an initial state followed by a loaded state.

So how do you deal with the state of your app before the data comes in?

You've already expressed your understandable dislike of presenting the app with blank data. You could delay the rendering of your app until the data arrives. But this would result in a horrible user experience. They could be staring at a blank page for potentially several seconds before anything happens.

Or there's the old loader trick. You might have something like isLoading: true in your initial state. That would correspond to some visual loading indicator (traditionally a spinning GIF image) in your React component. This is surely the best option if you must load in your data via AJAX.

A better way

But here's the thing: you don't need to use AJAX for your initial state. You can avoid this delay entirely by appending your data to the page.

<!-- place this BEFORE your Redux/React scripts -->
<script>
    // This object must match the shape of your Redux reducer state
    var __INITIAL_DATA__ = {
        todos: [{
            name: "Gather requirements",
            done: false
        }, {
            name: "Write code",
            done: false
        }]
    };
</script>

Now all you need do is "hydrate" your store upon creation.

const store = createStore(rootReducer, window.__INITIAL_DATA__);
0
votes

A better way is for fetchSite to return some mock info immediately indicating your component that its not done yet. Also you should dispatch this action in componentWillMount. The render should simply return null if fetchSite returned the loading indicator data. When fetchSite actually finishes, it will trigger the store update and the component will get the actual data in the next fetchSite call in componentWillMount.

0
votes

Another case in my side that, I was loading my component with React Lazy. In the loaded component componentDidMount / useEffect were called multiple times when I used async dispatch with redux. So when I removed React Lazy the problem is solved.