Dox
Dox

Reputation: 591

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!

Upvotes: 2

Views: 2497

Answers (3)

Dox
Dox

Reputation: 591

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;

Upvotes: 0

user6904754
user6904754

Reputation:

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.

Upvotes: 0

markerikson
markerikson

Reputation: 67539

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.

Upvotes: 4

Related Questions