1
votes

I just started leaning React Native + Redux. It seems I successfully updated the state but the view does not change(not re-rendered). Could anyone give me an advice to solve this...?

I tried to debug with using console.log and tap the screen.

"selectedMenuMap": Object { "item2": true, "item3": false, "}, This is just a part of the logs and I could see the state is actually changed...

Reducer.js

import { SELECT_MENU } from './actions.js';

const initialState = {
  menu:
  [
    {key: 'item1', title: 'AAA',
    detail: [
      {key: 'detail_item1', title: 'AA', price: '1000', time: '30'},
      {key: 'detail_item2', title: 'AB', price: '1100', time: '35'},
      {key: 'detail_item3', title: 'AC', price: '1200', time: '40'},
    ]},
    {key: 'item2', title: 'BBB',
    detail: [
      {key: 'detail_item1', title: 'BA', price: '1000', time: '30'},
      {key: 'detail_item2', title: 'BB', price: '1100', time: '35'},
      {key: 'detail_item3', title: 'BC', price: '1200', time: '40'},
    ]},
    {key: 'item3', title: 'CCC',
    detail: [
      {key: 'detail_item1', title: 'CA', price: '1000', time: '30'},
      {key: 'detail_item2', title: 'CB', price: '1100', time: '35'},
      {key: 'detail_item3', title: 'CC', price: '1200', time: '40'},
    ]},
  ],
  selectedMenuMap: {} 
};

function menuList(state = initialState, action) {
  switch (action.type) {
    case SELECT_MENU:
    console.log('state');
      let newState = Object.assign({}, state);
      newState.selectedMenuMap[action.key] = !state.selectedMenuMap[action.key];
      console.log(newState === state);
      return newState;
    default:
      return state;
  }
}

export default menuList;

Container.js

import MenuList from '../components/MenuList.js';
import { selectMenu } from '../actions.js';
import { connect } from 'react-redux';

const mapStateToProps = state => ({
    menu: state.menu,
    selectedMenuMap: state.selectedMenuMap,
});

const mapDispatchToProps = dispatch => {
  return {
    onPressMenu: key => {
      dispatch(selectMenu(key))
    },
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(MenuList);

components.js

import React, { Component, Purecomponent } from 'react';
import {
  FlatList,
} from 'react-native';
import MenuItem from './MenuItem.js';

export default class MenuList extends React.PureComponent {
  _renderItem = ({item}) => (
  <MenuItem
    id={item.key}
    onPressItem={() => this.props.onPressMenu(item.key)}
    selected={this.props.selectedMenuMap[item.key]}
    title={item.title}
    detail={item.detail}
  />
  );

  render() {console.log(this.props);
    return (
      <FlatList
        data={this.props.menu}
        renderItem={this._renderItem}
      />
    );
  }
}

Is this a problem???state and newState are the same??

function menuList(state = initialState, action) {
  switch (action.type) {
    case SELECT_MENU:
    console.log('reducer');
      let newState = Object.assign({}, state);
      newState.selectedMenuMap[action.key] = !state.selectedMenuMap[action.key];
      console.log('oldState');
      console.log(state.selectedMenuMap);
      console.log('newState');
      console.log(newState.selectedMenuMap);
      console.log(newState.selectedMenuMap === state.selectedMenuMap);
      return newState;
    default:
      return state;
  }
}

17:35:33: reducer
17:35:33: oldState
17:35:33: Object {
17:35:33:   "item3": true,
17:35:33: }
17:35:33: newState
17:35:33: Object {
17:35:33:   "item3": true,
17:35:33: }
17:35:33: true
1
Could you also add the code for the MenuList component? The redux code seems to be correctdentemm
@dentemm Thanks for your help! Did it!Jackie Kim

1 Answers

3
votes

Object.assign() only performs a shallow copy. So this statement doesn't work as you expect.

  let newState = Object.assign({}, state);

While newState and state are different objects, the nested newState.selectedMenuMap points to the same object as state.selectedMenuMap. You cannot mutate one without mutating both.

Instead make sure you do not modify the original state. Something like this should work.

const newState = { ...state,
  selectedMenuMap: { ...state.selectedMenuMap,
    [action.key]: !state.selectedMenuMap[action.key]
  }
}

Each spread operation {...foo} does the same as Object.assign({}, foo), so we create a shallow copy of both state and then of state.selectedMenuMap. Now state.selectedMenuMap and newState.selectedMenuMap are pointing to different objects, and redux will know that components that connects to this part of the state tree should be rerendered.

This looks a bit complicated, and it might not be obvious whether the original state is mutated or not here.

One way to avoid mutating existing state when updating nested state slices is to use a library such as Ramda.js which never mutates the objects passed in as arguments.

const newState = R.over(R.lensPath(['selectedMenuMap', action.key]), R.not, state)