3

I currently am using action creators with Redux to dispatch user actions (for example, fetching account information). These action creators return promises from an API request that, when they resolve, update the store.

The issue I'm having is that I want to "intercept" certain responses from the API, in my case, an invalid JWT token. In such cases, I want to preserve the pending promise, prompt a Login modal, and if the login is successful, refetch and resolve the promise.

It's sufficient for me to check the response after I call fetch this way:

Root.js - Put Modal component in the DOM

export default (props) => {
    return (
        <div>
            <Modal />
            <App />
        </div>
    )
}

Modal.js - handle different modal types (approach adopted from this post)

const MODALS = {
    LOGIN: LoginModal
}

const ModalRoot = ({type, props}) => {
    if (!MODALS[type]) return null;

    return <Modal {...props} />;
}

export default connect(
    state => state.modal
)(ModalRoot);

reducer.js - Modal reducer

const initialState = {type: null, props: {}}
export default function modal(state = initialState, action) {
    switch (action.type) {
        case 'SHOW_MODAL':
            return Object.assign({}, state, {
                type: action.modalType,
                props: {...action.props}
            });
        case 'HIDE_MODAL':
            return initialState;
        default:
            return state;
    }
}

actions.js - return promise to fetch call

const promptLogin = () => (dispatch, getState) => {
    return new Promise((resolve, reject) => {
        return dispatch(showModal('LOGIN', {
            successCallback: (user) => resolve(user)
        }));
    })
}

makeAPIRequest.js - make API call, handle invalid token

fetch(endpoint, requestConfig)
    .then(res => res.json())
    .then(data => {

        /** Invalid JWT Token */
        if (data && data.error_code === 403) {
            return store.dispatch(promptLogin())
                .then(token => {
                    return makeRequest(method, endpoint, headers, body)
                });
        }

        return data;
    });

This problem with this approach is that I'm persisting a callback function (in order to complete the initial request) to the store, which is discouraged in the redux docs.

If my Modal component isn't connected to my fetch logic, and I can't store a Promise or a callback in the state (because they aren't serializable), how can I continue a promise from an initial request after the user performs a login from the modal?

Community
  • 1
  • 1
Himmel
  • 3,629
  • 6
  • 40
  • 77
  • Have you thought about solutions like redux-thunk or redux-saga which can help you implement async behaviour in easy way? – Alex Pashkin Apr 11 '17 at 03:00
  • Yeah I'm using redux-thunk currently, didn't know if that was implicit or not with my action creators. Still doesn't solve my problem, though – Himmel Apr 11 '17 at 03:01
  • Could you pass the endpoint as an argument to `promptLogin()` as an enpoint to fetch after login is successful? – user2027202827 Apr 11 '17 at 03:20
  • It's not sufficient to just pass the endpoint, because I have action creators that are awaiting Promise responses. They, in turn, update the state. This is what I need to happen. – Himmel Apr 11 '17 at 03:37
  • What do you think of batching failed requests and just remaking them once the user logs in? For example, you could create an empty array and just append callbacks to it each time you have a failed request. Then, once you receive a token from login, iterate over the array and call each callback. You could keep the callbacks out of your store, but it could admittedly get messy in other ways. – user2027202827 Apr 11 '17 at 11:23
  • A variation on that could be pushing promises to the array. Then on login, reject all the promises in your array. When you catch a failed promise in your api call, just re-call the action creator. Again, not exactly elegant, but I just tried it with a mock up and it works alright. – user2027202827 Apr 11 '17 at 11:40
  • That is definitely a viable solution, but how can I keep the callbacks out of the store? – Himmel Apr 11 '17 at 15:14
  • The way I tried yesterday was a little ugly but I created a module level global in the file for my action creators. I made a repo for it if you want to take a look: https://github.com/hobenkr88/react-authentication-test – user2027202827 Apr 12 '17 at 16:30

0 Answers0