How to solve data loading issue in React redux

2020-05-09 18:38发布

问题:

I am trying to figure out how can i manage/display this component when data is still loading.

I am using react redux for this case.

any suggestion for solving this out?

Although I wrapped this with lazy loading but it seems it is not that much working in this case.

Any suggestion for this.

//Actions.js

export const getContact= () => dispatch => {
    dispatch(setResumesLoading());
    axios
        .get('/api/contacts')
        .then(res => 
            dispatch({
                type: GET_CONTACTS,
                payload: res.data
            })
        ).catch (err => dispatch (returnErrors(err.response.data, err.response.status)));
};

//component.js

import React, {Component} from 'react';
import {Grid, Cell, List, ListItem, ListItemContent, Button} from 'react-mdl';
import { connect } from 'react-redux';
import { getContact, deleteContact} from '../../actions/resumeActions';
import PropTypes from 'prop-types';

class Contact extends Component{

    static propTypes = {
        getContact: PropTypes.func.isRequired,
        deleteContact: PropTypes.func.isRequired,
        resume: PropTypes.object.isRequired,
        isAuthenticated: PropTypes.bool,
        auth: PropTypes.object.isRequired
    }

    componentDidMount() {
        this.props.getContact();
    }

    onDeleteContactClick = (id) => {
        this.props.deleteContact(id);
    };

    render(){
        const { contacts } = this.props.resume;
        const { user } = this.props.auth;

        return(
            <div>
                {/* {loading ? <Loading /> : <ResultsComponent results={data} />} */}
                 {contacts.map(({ _id, contact_name, contact_phone, contact_email, contact_skype, contact_image }) => (
            <Grid key={_id} timeout={100} classNames="fade">

               { this.props.isAuthenticated && (user.is_admin === true) ? 
                            <Button className="remove-btn"
                            color="danger"
                            size="sm"
                            onClick= {this.onDeleteContactClick.bind(this, _id)}>
                                &times;
                            </Button> : null }
                    <Cell col={6}>
                        <div style={{textAlign: 'center'}}>
                            <h2> {contact_name} </h2>
                            <img src={contact_image}
                            alt="avatar"
                            style={{height: '40%', borderRadius: '50%', width: '50%'}}
                            img-rounded />
                        </div>

                    </Cell>
                    <Cell col={6} className="contact-right-col text-center">

                        <h2 >Contact Me</h2>
                        <hr  className="resume-left-contact-section-border" />

                        <List>
                          <ListItem>
                            <ListItemContent  className="contact-list-item">
                                <i className="fa fa-phone-square" aria-hidden="true"/>
                                {contact_phone}
                            </ListItemContent>
                          </ListItem>
                        </List>

                    </Cell>
            </Grid>
            ))} 
            </div>


        )
    }
}



const mapStateToProps = (state) => ({
    resume: state.resume,
    isAuthenticated : state.auth.isAuthenticated,
    auth: state.auth
});

export default connect(mapStateToProps, {getContact, deleteContact }) (Contact);

回答1:

Well, you can add two more actions into your existing list of actions. One for getting the status of the beginning of the API call and one for any error. Sort of like this:

import * as types from "./actionTypes";

export function beginApiCall() {
  return { type: types.BEGIN_API_CALL };
}

export function apiCallError() {
  return { type: types.API_CALL_ERROR };
}

Then you can make use of these actions by dispatching them at the right time.

export const getWorkexperience = () => dispatch => {
    dispatch(beginApiCall());
    axios
        .get('/api/workexperiences')
        .then(res => 
            dispatch({
                type: GET_WORKEXPERIENCE,
                payload: res.data
            })
        ).catch (err => dispatch(apiCallError(error)););
};

Then you have to create a new reducer for this action. Writing a reducer for this is a little tricky. You need to store the number of API calls in progress and increment or decrement them based on their status. For that, you can append _SUCCESS to your existing action type in all your action creators and reducers.

import * as types from "../actions/actionTypes";
import initialState from "./initialState";

function actionTypeEndsInSuccess(type) {
  return type.substring(type.length - 8) === "_SUCCESS";
}

export default function apiCallStatusReducer(
  state = initialState.apiCallsInProgress,
  action
) {
  if (action.type == types.BEGIN_API_CALL) {
    return state + 1;
  } else if (
    action.type === types.API_CALL_ERROR ||
    actionTypeEndsInSuccess(action.type)
  ) {
    return state - 1;
  }

  return state;
}
  //initialState.js
    export default {
      state1: [],
      state2: [],
      apiCallsInProgress: 0
    };

Once inside your component, after you make a fetch request, you can use the state of this reducer to render a spinner or anything you want just by fetching it from the reducer.

  const loading = useSelector((state) => state.apiCallsInProgress > 0);

or you can access it via mapStateToProps like this, which I see you have used to fetch props in your component.

const mapStateToProps = (state) => ({
    resume: state.resume,
    isAuthenticated : state.auth.isAuthenticated,
    auth: state.auth,
    loading: state.apiCallsInProgress > 0
});

And you can return the content of the function like this.

 {loading ? (
       Loading...
      ) : (
        <div>My component</div>
)}


回答2:

One of the common ways to deal with presentation of the component, especially if it is a container, is to implement loading activity indicator, which would disappear once you have the data to display. Just make sure to implement loading boolean in your local state, and once you confirm that data is there, change loading to false.

async componentWillMount() {
  await getWorkexperience();
  this.setState({
     loading: false,
  });
}

...

render() {
  const { data, loading } = this.state;

  return (
    <div>
      {/*
        Check the status of the 'loading' variable. If true, then display
        the loading spinner. Otherwise, display the data.
      */}
      {loading ? <LoadingSpinner /> : <ResultsComponent results={data} />}
    </div>
  );

}

Is this something you were looking for?

Among ready made solutions there are packages that can be used right away:

  • https://www.npmjs.com/package/react-activity
  • https://www.npmjs.com/package/react-activity-indicator