lelandrichardson / redux-pack

Sensible promise handling and middleware for redux
MIT License
1.33k stars 60 forks source link

Promise seems to disappear in dispatch #69

Closed nigel-daniels closed 6 years ago

nigel-daniels commented 6 years ago

I'm new to using redux-pack and it looks like a great way to simplify my code and make it more maintainable. However I have hit an issue where the handler throws and error and I cannot figure out why this is, I wanted to check if there is a bug in the handler/dispatch set up somewhere?

My code looks like the following and is instrumented with debug to figure out what is going on:

action_types.js

// Auth Action Types
export const LOGIN = 'LOGIN';

auth_actions.js

import * as types from './action_types';
import * as services from '../services';
import Debug from 'debug';

let debug = Debug('auth_actions');

export const login = (email, password) => {
    debug('login, called.');
    let promise = services.login(email, password);
    debug('login promise = ' + promise);
    return {type: types.LOGIN, promise: promise};
};

auth_services.js

import 'whatwg-fetch';
import Debug from 'debug';

let debug = Debug('auth_servcies');

const checkResponse = (response) => {
    if (!response.ok) {
        let error = new Error(response.statusText);
        error.response = response;
        return Promise.reject(error);
    }
    return response;
};

export const login = (email, password) => {

    debug('login, called.');
    debug('login, email: ' + email);

    let formData = new FormData();
    formData.append('email', email);
    formData.append('password', password);

    let init = {
        method:         'POST',
        credentials:    'same-origin',
        headers:        {'content-type': 'application/json'},
        body:           formData
    };

    let request = new Request('/login', init);

    debug('login, fetch request = ' + request.url + ', '+ request.method + ', ' + request.credentials + ', ' + request.body);

    return fetch(request)
        .then(checkResponse)
        .then(response => response.json())
        .catch(error => error);
};

auth_reducer.js

import handle from 'redux-pack';
import * as actions from '../actions';
import Debug from 'debug';

let debug = Debug('auth_reducer');

const INITIAL_STATE = {
    loggedin: false,
    isworking: false,
    err: null,
};

export default function reducer(state = INITIAL_STATE, action) {
    debug('reducer called');
    const { type, payload } = action;

    debug('state is: ' + JSON.stringify(state));
    debug('action : ' + JSON.stringify(action));

    switch (type) {

    case actions.LOGIN: {
        debug('login called');
        handle(state, action, {
            start: (prevState) => {debug('login start'); return {...prevState, isworking: true, err: null};},
            finish: prevState => {debug('login success'); return { ...prevState, isworking: false };},
            failure: prevState => {debug('login fail'); return { ...prevState, err: payload };},
            success: prevState => {debug('login success'); return { ...prevState, loggedin: payload.ok };}
        });
    }
    default:
        return state;
    }
}

When I hit the submit button on my Login.jsx component I see the following on the console:

Console Output

