首页 > 解决方案 > redux connect mapStateToProps not called when action is dispatched

问题描述

Solution(updated):

I thought any action would cause react-redux-connect to call the mapState functions but when an action doesn't change anything then this is not the case.

I have a localStorage module that dispatches actions but don't change state, instead thy will write to localStorage. The module has selectors that are used in the containers but they won't get called until the state actually changes so the UI would only show correctly after another action was dispatched that would change the state.

Problem

When I put the store on window (window.store=store), add a console.log in the mapStateToProps, then in the console I dispatch an action: store.dispatch({type:'some action'}) then the console.log of the mapStateToProps does not show.

I do memoize the result but the mapStateToProps should be called see here

Full code is here and running example here (you can open a console clicking on 'console' link in the right bottom of the screen).

package.json

store.js:

import { createStore } from 'redux';
export default (initialState, reducer) => {
  const store = createStore(
    reducer,
    initialState,
    window.__REDUX_DEVTOOLS_EXTENSION__ &&
      window.__REDUX_DEVTOOLS_EXTENSION__()
  );
  window.store = store;
  return store;
};

app.js

import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import createStore from './store';
import { Provider } from 'react-redux';
import initCounter from './components/Counter';
import {
  createWrapper,
  memoize,
} from './components/@common';
const COUNTER = 'COUNTER';
const selectCounterState = state => state.myCounter;
const counter = initCounter({
  actionWrapper: createWrapper(COUNTER, 'counter1'),
  selectors: { myState: selectCounterState },
  connect,
  memoize,
});
const initialState = {
  myCounter: counter.initialState,
};
const reducer = (state = initialState, action) => {
  if (action.emittedBy === COUNTER) {
    return {
      ...state,
      myCounter: counter.reducer(
        selectCounterState(state),
        action.payload
      ),
    };
  }
  return state;
};
const store = createStore(initialState, reducer);
const Counter = counter.container;
const App = () => (
  <Provider store={store}>
    <Counter id="counter1" parentId={[]} />
  </Provider>
);

export default App;

component/Counter/index:

import component from './component';
const INCREASE = 'INCREASE';
const reducer = (state, action) => {
  if (action.type === INCREASE) {
    return { ...state, count: state.count + 1 };
  }
  return state;
};
const makeState = memoize =>
  memoize((id, parentId, { count }) => ({
    id: parentId.concat(id),
    parentId,
    count,
  }));
const mapStateToProps = ({ myState }, memoize) => () => {
  const newState = makeState(memoize);
  return (state, ownProps) =>
    console.log('in map state to props', new Date()) ||
    newState(
      ownProps.id,
      ownProps.parentId,
      myState(state)
    );
};

export default ({
  actionWrapper,
  selectors,
  connect,
  memoize,
}) => {
  const actions = {
    increase: ({ id }) =>
      actionWrapper({
        type: INCREASE,
        id,
      }),
  };
  const container = connect(
    mapStateToProps(selectors, memoize),
    actions
  )(component);
  return {
    container,
    reducer,
    initialState: { count: 0 },
  };
};

components/counter/component.js:

import React from 'react';

export default props => (
  <div>
    <button onClick={() => props.increase(props)}>
      add
    </button>
    {props.count}
  </div>
);

标签: javascriptreactjsreduxreact-redux

解决方案


This problem was caused because I had a localStorage module that did dispatch actions but did not change the state, instead it would write to localStorage.

The module had selectors that would get the right data and the containers would use them to build the correct state but since the dispatched action did not change the state in the redux store react-redux would skip calling my mapState functions (probably memoizing state in Provider).

The solution is to let the root reducer return a new state reference {...state} so any action would cause the mapState functions to be called.


推荐阅读