Search code examples
reduxreact-reduxredux-thunk

React/Redux: Why does mapStateToProps() make my store state of an array disappear?


In my store I have a state with this shape: {posts: [{...},{...}]}, but when I use mapStateToProps() in Home.js, the state returns {posts: []}, with an empty array (where there used to be an array in the store's state).

Am I using mapStateToProps() incorrectly or does the problem stem from other parts of the Redux cycle?

API fetch I'm using, temporarily located in actions.js

// api

const API = "http://localhost:3001"

let token = localStorage.token
if (!token) {
    token = localStorage.token = Math.random().toString(36).substr(-8)
}

const headers = {
    'Accept': 'application/json',
    'Authorization': token
}

// gets all posts
const getAllPosts = token => (
    fetch(`${API}/posts`, { method: 'GET', headers })
);

Action and action creators, using thunk middleware:

// actions.js

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

export const RECEIVE_POSTS = 'RECEIVE_POSTS';
function receivePosts (posts) {
    return {
        type: RECEIVE_POSTS,
        posts,
        receivedAt: Date.now()
    }
}

// thunk middleware action creator, intervenes in the above function
export function fetchPosts (posts) {
    return function (dispatch) {
        dispatch(requestPosts(posts))
        return getAllPosts()
               .then(
                    res => res.json(),
                    error => console.log('An error occured.', error)
                )
               .then(posts => 
                    dispatch(receivePosts(posts))
                )
    }
}

Reducer:

// rootReducer.js

function posts (state = [], action) {
    const { posts } = action

    switch(action.type) {
        case RECEIVE_POSTS :
            return posts;
        default : 
            return state;
    }
}

Root component that temporarily contains the Redux store:

// index.js (contains store)

const store = createStore(
  rootReducer,
  composeEnhancers(
    applyMiddleware(
        logger, // logs actions
        thunk // lets us dispatch() functions
    )
  )
)

store
  .dispatch(fetchPosts())
  .then(() => console.log('On store dispatch: ', store.getState())) // returns expected

ReactDOM.render(
    <BrowserRouter>
        <Provider store={store}>
            <Quoted />
        </Provider>
    </BrowserRouter>, document.getElementById('root'));
registerServiceWorker();

Main component:

// Home.js
function mapStateToProps(state) {
    return {
        posts: state
    }
}


export default connect(mapStateToProps)(Home)

In Home.js component, console.log('Props', this.props) returns {posts: []}, where I expect {posts: [{...},{...}]}.

*** EDIT: After adding a console.log() in the action before dispatch and in the reducer, here is the console output: Console output link (not high enough rep to embed yet)


Solution

  • The redux store should be an object, but seems like it's getting initialized as an array in the root reducer. You can try the following:

    const initialState = {
        posts: []
    }
    
    function posts (state = initialState, action) {
    
        switch(action.type) {
            case RECEIVE_POSTS :
                return Object.assign({}, state, {posts: action.posts})
            default : 
                return state;
        }
    }
    

    Then in your mapStateToProps function:

    function mapStateToProps(state) {
        return {
            posts: state.posts
        }
    }