[Log] Login login, called +3s (client.js, line 7247)
[Log] Login props = {"match":{"path":"/login","url":"/login","isExact":true,"params":{}},"location":{"pathname":"/login","state":{"from":"/authRoute"},"search":"","hash":"","key":"94243a"},"history":{"length":58,"action":"REPLACE","location":{"pathname":"/login","state":{"from":"/authRoute"},"search":"","hash":"","key":"94243a"}},"isworking":false,"loggedin":false,"err":null} +0ms (client.js, line 7247)
[Log] Login mapDispatchToProps, login dispatched. +0ms (client.js, line 7247)
[Log] auth_actions login, called. +0ms (client.js, line 7247)
[Log] auth_servcies login, called. +0ms (client.js, line 7247)
[Log] auth_servcies login, email: foo@example.com +0ms (client.js, line 7247)
[Log] auth_servcies login, fetch request = https://localhost:3000/login, POST, same-origin, [object ReadableStream] +1ms (client.js, line 7247)
[Log] auth_actions login promise = [object Promise] +1ms (client.js, line 7247)
[Log] auth_reducer reducer called +4s (client.js, line 7247)
[Log] auth_reducer state is: {"loggedin":false,"isworking":false,"err":null} +0ms (client.js, line 7247)
[Log] auth_reducer action : {"type":"LOGIN","meta":{"redux-pack/LIFECYCLE":"start","redux-pack/TRANSACTION":"60655b16-6233-480f-a5c1-c9506f4e9493"}} +0ms (client.js, line 7247)
[Log] auth_reducer login called +0ms (client.js, line 7247)
[Error] TypeError: Object is not a function (near '...(0, _reduxPack2.default)...')
    reducer (client.js:48752)
    combination (client.js:47039)
    dispatch (client.js:46817)
    (anonymous function) (client.js:46581)
    handlePromise (client.js:46515)
    (anonymous function) (client.js:46577)
    login (client.js:1891)
    login (client.js:1369)
    login
    callCallback (client.js:25590)
    dispatchEvent
    invokeGuardedCallbackDev (client.js:25628)
    invokeGuardedCallback (client.js:25677)
    invokeGuardedCallbackAndCatchFirstError (client.js:25691)
    executeDispatch (client.js:25956)
    executeDispatchesInOrder (client.js:25978)
    executeDispatchesAndRelease (client.js:26076)
    forEachAccumulated (client.js:26057)
    runEventsInBatch (client.js:26218)
    runExtractedEventsInBatch (client.js:26227)
    handleTopLevel (client.js:29691)
    batchedUpdates (client.js:38027)
    batchedUpdates (client.js:27429)
    dispatchEvent (client.js:29772)
    interactiveUpdates (client.js:38082)
    dispatchInteractiveEvent (client.js:29749)
    dispatchInteractiveEvent

So I see the Login method gets called, the state has the expected values, the dispatch is called, then the action calls the service and build the request with the expected data. The action then returns a Promise Object. The reducer is then called with the expected state but I note the action appears to have the correct type (LOGIN) but no apparent promise??? The type maps to the correct switch case then the handler throws the error above. In fact it throws two of the same type and then I get a 404 response from the server which has some how been called with a GET (not a POST) to /login?.

So I guess my question is what's causing this? Is the dispatch stripping the promise or is the action formed correctly and I'm hitting an issue in the handler?

Let me know if more information is needed to help diagnose or if you need me to try out anything.

nigel-daniels commented 6 years ago

As an update it seems that the issue is not so much the 'disappearing' promise, as far as I can tell the middleware component of redux-pack is consuming this, but the Promise returned by the fetch API is not being handled correctly for some reason?

As I understood from the documentation for redux-pack when building the action the return from the call to the API should be returning a promise. The ReadMe shows the following:

// actions.js
export function loadFoo(id) {
  return {
    type: LOAD_FOO,
    promise: Api.getFoo(id),
  };
}

So in my own code I am doing this (debug removed for clarity):

export const login = (email, password) => {
    return {
        type: types.LOGIN, 
        promise: services.login(email, password)
  };
};

However I suspect the returned Promise from the fetch call in my API (services) function is not being handled as a Promise for some reason? I end up that call with:

return fetch(request)
    .then(checkResponse)
    .then(response => response.json())
    .catch(error => error);

The documentation for the fetch API states:

The fetch() method of the WindowOrWorkerGlobalScope mixin starts the process of fetching a resource from the network. This returns a promise that resolves to the Response object representing the response to your request.

So am I wondering if:

  1. I am not returning the correct sort of Promise, in which case what should my API return for this fetch to be handled correctly?
  2. The middleware in redux-pack is struggling to handle the Promise returned by the fetch API for some internal reason?

Any guidance or suggestions welcome, if the answer is 1 then maybe I can contribute something using fetch() to the documentation to help future users, if it's 2 then let me know if there is any diagnostics I can run to help out.

nigel-daniels commented 6 years ago

Closed as I finally noticed the import of handle to be incorrect, it was missing the braces.