Search code examples
node.jsreduxredux-thunkes6-modules

How to use redux-thunk from node server side?


(code from https://redux.js.org/advanced/async-actions)

The code setups a redux store and then calls dispatch on the store with some actions. The store use redux-thunk to manage async API calls.

Here is the index.js

    import reduxThunk from 'redux-thunk'
    const { thunkMiddleware } = reduxThunk;
    import redux from 'redux';
    const { createStore } = redux;
    const { applyMiddleware } = redux;
    import { selectSubreddit, fetchPosts } from './actions.js'

    import rootReducer from './reducers.js'

    const store = createStore(
      rootReducer,
      applyMiddleware(thunkMiddleware)
    );

    store.dispatch(selectSubreddit('reactjs'));
    store.dispatch(fetchPosts('reactjs')).then(() => console.log(store.getState()));

Error after running node index.js

    (node:19229) ExperimentalWarning: The ESM module loader is experimental.
    applyMiddleware [Function: applyMiddleware]
    /home/code/redux/tutorial_async_actions/node_modules/redux/lib/redux.js:648
            return middleware(middlewareAPI);
                  ^
    TypeError: middleware is not a function
        at /home/code/redux/tutorial_async_actions/node_modules/redux/lib/redux.js:648:16
        at Array.map (<anonymous>)
        at /home/code/redux/tutorial_async_actions/node_modules/redux/lib/redux.js:647:31
        at createStore (/home/code/redux/tutorial_async_actions/node_modules/redux/lib/redux.js:85:33)
        at file:///home/code/redux/tutorial_async_actions/index.js:18:15
        at ModuleJob.run (internal/modules/esm/module_job.js:110:37)
        at async Loader.import (internal/modules/esm/loader.js:164:24)

What do I do to get this to run? I think this has something to do with ES6 and modules but I'm stuck... :(

I am already doing this (as suggested by this answer)

import redux from 'redux';
const { createStore, applyMiddleware } = redux;

(I could get this to work using create-react-app ... but I would prefer to get this working without webpack et al)


Below the remaining code for reference.

Here the actions

    export const SELECT_SUBREDDIT = 'SELECT_SUBREDDIT'
    export function selectSubreddit(subreddit) {
      return {
        type: SELECT_SUBREDDIT,
        subreddit
      };
    }

    export const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT'
    function invalidateSubreddit(subreddit) {
      return {
        type: INVALIDATE_SUBREDDIT,
        subreddit
      };
    }

    export const REQUEST_POSTS = 'REQUEST_POSTS'
    function requestPosts(subreddit) {
      return {
        type: REQUEST_POSTS,
        subreddit
      }
    }

    export const RECEIVE_POSTS = 'RECEIVE_POSTS'
    function receivePosts(subreddit, json) {
      return {
        type: RECEIVE_POSTS,
        subreddit,
        posts: json.data.children.map(child => child.data),
        receivedAt: Date.now()
      }
    }

    export function fetchPosts(subreddit) {
      return function (dispatch) {
        dispatch(requestPosts(subreddit));

        return fetch(`https://www.reddit.com/r/${subreddit}.json`)
          .then(
            response => response.json(),
            error => console.log('An error occurred.', error)
          )
          .then(json =>
            dispatch(receivePosts(subreddit, json))
          )
      }
    }

and here the reducers

    import redux from 'redux';
    const { combineReducers } = redux;
    import {
      SELECT_SUBREDDIT,
      INVALIDATE_SUBREDDIT,
      REQUEST_POSTS,
      RECEIVE_POSTS
    } from './actions.js';

    function selectedSubreddit(state = 'reactjs', action) {
      switch (action.type) {
        case SELECT_SUBREDDIT:
          return action.subreddit
        default:
          return state
      }
    }

    function posts(
      state = {
        isFetching: false,
        didInvalidate: false,
        items: []
      },
      action
    ) {
      switch (action.type) {
        case INVALIDATE_SUBREDDIT:
          return Object.assign({}, state, { didInvalidate: true })
        case REQUEST_POSTS:
          return Object.assign({}, state, { isFetching: true, didInvalidate: false });
        case RECEIVE_POSTS:
          return Object.assign({}, state, {
            isFetching: false, didInvalidate: false,
            items: action.posts,
            lastUpdated: action.receivedAt
          });
        default:
          return state;

      }
    }

    function postsBySubreddit(state = {}, action) {
      switch (action.type) {
        case INVALIDATE_SUBREDDIT:
        case RECEIVE_POSTS:
        case REQUEST_POSTS:
          return Object.assign({}, state, {
            [action.subreddit]: posts(state[action.subreddit], action)
          });
        default:
          return state
      }

    }

    const rootReducer = combineReducers({
      postsBySubreddit,
      selectedSubreddit
    });

    export default rootReducer;

Here package.json

    {
      "name": "redux_async_actions",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "",
      "license": "ISC",
      "type": "module",
      "dependencies": {
        "redux": "^4.0.5",
        "redux-thunk": "^2.3.0"
      }
    }

Solution

  • (I originally asked the question but I lost my login)

    I got this to work using the node module system (i.e. using the function require). The ES6 export / import should work but I guess one or other of the modules I am trying to use (redux, redux-thunk) does not play nicely with ES6 export / import.

    Essentially I converted the export statements to exports. statements

    export function(...) {...} => exports.myFunction = function(...) {...}

    And the I converted import statements to require statements.

    import {myFunction} from './somefile.js'=> const module = require('./somefile.js')

    Below the code as in the question but using require.

    index.js

        const redux = require('redux');
        const { createStore, applyMiddleware } = redux;
    
        const ReduxThunk = require('redux-thunk').default
    
        const actions = require('./actions.js');
        const { selectSubreddit, fetchPosts } = actions;
    
        const rootReducer = require('./reducers.js');
    
        const store = createStore(
          rootReducer.rootReducer,
          applyMiddleware(ReduxThunk)
        );
    
        store.dispatch(selectSubreddit('reactjs'));
        store.dispatch(fetchPosts('reactjs')).then(() => console.log(store.getState()));
    

    actions.js

        const fetch = require('cross-fetch');
    
        const SELECT_SUBREDDIT = 'SELECT_SUBREDDIT'
        exports.SELECT_SUBREDDIT = SELECT_SUBREDDIT
        function selectSubreddit(subreddit) {
          return {
            type: SELECT_SUBREDDIT,
            subreddit
          };
        }
        exports.selectSubreddit = selectSubreddit;
    
        const INVALIDATE_SUBREDDIT = 'INVALIDATE_SUBREDDIT'
        exports.INVALIDATE_SUBREDDIT = INVALIDATE_SUBREDDIT
        function invalidateSubreddit(subreddit) {
          return {
            type: INVALIDATE_SUBREDDIT,
            subreddit
          };
        }
    
        const REQUEST_POSTS = 'REQUEST_POSTS'
        exports.REQUEST_POSTS = REQUEST_POSTS
        function requestPosts(subreddit) {
          return {
            type: REQUEST_POSTS,
            subreddit
          }
        }
    
        const RECEIVE_POSTS = 'RECEIVE_POSTS'
        exports.RECEIVE_POSTS = RECEIVE_POSTS
        function receivePosts(subreddit, json) {
          return {
            type: RECEIVE_POSTS,
            subreddit,
            posts: json.data.children.map(child => child.data),
            receivedAt: Date.now()
          }
        }
    
        function fetchPosts(subreddit) {
          return function (dispatch) {
            dispatch(requestPosts(subreddit));
    
            return fetch(`https://www.reddit.com/r/${subreddit}.json`)
              .then(
                response => response.json(),
                error => console.log('An error occurred.', error)
              )
              .then(json =>
                dispatch(receivePosts(subreddit, json))
              )
          }
        }
        exports.fetchPosts = fetchPosts;
    

    reducers.js

        const redux = require('redux');
        const { combineReducers } = redux;
        const actions = require('./actions.js');
        const {
          SELECT_SUBREDDIT,
          INVALIDATE_SUBREDDIT,
          REQUEST_POSTS,
          RECEIVE_POSTS
        } = actions;
    
        function selectedSubreddit(state = 'reactjs', action) {
          switch (action.type) {
            case SELECT_SUBREDDIT:
              return action.subreddit
            default:
              return state
          }
        }
    
        function posts(
          state = {
            isFetching: false,
            didInvalidate: false,
            items: []
          },
          action
        ) {
          switch (action.type) {
            case INVALIDATE_SUBREDDIT:
              return Object.assign({}, state, { didInvalidate: true })
            case REQUEST_POSTS:
              return Object.assign({}, state, { isFetching: true, didInvalidate: false });
            case RECEIVE_POSTS:
              return Object.assign({}, state, {
                isFetching: false, didInvalidate: false,
                items: action.posts,
                lastUpdated: action.receivedAt
              });
            default:
              return state;
    
          }
        }
    
        function postsBySubreddit(state = {}, action) {
          switch (action.type) {
            case INVALIDATE_SUBREDDIT:
            case RECEIVE_POSTS:
            case REQUEST_POSTS:
              return Object.assign({}, state, {
                [action.subreddit]: posts(state[action.subreddit], action)
              });
            default:
              return state
          }
    
        }
    
        const rootReducer = combineReducers({
          postsBySubreddit,
          selectedSubreddit
        });
    
        exports.rootReducer = rootReducer;
    

    package.json (notice package does not have "type": "module",)

        {
          "name": "basic_example_only_node",
          "version": "1.0.0",
          "description": "",
          "main": "index.js",
          "scripts": {
            "test": "echo \"Error: no test specified\" && exit 1"
          },
          "author": "",
          "license": "ISC",
          "dependencies": {
            "cross-fetch": "^3.0.4",
            "redux": "^4.0.5",
            "redux-thunk": "^2.3.0"
          }
        }