React-starter-kit: Question: How to set preloader when using async/await fetch in router?

Created on 28 Jan 2017  路  4Comments  路  Source: kriasoft/react-starter-kit

Hey guys, I have some trouble with set state for preloader.

I need to set preloader before fetching data and after that remove preloader.

Here is simplified example of my code:

import React from 'react';
import Channel from './Channel';
import fetch from '../../core/fetch';
import Layout from '../../components/Layout';

export default {
    path: '/channel/:cid',

    async action(context) {
        // Set preloader state as TRUE
        let preloader = true;

        const
            channelReq = await fetch(api.getChannel(urlCID), { method: 'get' }),
            channel    = await channelReq.json();

        return {
            title: 'Title',
            component: <Layout><Channel preloader={preloader} /></Layout>
        };
    }
};

Preloader component should be into Channel component and appear when prop preloader === true.

This code works well after first loading. When I try to change page, with similar URL (/channel/asdasdasd), this code don't work because data always loaded synchronously for SSR (cause async/await using).

How I can fix it and save server rendering?

question

Most helpful comment

By default, when page transition is requested by user we wait until all the data necessary to display the page will be loaded and only after that we can render the page and this works similarly to the native browser behavior.

Meanwhile, you can implement lazy loading for all components or data on the page.

Basic example:

class LazyArticles extends React.Component {
  constructor(props, state) {
    super(props, state);
    this.state = { articles: null };
  }
  componentDidMount() {
    fetch('/api/articles')
      .then(resp => resp.json())
      .then(data => this.setState({ articles: data.articles }));
  }
  render() {
    if (!this.state.articles) {
      return <div>Loading Articles...</div>
    }
    return <ul>{this.props.articles.map(a => <li key={a.id}>{a.title}</li>)}</ul>
    );
  }
}

Redux example:

import React from 'react';
import { connect } from 'react-redux';

class Articles extends React.Component {
  render() {
    if (this.props.isFetching) {
      return <div>Fetching Articles...</div>
    }
    if (!this.props.articles.length) {
      return <div>No Articles Found</div>
    }
    return <ul>{this.props.articles.map(a => <li key={a.id}>{a.title}</li>)}</ul>
  }
}

const ConnectedArticles = connect()(Articles);

const route = {
  path: '/articles',
  action({ store }) {
    store.dispatch(fetchArticlesAction()); // trigger data lazy loading (no await here)
    return {
      title: 'Articles',
      component: <Layout><ConnectedArticles /></Layout>,
    };
  }
};

const ARTICLES_FETCH = 'ARTICLES_FETCH';

function fetchArticlesAction() {
  return (dispatch) => {
    dispatch({ type: ARTICLES_FETCH });
    return fetch('/api/articles')
      .then(resp => resp.json())
      .then(payload => dispatch({ type: ARTICLES_FETCH, error: false, payload }))
      .catch(error => dispatch({ type: ARTICLES_FETCH, error: true, payload: error }));
  }
}

function articlesReducer(state, action) {
  switch (action) {
    case ARTICLES_FETCH:
      return {
        isFetching: action.error === undefined,
        articles: action.error === false ? action.payload : state.articles,
      };
    default:
      return state;
  }
}

All 4 comments

Can you look at #949 and #1033, is there what you are looking for?

Hey @frenzzy, thank you for answer, but there is not what I am try to implement.
Most important thing is show/hide specific preloader in any components via passed props.

In your examples I should use global preloader and add changes directly to DOM. So this is not what I am try to find.

By default, when page transition is requested by user we wait until all the data necessary to display the page will be loaded and only after that we can render the page and this works similarly to the native browser behavior.

Meanwhile, you can implement lazy loading for all components or data on the page.

Basic example:

class LazyArticles extends React.Component {
  constructor(props, state) {
    super(props, state);
    this.state = { articles: null };
  }
  componentDidMount() {
    fetch('/api/articles')
      .then(resp => resp.json())
      .then(data => this.setState({ articles: data.articles }));
  }
  render() {
    if (!this.state.articles) {
      return <div>Loading Articles...</div>
    }
    return <ul>{this.props.articles.map(a => <li key={a.id}>{a.title}</li>)}</ul>
    );
  }
}

Redux example:

import React from 'react';
import { connect } from 'react-redux';

class Articles extends React.Component {
  render() {
    if (this.props.isFetching) {
      return <div>Fetching Articles...</div>
    }
    if (!this.props.articles.length) {
      return <div>No Articles Found</div>
    }
    return <ul>{this.props.articles.map(a => <li key={a.id}>{a.title}</li>)}</ul>
  }
}

const ConnectedArticles = connect()(Articles);

const route = {
  path: '/articles',
  action({ store }) {
    store.dispatch(fetchArticlesAction()); // trigger data lazy loading (no await here)
    return {
      title: 'Articles',
      component: <Layout><ConnectedArticles /></Layout>,
    };
  }
};

const ARTICLES_FETCH = 'ARTICLES_FETCH';

function fetchArticlesAction() {
  return (dispatch) => {
    dispatch({ type: ARTICLES_FETCH });
    return fetch('/api/articles')
      .then(resp => resp.json())
      .then(payload => dispatch({ type: ARTICLES_FETCH, error: false, payload }))
      .catch(error => dispatch({ type: ARTICLES_FETCH, error: true, payload: error }));
  }
}

function articlesReducer(state, action) {
  switch (action) {
    case ARTICLES_FETCH:
      return {
        isFetching: action.error === undefined,
        articles: action.error === false ? action.payload : state.articles,
      };
    default:
      return state;
  }
}

Thank you very much, @frenzzy! That exactly what I need

Was this page helpful?
0 / 5 - 0 ratings