简体   繁体   中英

show loader while fetching initial data in react-redux component

I am trying to do initial fetch in component and render Loader while data is fetching. But render() calls before isFetching becomes true and therefore my component is blinking (isFetching=false => isFetching=true => isFetching=false). I know that i can just render Loader while there is no data and blinking will be gone, but maybe someone knows a more elegant solution?

actions/files.js

import 'whatwg-fetch';

import { REQUEST_FILES, RECEIVE_FILES } from '../constants'

export function requestFiles() {
  return { type: REQUEST_FILES };
}

export function receiveFiles(files) {
  const payload = files;
  return { payload:payload, type: RECEIVE_FILES };
}

export function getFile(hash) {
  return dispatch => {
    dispatch(requestFiles());
    fetch('/api/files/'+hash+'/info', {
        method: 'GET',
      })
      .then(response => response.json())
      .then(data => {
        if (data.error) {
        }
        else {
          dispatch(receiveFiles([data]));
        }
      })
      .catch(error => { console.log('request failed', error); });
  }
}

reducers/files.js

import { REQUEST_FILES, RECEIVE_FILES } from '../constants'

var initialState = {
    items: [],
    isFetching: false
}

const files = (state = initialState, action) => {
  switch (action.type) {
    case RECEIVE_FILES: {
      return { ...state, isFetching:false, items: action.payload };
    }
    case REQUEST_FILES: {
      return { ...state, isFetching:true };
    }
    default:
      return state;
  }
}

export default files;

components/smart/fileboard.jsx

require('styles/app.scss');

import React from 'react';
import { Link } from 'react-router'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'

import * as filesActions from '../../actions/files'

import Loader from '../dumb/loader'
import withBoard from './board'
import DumbFileboard from '../dumb/fileboard'


let getFileSrc = require('config').default.getFileSrc;

const mapStateToProps = (state) => {
  return {
    files: state.files
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    filesActions: bindActionCreators(filesActions, dispatch)
  }
}


class Fileboard extends React.Component {

  componentWillMount() {
    if (this.props.files.items.length === 0) {
      this.props.filesActions.getFile(this.props.params.hash);
    }
  }

  constructor(props) {
    super(props);
  }

  render() {
    return this.props.files.isFetching ? Loader : (
      <DumbFileboard fileSrc={ getFileSrc(this.props.files.items[0]) } />
    );
  }
}


export default connect(mapStateToProps, mapDispatchToProps)(withBoard(Fileboard));

Why not make the initial state have isFetching set to true ? You can pass an initial state when you create the store with the createStore function.

Also, the React docs recommend fetching data in componentDidMount :

if you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.

https://facebook.github.io/react/docs/react-component.html#componentdidmount

In my opinion is elegant enough, only three things:

There is no files property in the initial state.

const mapStateToProps = (state) => {
  return {
    files: state.files
  }
}

Why don't you use the isFetching property from state?

const mapStateToProps = (state) => {
  return {
    files: state.files,
    isFetching: state.isFetching
  }
}

Then

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

    return (
      isFetching ? 
        Loader 
        : 
        <DumbFileboard fileSrc={ getFileSrc(this.props.files.items[0]) } />
    );
  }

I don't think your Loader is getting rendered ever (but you're showing a blank child until data is loaded, then child gets data and renders more things).

Instead of this,

  render() {
    return this.props.files.isFetching ? Loader : (
      <DumbFileboard fileSrc={ getFileSrc(this.props.files.items[0]) } />
    );
  }

you need this:

  render() {
    return this.props.files.isFetching ? <Loader/> : (
      <DumbFileboard fileSrc={ getFileSrc(this.props.files.items[0]) } />
    );
  }

Note the missing < and /> .


See this fiddle (a simulation of the case): https://jsfiddle.net/free_soul/49bdw76z/

and the fixed one with working Loader: https://jsfiddle.net/free_soul/u0v8e0zg/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM