State seems to be getting overwritten instead of merged

I'm currently trying to learn Redux and I think I must be implementing it wrong as only the last component to be rendered on the page actually works. I suspect the state is being overwritten each time.

I followed this tutorial which only uses one example of data retrieval so I have probably just misunderstood how to write to the state.

Here is my reducer. Any ideas where I've gone wrong? Also this all seems very verbose, is there a DRYer way of writing this out?

import Immutable from 'seamless-immutable';
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';

// Import actions
import {
  REQUEST_CONFIG,
  GET_CONFIG,
  REQUEST_PAGES,
  GET_PAGES,
  REQUEST_SOCIAL_LINKS,
  GET_SOCIAL_LINKS,
  REQUEST_NAV,
  GET_NAV
} from './actions';

const configInitialState = Immutable({
  items: [],
  isFetching: false
})

const pagesInitialState = Immutable({
  items: [],
  isFetching: false
})

const socialLinksInitialState = Immutable({
  items: [],
  isFetching: false
})

const navInitialState = Immutable({
  items: [],
  isFetching: false
})

export function config(state = configInitialState, action) {
  switch (action.type) {
    case GET_CONFIG :
      return Immutable(state).merge({
        items: action.payload.config[0],
        isFetching: false
      })
    case REQUEST_CONFIG :
      return Immutable(state).merge({
        isFetching: true
      })
    default:
      return state;
  }
}

export function pages(state = pagesInitialState, action) {
  switch (action.type) {
    case GET_PAGES :
      return Immutable(state).merge({
        items: action.payload.pages,
        isFetching: false
      })
    case REQUEST_PAGES :
      return Immutable(state).merge({
        isFetching: true
      })
    default:
      return state;
  }
}

export function socialLinks(state = socialLinksInitialState, action)      {
  switch (action.type) {
    case GET_SOCIAL_LINKS :
      return Immutable(state).merge({
        items: action.payload.socialLinks,
        isFetching: false
      })
    case REQUEST_SOCIAL_LINKS :
      return Immutable(state).merge({
        isFetching: true
      })
    default:
      return state;
  }
}

export function nav(state = navInitialState, action) {
  switch (action.type) {
    case GET_NAV :
      return Immutable(state).merge({
        items: action.payload.nav,
        isFetching: false
      })
    case REQUEST_NAV :
      return Immutable(state).merge({
        isFetching: true
      })
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  config,
  pages,
  socialLinks,
  nav,
  routing: routerReducer
});

export default rootReducer;

Just in case it's required, here is an example of one of my components as well:

import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { navLoad } from '../../../scripts/actions';

export default class HeaderNav extends React.Component {
  componentWillMount() {
    const { dispatch } = this.props;
    dispatch(navLoad());
  }

  render() {
    const { nav } = this.props;
    const navitems = nav && nav.items ? nav.items.asMutable().map((item) => {
      if(item.inNav === 'header' || item.inNav === 'both') {
        return <li key={item._id}><Link to={item.slug}>{item.name}</Link></li>
      }
    }) : null;
    if(nav.isFetching) {
      return(
        <section class="loader">
          <span>Content is loading...</span>
        </section>
      )
    } else {
      return(
        <nav class="c-primary-nav">
          <span class="c-primary-nav_toggle">Menu</span>
          <ul>
            { navitems }
          </ul>
        </nav>
      )
    }
  }
}

function select(state) {
  const { nav } = state;
  return {
    nav
  };
}

export default connect(select)(HeaderNav);

and here is where the components are called:

import React from 'react';
import HeaderNav from './Header/nav.jsx';
import SocialLinks from './Header/socialLinks.jsx';

export default class Header extends React.Component {
  render() {
    return(
      <header class="c-global-header" role="banner">
        <HeaderNav />
        <SocialLinks />
      </header>
    )
  }
}

Update: I'm still hoping for an answer to this question as it's still stumping me, I've altered the code a little now but to no avail. You can see my full codebase here: https://github.com/alexward1981/portfolio-react (All the relevant stuff is in the 'src' folder)


我只是遇到同样的问题,问题是在reducer中,而不是默认开关情况下的返回状态部分,我返回initialState。

export const getAppShopReducer = (state = initialState, action) => {
  switch (action.type) {
    case types.FETCH_APP_SHOP_SUCCESS:

      return state
        .set('loading', false)
        .set('user', action.payload.user)
        .set('appShop', action.payload.appShop)
        .set('shop', action.payload.shop)
        ;

    case types.FETCH_APP_SHOP_REQUEST:

      return initialState.set('loading', true);

    default:
      return state; // Here's the problem, it was `initialState`;
  }
};

If your problem is that you want to get new data for a component when the page updates, then the data-fetching in componentWillMount() is not enough (because it's only called on 1st rendering of the component).

You also have to add data-fetching in another component's life-cycle method, componentWillReceiveProps():

componentWillReceiveProps(nextProps) {
  const { dispatch } = this.props;
  dispatch(navLoad());
}

You could add a proper condition (comparing nextProps with this.props) to check if you really need to fetch new data on each update.


I'm not familiar with seamless-immutable, but your problem may be related to its usage in your reducers. Are you sure that this library really produces new object references when state changes, as it is required by redux?

Anyway, you may have to use more code to properly combine redux and seamless-immutable, like this library does.

To simplify things, I think you should try to use plain JS immutability patterns (with Object.assign(), or object spread syntax for example) instead of seamless-immutable. For example, you could write your reducers like this:

const navInitialState = {
  items: [],
  isFetching: false
}

function nav(state = navInitialState, action) {
  switch (action.type) {
    case GET_NAV :
      return {
        ...state,
        items: action.payload.nav,
        isFetching: false
      }
    case REQUEST_NAV :
      return {
        ...state,
        isFetching: true
      }
    default:
      return state
  }
}
链接地址: http://www.djcxy.com/p/91538.html

上一篇: C#vs Java Enum(适用于C#的新手)

下一篇: 状态似乎被覆盖而不是合并