Kim Gysen Kim Gysen - 3 months ago 73
Javascript Question

Redux mapStateToProps: dispatch action to close popup after async update

I pass state to my component as such:

const mapStateToProps = (state, ownProps) => {
return {
pageState: state.pageState
}
}


The pageState contains information on the 'page', i.e. page meta data (such as page-name), and it also receives the method that was used when an async CRUD operation has updated the page data ("PUT").

The React component is a modal window that the user calls when (s)he wants to update and save the page name.

The async PUT operation works fine and the pageState gets properly updated (no problem here), but I don't understand from where I should dispatch the "hideModal" action that is executed after the async call.

Options are:


  1. mapStateToProps: check here if the pageState contains "PUT", then dispatch the close action;

  2. From the render method: but this is discouraged;

  3. From
    shouldComponentUpdate
    . However, I bumped into a strange bug when testing this; the
    mapStateToProps
    doesn't appear to update the
    this.props.pageState
    properly on the first request. It only works on the second (?). Also, I don't feel like I should put it here anyhow; shouldComponentUpdate should not have side-effects like this.



My code to connect the component from the container:

const renameModal = connect(
mapStateToProps,
mapDispatchToProps
)(RenameModal);


Any help?

For info: I use Redux-saga for the async call. This shouldn't matter though. Updates of page info can happen from different places in my app, and this specific action (closing the modal) should stay decoupled from the implementation that takes care of the server requests.

Redux-saga, listening for async update requests:

function* updatePage(action){
try{
const page = yield call(Api.updatePage, action.payload.siteSlug, action.payload.pageId, action.payload.page);
yield put({type: 'PUT_PAGE_SUCCEEDED', page});
}catch(error){
yield put({type: 'PUT_PAGE_FAILED', error});
}
}

export function* watchUpdatePage(){
yield* takeLatest('PUT_PAGE_REQ', updatePage);
}


The reducer:

const asyncReducer = (state = pageState, action) => {
switch(action.type){
case 'GET_PAGE_SUCCEEDED':
console.log("get page succeeded");
return Immutable.fromJS({
type: "GET_PAGE_SUCCEEDED",
method: "GET",
pageState: action.page
});

case 'PUT_PAGE_SUCCEEDED':
console.log("put page succeeded");
return Immutable.fromJS({
type: "PUT_PAGE_SUCCEEDED",
method: "PUT",
pageState: action.page
});

default:
return state;
}
}

Answer

Actions in redux can be handled by multiple reducers. Since your async call already dispatches the PUT_PAGE_SUCCEEDED action type when the call ends (and PUT_PAGE_REQ before it), you don't have to dispatch another action. Create a modal reducer like this:

const modalReducer = (state = false, action) => { 
        case 'SHOW_MODAL':
            return Immutable.fromJS({ 
                showModal: true
            });   
        case 'PUT_PAGE_SUCCEEDED': 
            return Immutable.fromJS({ 
                showModal: false
            }); 

        default: 
            return state; 
    }
}

And change mapStateToProps accordingly:

const mapStateToProps = (state, ownProps) => { 
    return { 
        pageState: state.pageState,
        modalState: state.modalState
    } 
} 

btw - I'm not really familiar with redux-saga, but I believe that it's not that different from the simple API middleware in the way in dispatches actions during the API call lifecycle. If it is....

Comments