Loading data from a REST API

  • Tutorial

I want to share another small bike - first of all, to get invaluable advice. Additional examples can be found in the source code for the GitHub fan project .


Almost all pages in the project are wrapped by the Page component:


const MyPage = () => (
  
    Hello World
  
)

To load external data, the Page component has three passed properties (props):


  • The onMounted callback function is called inside the componentDidMount component life cycle method; according to React documentation, it is in this place that it is recommended to download external data.
  • The isLoading flag we pass before loading external data is true, and after this operation is completed, false.
  • We pass the isNotFound flag if loading external data is unsuccessful.

Redux example:


// components/Post/PostViewPage.js
const PostViewPage = ({ post, ...props }) => (
  
)
const mapStateToProps = (state) => ({
  post: state.postView,
  isNotFound: isEmpty(state.postView),
})
const mapDispatchToProps = (dispatch, ownProps) => ({
  onMounted: () => {
    const id = parseInt(ownProps.match.params.id, 10)
    dispatch(actions.read(id))
  }
})
export default connect(mapStateToProps, mapDispatchToProps)(PostViewPage)

Please note that the isLoading flag is not explicitly passed to props, it is bound via mapStateToProps in the Page component (which will be demonstrated later in the text).


If you have questions about expressions:


// деструктуризация и рест-параметры
{ post, ...props }
// спред
{...props}

... then you can refer to the MDN directory:



The side effect actions.read (id) provides redux-thunk:


// ducks/postView.js
const read = id => (dispatch) => {
  // установить флаг state.app.isLoading
  dispatch(appActions.setLoading(true))
  // сбросить значение state.postView
  dispatch(reset())
  // флаг о завершении таймаута
  let isTimeout = false
  // флаг о завершении загрузки
  let isFetch = false
  setTimeout(() => {
    isTimeout = true
    if (isFetch) {
      dispatch(appActions.setLoading(false))
    }
  }, 500) // демонстрировать state.app.isLoading не менее 500 мс
  axios(`/post/${id}`)
    .then(response => {
      const post = response.data
      // записать данные в state.posts
      dispatch(postsActions.setPost(post))
      // записать данные в state.postView
      dispatch(set(post))
    })
    .catch(error => {
      dispatch(appActions.setMainError(error.toString()))
    })
    .then(response => {
      isFetch = true
      if (isTimeout) {
        dispatch(appActions.setLoading(false))
      }
    })
}

When the data loads too fast, an unpleasant visual effect of the flashing download indicator occurs. To avoid this, I added a 500 ms timer and logic on the isTimeout and isFetch flags.


The Page component, if we discard other decorations, provides the process of loading external data:


// components/Page/Page.js
class Page extends React.Component {
  _isMounted = false
  componentDidMount() {
    this._isMounted = true
    const { onMounted } = this.props
    if (onMounted !== void 0) {
      onMounted()
    }
  }
  render() {
    const { isNotFound, isLoading, children } = this.props
    if (this._isMounted && !isLoading && isNotFound) {
      return 
    }
    return (
      
{this._isMounted && !isLoading ? children :
Загрузка...
}
) } } const mapStateToProps = (state, props) => ({ isLoading: state.app.isLoading }) export default connect(mapStateToProps)(Page)

How it works? The first render pass will be executed with the _isMounted flag turned off - displaying the loading indicator. Next, the lifecycle method componentDidMount will be executed, where the _isMounted flag will turn on and the onMounted callback function will start; inside onMounted we cause a side effect (for example, actions.read (id)), where we enable the state.app.isLoading flag, which will cause a new render - the loading indicator will still be displayed. After asynchronously invoking axios (or fetch) inside our side effect, we turn off the state.app.isLoading flag, which will cause a new render - now, instead of displaying the loading indicator, the render of the embedded component (children) will be executed; but if the inclusion of the isNotFound flag works, then instead of render for the nested component (children), the render component will be executed.


Also popular now: