Search code examples
reactjsreact-nativereduxreact-reduxreact-final-form

React + Redux PUT api request to edit data in my redux store


My application is using redux to keep data from back-end API inside redux store entities reducer for example MEMBER object of data, which is shared across multiple components in App to display members details. Now I'm implementing EditProfile component which should take the data from form, send it as PUT request to backend API and if request ended successfuly (Done till here) update current Member Profile entity in redux store. I'm trying to find something like "best practice" to reach this redux store update. I have implemented basic api middleware (similiar to one in the Redux real-world application example), but I'm not totally sure, how to reach the part with state updating.

My backend api return after PUT request only a status code and a reason (if error) or memberId (if success). I'm not able to change back-end endpoint, to return new member profile object, because we have another "global" server beyond our web-server which works same as this one and I can't change it.

So I think I will need to persist data from Form (react-final-form) and in case of successful response which contains memberId (from backend), use this data to update current Redux store entity (Member Profile).

I was thinking about saving update request data into store entities reducer -> with a key like UPDATING_MEMBER_PROFILE and id request ends with success, merge this two objects into one and clear UPDATING_ part. Do you guys have any suggestion please?

UPDATE:

To call API in my code i have actions like:

memberAction.js

export const updateProfile = values => ({
type: API,
  payload: {
    url: "/members/update",
    method: "PUT",
    data: values,
    meta: {
      label: MEMBER_PROFILE,
      success: [], // action callbacks onSuccess
      error: [] // action callbacks onError
    }
  }
});

then my API middleware takes care of action.type === API and generate actions for API_REQUEST, API_SUCCESS and API_ERROR types, with entity and label from first fetchProfile actions. It looks like:

apiMiddleware.js

...getting Info from origin action about request here

next(action);
dispatch(apiRequest(entity, label));

return callApi(url, method, data)
  .then(response => {
    dispatch(apiSuccess(response.status, response, label));
    dispatch(success(response)); // Dispatch callbacks...
  })
  .catch(error => {
    dispatch(apiError(response.status, error, label));
    dispatch(error(response)); // Dispatch callbacks...
  });

and after middleware server and dispatch another actions my api reducer will assign data from API to store by entity like:

apiReducer.js

const apiEntitiesReducer = (state = {}, action) => {
  switch (action.type) {
    case API_REQUEST: 
      return {
        ...state,
        [action.payload.label]: {
          error: false,
          data: null
        }
      };
    case API_ERROR:
    case API_SUCCESS:
      return {
        ...state,
        [action.payload.label]: {
          error: action.isError,
          data: action.payload.data
        }
      };
    default:
      return state;
  }
};

these are just pseudo codes to keep it simple (and i dont use thunks for now...). So I need somewhere (probably in api middleware) save temporary data into the store (probably by another action) to reach what I need by this approach?


Solution

  • You're on the right path. Try dispatching one action for sending the request to the API and then (after success) dispatch another action to update your store. Something like that

    profile.js:

    export const updateProfile = newMemberProfile => { //your action, using thunk here
      return dispatch => {
       fetch(UPDATE_URL, {
            method: "PUT",
            body: JSON.stringify(newMemberProfile),            
        })
        .catch(err => {
          console.log(err);
          alert("Profile update failed, please try again!");
        })
        .then(res => {
            dispatch(memberProfileUpdated(newMemberProfile));
        });
       }
    }
    
    export const memberProfileUpdated = newMemberProfile => {
      return {
        type: UPDATING_MEMBER_PROFILE,
        newMemberProfile
      };
    };
    

    Then add a reducer for this constant (put all the constants, and reducers in separate files for clarity) reducer.js:

    const initialState = {
        memberProfile: null,
    };
    
    const reducer = (state = initialState, action) => {
      switch (action.type) {
        case UPDATING_MEMBER_PROFILE:
          return {
            ...state,
            memberProfile: action.newMemberProfile
          };
        default:
          return state;
      }
    };
    
    export default reducer;
    

    Don't forget to register your reducer in your configuration! Hope it helps!