1
votes

I am working on a web app that uses React + Redux. However, I am struggling with the loading screens. Basically, my initial state for the redux store is this.

const initialState = {
    project: {},
    projects: [],
    customers: [],
    vendors: [],
    customer: {},
    vendor: {},
    loading: false
};

An example of one of my reducers is this

const fetchSalesProject = (state, action) => {
    return updateObject(state, {
        project: action.payload,
        loading: false
    });
}

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.FETCH_SALES_PROJECT: return fetchSalesProject(state, action);
    default:
        return state;

where updateObject is this

export const updateObject = (oldObject, updatedProperties) => {
    return {
        ...oldObject,
        ...updatedProperties
    }
}

here are my actions (axiosInstance is just a custom addon to axios to include the necessary headers for authentication, but works exactly the same like a normal axios api call)

export const fetchSalesProject = (id) => (dispatch) => {
    axiosInstance
        .get(`/sales-project/detail/${id}/`)
        .then((res) => {
            dispatch({
                type: FETCH_SALES_PROJECT,
                payload: res.data,
            });
        })
        .catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};

export const showLoader = (area) => (dispatch) => {
    dispatch({
        type: SHOW_LOADER,
        area: area ? area : true
    });
};

where the param area is just in the case where loading is meant to be rendered only on a specific area on the page (not relevant for this question hence when showLoader is called, no params would be passed in and hence the area would be set to true for this question)

this is my component

componentDidMount() {
    this.props.showLoader()
    this.props.fetchSalesProject(this.props.match.params.id)
};

this is my mapStateToProps

const mapStateToProps = state => ({
     project: state.api.project,
     loading: state.api.loading
})

this is my conditional rendering

render() {
    return (
        !this.props.loading ?
            {... whatever content within my page }
        : { ...loading screen }

Basically, the issue I am facing is that, when i first load this component, there would be an error, as my this.props.project would be an empty {} (due to initialState). The reason why is that before the showLoader action is dispatched fully by componentDidMount, the component renders, and hence since this.props.loading is still set to the initialState of false, the conditional rendering passes through and the this.props.project would be rendered (however since it is empty, the various keys that I try to access within the rendering would be undefined hence throwing an error).

I tried various methods to try to overcome this, but none seem like an optimum solution. For example, I have tried to set the initialState of loading to an arbitary number, like eg. 0, and only render the page when this.props.loading === false, which is only after the data is fetched. However, the issue arises when I go onto a new page, like for example, another component called CustomerDetail. When that component is mounted, this.props.loading would be false and not 0, as it was previously manipulated by the previous component that I was on (SalesProjectDetail). Hence, that same error would come up as the conditional rendering passes through before any data was actually fetched.

Another method I tried was to use component states to handle the conditional rendering. For example, I only set a component state of done : true only after all the dispatches are completed. However, I felt like it was not good practice to mix component states and a state management system like Redux, and hence was hoping to see if there are any solutions to my problem that uses Redux only.

I am looking for a solution to my problem, which is to only render the contents only after the data has been fetched, and before it is fetched, a loading screen should be rendered. All help is appreciated, and I am new to React and especially Redux, so do guide me on the correct path if I am misguided on how this problem should be approached. Thanks all in advance!

1

1 Answers

1
votes

there can be more ways to do it. But to go ahead with your scheme of local state...

I think there is no problem if you use local state to keep track of load complete or underway. You can have a local state variable as you already said. Before dispatch set that variable to loading=true then use async/await to fetch data and afterwards set loading back to false.

I think it will work for you.