Search code examples
reactjsreduxredux-thunk

Redux thunk – Proper implementation


My implementation of the redux thunk is causing to override the array data.

Obviously something is wrong in the flow but I cant figured it out.

Basically I have two components StringInstrument and UsersListedItems.

StringInstrument will fetch data from DB (via axios) to get a list of item owners.

For each owner a UsersListedItems component will be created and this component will also fetch data from DB (images) via the owner ID.

So I would say that StringInstrument actually creating the UsersListedItems.

Here is some code for StringInstrument:

if (this.props.error) {
        return <p>Sorry! There was an error loading the items</p>;
    }
    if (this.props.isLoading) {
        return <CircularProgress/>;
    }

     return (
        <div>
            <Grid container spacing={24}>
                {this.props.itemOwner.map((item, index) => (
                    <Grid item xs={6} sm={3} key={index}>
                        <UsersListedItems
                            ownerId={item.ownerId}
                            userName={item.userName}
                            categoryId={1}>
                        </UsersListedItems>
                    </Grid >)
                )}
            </Grid>
        </div>
    );
   }
  }

const mapStateToProps = (state) => {
return {
    itemOwner: state.itemOwner.data,
    isLoading: state.itemOwner.isLoading,
    error: state.itemOwner.error
}
 }

const mapDispatchToProps = (dispatch) => {
return {
    getItemOwners: (id) => dispatch(itemAction.getItemOwners(id))
  }

This is how I implemented the action & reducer.

export function getItemOwner(state = initState, action) {
switch (action.type) {
    case GET_ITEM_OWNER_START:
        state = Object.assign({}, state, { isLoading: true });
        break;
    case GET_ITEM_OWNER_SUCCESS:
        state = Object.assign({}, state, { data: action.payload, isLoading: false });
        break;
    case GET_ITEM_OWNER_ERROR:
        state = Object.assign({}, state, { error: action.payload, isLoading: false });
        break;
}
return state;
 }

 export function getItems(state = initState, action) {
switch (action.type) {
    case GET_ITEMS_START:
        state = Object.assign({}, state, { isLoading: true });
        break;
    case GET_ITEMS_SUCCESS:
        state = Object.assign({}, state, { data: action.payload, isLoading: false });
        break;
    case GET_ITEMS_ERROR:
        state = Object.assign({}, state, { error: action.payload, isLoading: false });
        break;
}
return state;


export const getItemOwners = (categoryId) => {
return (dispatch, getState) => {
    //make async call to database
    dispatch({ type: GET_ITEM_OWNER_START });
    axios.get('api/items/owner/category/' + categoryId)
        .then(function (response) {
            dispatch({ type: GET_ITEM_OWNER_SUCCESS, payload: response.data });
        })
        .catch(function (error) {
            dispatch({ type: GET_ITEM_OWNER_ERROR, payload: error });
        });
}
 };

  export const getItems = (categoryId, ownerId) => {
   return (dispatch, getState) => {
    dispatch({ type: GET_ITEMS_START });
    axios.get('api/items/' + categoryId + '/' + ownerId)
        .then(function (response) {
            dispatch({ type: GET_ITEMS_SUCCESS, payload: response.data });
        })
        .catch(function (error) {
            dispatch({ type: GET_ITEMS_ERROR, payload: error });
        });
}

I am not sure how to manage\control the flow of the dispatchers in order so it will fit to the component structure without override the collected data.

As you can see in the attach image the 4 'GET_ITEM_SUCCESS' are at the end and each one of them will override the next one.

enter image description here

Hope I was clear and apologized for this long code example.

Thank you


Solution

  • I think the issue you're having is that you're only storing one of the items in state at any given time, and each subsequent call is clobbering the content in state. If I'm correct in that assumption, then the solution would be to use categoryId and ownerId as a key for storing the data in an object where the value would be the payload.

    First you'll need to provide the categoryId and ownerId in the action

    export const getItems = (categoryId, ownerId) => (dispatch, getState) => {
      dispatch({ type: GET_ITEMS_START, categoryId, ownerId });
      axios.get('api/items/' + categoryId + '/' + ownerId)
        .then(function (response) {
          dispatch({ type: GET_ITEMS_SUCCESS, payload: response.data, categoryId, ownerId });
        })
        .catch(function (error) {
          dispatch({ type: GET_ITEMS_ERROR, payload: error, categoryId, ownerId });
        });
    
    

    Next, you'll need to update your reducer to assign the payload to an object

    const key = `${action.categoryId}-${action.ownerId}`; // unique key for the payload
    switch (action.type) {
        case GET_ITEMS_START:
            state = Object.assign({}, state, { isLoading: true, data: Object.assign({}, state.data, {[key]: []}) });
            break;
        case GET_ITEMS_SUCCESS:
            state = Object.assign({}, state, { data: Object.assign({}, state.data, { [key]: action.payload }), isLoading: false });
            break;
        case GET_ITEMS_ERROR:
            state = Object.assign({}, state, { error: action.payload, isLoading: false });
            break;
    }
    

    Finally, you'll need to map the data to the state of your component via props (redux). Then you can access the items using the same key items[${categoryId}-${ownerId}]