简体   繁体   中英

React Redux: Connected child component not receiving props

I can't seem to figure out what the issue is. I have a component called DisplayItems that gets some data from an API (a list of items).

I am using Redux actions and thunk middleware to get the items and data from the API. I save the items and other data to the redux store as an array of objects, and I name it entriesData . The actual array is in entriesData.data . I am using connect to connect the state and actions to DisplayItems ' props.

Next, I have a child component called GalleryItemContentV1 . GalleryItemContentV1 isn't connected to redux. It just receives the data from DisplayItems which loops through the array of items and passes each one to GalleryItemContentV1 . So, again, DisplayItems loops through this.props.entriesData.data and passes each item to GalleryItemContentV1 as a prop called entry .

Finally, I have a child component in GalleryItemContentV1 called TestCom . That's where the issue is. I am also passing the item that I got from DisplyItem to TestCom . So, the data flow is DisplayItems (connected) > GalleryItemContentV1 (not connected) > TestCom (connected).

TestCom is connected to redux. It uses some actions from redux to update an item in entriesData.data . When I update this.props.entriesData.data in TestCom , GalleryItemContentV1 receives the updated data just fine, but TestCom doesn't receive anything. The item in TestCom is never updated.

I figured out that the issue is when I connect TestCom to redux. If TestCom is not connected to redux, it also receives the updated props, just like GalleryItemContentV1 .

The components are below. I've tried to simplify them as much as possible.

DisplayItems

import React, {Component} from 'react';
import GalleryItemContentV1 from './GalleryItemContentV1';
import { connect } from 'react-redux';
import {getContestEntriesPageData} from '../../actions/contest';

class DisplayItems extends Component {
  componentDidMount(){
    this.props.getContestEntriesPageData(uri, search);
  }

  render(){

    console.log('DisplayItems props', this.props);

      return (
        <div>
          <div className="card-deck thumbnails" id="card-list">
            {  this.props.entriesData.data instanceof Array ? 
                  this.props.entriesData.data.map(function(object, i){
                    return <GalleryItemContentV1 
                      entry={object} key={i} 
                      arr_key={i}
                    />;
                  }, this) : ''
              }
          </div>
        </div>
      )
  }
}

const mapStateToProps = state => (
  {
    entriesData: state.entriesData,
  }
)

const mapDispatchToProps = (dispatch) => {
  return {
    getContestEntriesPageData: (uri, search) => {
      dispatch(getContestEntriesPageData(uri, search));
    }
  }
}

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

GalleryItemContentV1

import React, { Component } from 'react';
import TestCom from './TestCom';

class GalleryItemContentV1 extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
        <div>
        <TestCom entry={this.props.entry} />
      </div>
    );
    }
}
export default GalleryItemContentV1;

TestCom

I am outputting the results of this.props.entry.VotesCount (the item from DisplayItems ) in TestCom . That's where the issue occurs. For some reason, when I connect TestCom to redux, its props aren't updated, but when it's disconnected from Redux, then its props are updated.

import React, {Component} from 'react';
import { connect } from 'react-redux';

class TestCom extends Component {
  constructor(props) { 
    super(props);
  }

  render(){

    console.log('TestCom props', this.props);

    return (

      <div>{this.props.entry.VotesCount}</div>

    )
  }
}

const mapStateToProps = state => (
  {
    user: state.user
  }
)
export default connect(mapStateToProps)(TestCom);

Here's where I'm configuring redux, but I don't think it's relevant to know since GalleryItemContentV1 is seeing the updated data just fine.

import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import ContestReducer from '../reducers/contest';

export default function configureStore(initialState) {
  return createStore(
    ContestReducer,
    initialState,
    applyMiddleware(thunk)
  );
}

So, to summarize.

DisplayItems (is connected, receives updated data from store okay) > GalleryItemContentV1 (is not connected, receives updates okay) > TestCom (is connected, does not receive updates, but receives updates if disconnected)

Here's my reducer, just in case it helps.

import * as ContestActionTypes from '../actiontypes/contest';

const initialState = {
  contest: null,
  subscriptions: [],
  user: null,
  page: null
}
export default function Contest(state=initialState, action) {
  console.log('redux action reducer: ', action)
  switch(action.type) {
    case ContestActionTypes.HANDLE_VOTE:
      const newState = { ...state };
      if (newState.entriesData) {
        const index = newState.entriesData.data.findIndex(x => x.id === action.entry.id)
        newState.entriesData.data[index].VotesCount = action.vote;
      } else {
        newState.entry.VotesCount = action.vote;
      }

      return newState

      default:
        return state;
  }
}

I also noticed the same thing happening with GalleryItemContentV1 . If I connect it, then it stops receiving updates from DisplayItems/the redux store.

Any input is appreciated. Thanks!

You're mutating your state. You're making a shallow copy of the top-level state in that slice reducer, but you aren't making copies of all the nested levels inside. Then, your connected component is extracting state.entriesData , which hasn't changed by reference. So, the connect wrapper component thinks that the state hasn't changed at all, and doesn't re-render your own component.

The Redux docs page on "Immutable Update Patterns" specifically points this out as a common mistake.

If updating nested state immutably is too much of a pain, I'd suggest looking into the immer immutable update library . Also, our new redux-starter-kit package uses Immer internally to let you write simpler reducers.

Also, see my post Idiomatic Redux: The History and Implementation of React-Redux for background and details on how connect works internally.

Yes, I also think you are mutating your state. The following is the TOGGLE_TODO case from reducer. I hope this code snippet will work for your code.

  case TOGGLE_TODO:
  return Object.assign({}, state, {
    todos: state.todos.map((todo, index) => {
      if (index === action.index) {
        return Object.assign({}, todo, {
          completed: !todo.completed
        })
      }
      return todo
    })
  })

So, in your case if id matches then update is with action.vote . Also read about deep and shallow object copies.

markerikson was right, but I am posting the specific answer that helped me, here.

The problem was that I was trying to update an array inside of an object (entriesData being the object and entriesData.data being the array), using the normal method of Object.assign({}, state, {}) when I should have been using JSON.parse(JSON.stringify(state)). I had to read Mozilla's explanation ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign ) to understand what was going on. I saw the JSON.parse solution before, but I didn't really understand shallow and deep cloning, and, whatever shallow cloning was, I didn't think I was guilty of it. Turns out, I was.

Here's my updated reducer that works perfectly.

case ContestActionTypes.HANDLE_VOTE:
  const newState = JSON.parse(JSON.stringify(state));
  if (newState.entriesData) {
    const index = newState.entriesData.data.findIndex(x => x.id === action.entry.id)
    newState.entriesData.data[index].VotesCount = action.vote;
  } else {
    newState.entry.VotesCount = action.vote;
  }

  return newState;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM