Search code examples
reactjsreduxreact-redux

Redux state show as Map, unable to access combineReducers properties


In mapStateToProps it's supposed to be as easy as accessing the key for the section of state you want, and get the state parameter but I am unable to do so.

main app.js file

import React from 'react'
import ReactDom from 'react-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router/immutable'
import { createBrowserHistory } from 'history'

import configureStore from './store/configureStore'

import App from './components/partials/App'

const history = createBrowserHistory()

const store = configureStore(history)

ReactDom.render(
    <Provider store={store}>
        <ConnectedRouter history={history}>
            <App/>
        </ConnectedRouter>
    </Provider>,
    document.getElementById('reactDiv')
)

configureStore.js

import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router/immutable'
import thunkMiddleware from 'redux-thunk'
import { combineReducers } from 'redux-immutable'
import { connectRouter } from 'connected-react-router'

import AppReducer from './reducers/app'

const createRootReducer = history => combineReducers({
        router: connectRouter(history),
        app: AppReducer
    })

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default function configureStore(history) {
    const middleware = [routerMiddleware(history), thunkMiddleware]

    const rootReducer = createRootReducer(history)

    return createStore(rootReducer, composeEnhancers(applyMiddleware(...middleware)))
}

appReducer

/**
 * App level reducer
 */
const initialState = {
    accounts: [],
    employees: []
}

const reducer = (state = initialState, action) => {
    switch(action.type) {
        case 'ACCOUNT_SELECTION_LIST_LOADED':
            return {...state, accounts: action.payload}
        case 'EMPLOYEE_SELECTION_LIST_LOADED':
            return {...state, employees: action.payload}
    }
    return state
}

export async function fetchAccountsSelectionList(dispatch, getState) {
    makeAjaxRequest('/getList/accounts', 'GET', null, response => {
        const accounts = JSON.parse(response)
        dispatch({type: 'ACCOUNT_SELECTION_LIST_LOADED', payload: accounts})
    })
}

export async function fetchEmployeesSelectionList(dispatch, getState) {
    makeAjaxRequest('/getList/employees', 'GET', null, response => {
        const employees = JSON.parse(response)
        dispatch({type: 'EMPLOYEE_SELECTION_LIST_LOADED', payload: employees})
    })
}

export default reducer

Primary component (shrunk down to save space)

import React, { Component } from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
import { LinkContainer } from 'react-router-bootstrap'
import { FormControl, InputGroup, Navbar, Nav, NavDropdown, NavLink } from 'react-bootstrap'
import { connect } from 'react-redux'
import { fetchAccountsSelectionList, fetchEmployeesSelectionList } from '../../store/reducers/app'
import Select from 'react-select'
  
class App extends Component {
    constructor() {
        super()
        this.state =  {
            billId: '',
            invoiceId: '',
            manifestId: ''
        }
        this.handleChange = this.handleChange.bind(this)
    }

    componentDidMount() {
        this.props.fetchAccountsSelectionList()
        this.props.fetchEmployeesSelectionList()
    }

    handleChange(event) {
        const {name, checked, value, type} = event.target
        this.setState({[name]: type === 'checkbox' ? checked : value})
    }

    render() {
        return (
            <BrowserRouter>
                <Navbar variant='dark' bg='dark' className={'navbar-expand-lg', 'navbar'}>
                    <LinkContainer to='/'>
                        <Navbar.Brand>Fast Forward Express v2.0</Navbar.Brand>
                    </LinkContainer>
                    <Navbar.Toggle aria-controls='responsive-navbar-nav' />
                    <Navbar.Collapse id='responsive-navbar-nav'>
                        <Nav className='ml-auto'>
                            <NavDropdown title='Bills' id='navbar-bills'>
                            </NavDropdown>
                            <NavDropdown title='Invoices' id='navbar-invoices'>
                            </NavDropdown>
                            <NavDropdown title='Accounts' id='navbar-accounts' alignRight>
                            </NavDropdown>
                            <NavDropdown title='Employees' id='navbar-employees' alignRight>
                            </NavDropdown>
                            <LinkContainer to='/app/dispatch'><NavLink>Dispatch</NavLink></LinkContainer>
                            <NavDropdown title='Administration' id='navbar-admin' alignRight>
                            </NavDropdown>
                        </Nav>
                    </Navbar.Collapse>
                </Navbar>
                <Switch>
                    <Route path='xxx' component={xxx}></Route>
                </Switch>
            </BrowserRouter>
        )
    }
}

const matchDispatchToProps = dispatch => {
    return {
        fetchAccountsSelectionList: () => dispatch(fetchAccountsSelectionList),
        fetchEmployeesSelectionList: () => dispatch(fetchEmployeesSelectionList)
    }
}

const mapStateToProps = state => {
    return {
         accounts: state.app.accounts,
         employees: state.app.employees
    }
}

export default connect(mapStateToProps, matchDispatchToProps)(App)

So. When I log out the "state" that mapStateToProps is receiving, it is a Map, not an object, that looks like this:

state results

Why doesn't my state look like any of the tutorials I've seen? I have been unable to find any other example like this, except for one that said to just iterate through manually - but since that is not the "correct" way, I must be configuring store incorrectly

I should note that the developer tools seem to be able to access state perfectly, and reflect what I would expect to see. Why can't I access state.app.accounts


Solution

  • The problem is that you're using two packages that rely on Immutable.js data structures, rather than plain JS objects: import { combineReducers } from 'redux-immutable' and import { routerMiddleware } from 'connected-react-router/immutable'.

    In particular, that combineReducers from redux-immutable is going to cause the root state object to be an Immutable.js Map instance, instead of a plain object, and that Map instance has .get() and .set() methods instead of plain fields.

    We strongly recommend against using Immutable.js in Redux apps at this point.

    Instead, you should write your state as plain JS objects and arrays, and you should be using our official Redux Toolkit package to set up your store and write your reducers.

    Please go through the official "Redux Essentials" and "Redux Fundamentals" tutorials in the Redux core docs to learn how to use Redux Toolkit to write Redux logic.