onurhb
onurhb

Reputation: 1181

Redux - Loading indicator while waiting for promise

I have a simple redux application that is displaying articles by requesting data from an api. I want to display "loading" indicator while waiting for promise. How can I implement this?

Upvotes: 5

Views: 1677

Answers (1)

rishat
rishat

Reputation: 8376

Two parties are involved: store and component representation.

In store, per reducer, you should add a prop and call it, say, loading. So the reducer's initial state would look like

// src/reducers/whatevs.js
const initialState = {
  // whatever
  loading: false
};

Then, pass this value into container's props with connect function, like

export default connect(({whatevs}) => ({
  // props you want to use in this container, and
  loading: whatevs.loading
}));

Next, you want to call an action and retrieve a Promise from it, so it's simply

componentWillMount() {
  const promise = requestSomething();

  this.props.dispatch(() => {
    type: LOADING_SOMETHING
  });
  promise.then((smth) => this.props.dispatch(() => {
    type: LOADED_SOMETHING,
    ...smth
  }));
}

So, first, you claim that you requested something, therefore you're loading; then, you claim that the response has been received, so you're not loading anymore. The corresponding reducers would look like

LOADING_SOMETHING: (state) => ({
  ...state,
  loading: true
})

and

LOADED_SOMETHING: (state, {type, ...smth}) => ({
  ...state,
  ...smth,
  loading: false
})

And in the component, simply rely on loading prop to make it render either loading indicator or actual data:

render() {
  const {loading} = this.props;

  if (loading) {
    return (
      <LoadingIndicator />
    );
  } else {
    return (
      <WhateverComponent />
    );
  }
}

Connect function

The connect function from react-redux package wraps a component and allows it to receive props from the application state aka the Store on every Provider component update. It happens reactively, which means top to bottom, every time you dispatch an action. A store is typically a plain-object (or may it be Immutable.js's Map object, or anything) that, conventionally, contains as many properties as you have reducers.

You can check the value of application state using React DevTools. Just open them, highlight the Provider component, and type

$r.props.store.getState()

You wrap every controlled component (may be called container, or view) with connect function to make it receive every update of the state and re-render if a part of the app state the component depends on changes.

For example, let's pretend the app state is

{
  authentication: {
    authenticated: null,
    username: null
  },
  photos: {
    items: [],
    favorites: []
  }
}

and you have a component

export default class Photos extends Component {
  render() {
    const {
      photoList
    } = this.props;

    return (
      <div>{this.renderPhotoList(photoList)}</div>
    );
  }
}

And what you really want is that photoList prop of this component would refer to photos.items property of the app state. Then you connect app state's photos.items property to component's photoList prop:

class Photos extends Component {
  render() {
    const {
      photoList
    } = this.props;

    return (
      <div>{doSomethingMeaningful(photoList)}</div>
    );
  }
}

export default connect((state) => ({
  photoList: state.photos.items // remember $r.props.store.getState()?
}))(Photos);

or, with some destructuring assignment,

export default connect(({photos}) => ({
  photoList: photos.items
}))(Photos);

The connect function accepts either one or two arguments, in this particular example there's only one argument that binds component's props to app state's properties.

There could be the second parameter, which is a function of dispatch:

import {
  fetchPhotos
} from 'actions/photos';

export default connect(({photos}) => ({
  photoList: photos.items
}), {
  fetchPhotos
})(Photos);

Which is a different topic.

Need some deeper explanation of how connect works? See the ref.

Upvotes: 4

Related Questions