Кажется, я не могу понять, в чем проблема. У меня есть компонент под названием DisplayItems, который получает некоторые данные из API (список элементов).
Я использую действия Redux и промежуточное программное обеспечение thunk для получения элементов и данных из API. Я сохраняю элементы и другие данные в хранилище redux как массив объектов и называю его entriesData. Фактический массив находится в entriesData.data. Я использую connect для подключения состояния и действий к реквизитам DisplayItems.
Затем у меня есть дочерний компонент GalleryItemContentV1. GalleryItemContentV1 не подключен к redux. Он просто получает данные от DisplayItems, который просматривает массив элементов и передает каждый из них GalleryItemContentV1. Итак, снова DisplayItems проходит через this.props.entriesData.data и передает каждый элемент в GalleryItemContentV1 как опору под названием entry.
Наконец, у меня есть дочерний компонент в GalleryItemContentV1 под названием TestCom. Вот в чем проблема. Я также передаю предмет, который я получил от DisplyItem, на TestCom. Итак, поток данных: DisplayItems (подключен)> GalleryItemContentV1 (не подключен)> TestCom (подключен).
TestCom подключен к redux. Он использует некоторые действия из redux для обновления элемента в entriesData.data. Когда я обновляю this.props.entriesData.data в TestCom, GalleryItemContentV1 получает обновленные данные нормально, но TestCom ничего не получает. Пункт в TestCom никогда не обновляется.
Я понял, что проблема в том, когда я подключаю TestCom к redux. Если TestCom не подключен к redux, он также получает обновленные реквизиты, как и GalleryItemContentV1.
Компоненты ниже. Я постарался максимально упростить их.
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.info('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);
ГалереяItemContentV1
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
Результаты this.props.entry.VotesCount (элемент из DisplayItems) вывожу в TestCom. Вот где возникает проблема. По какой-то причине, когда я подключаю TestCom к redux, его реквизиты не обновляются, но когда он отключается от Redux, его реквизиты обновляются.
import React, {Component} from 'react';
import { connect } from 'react-redux';
class TestCom extends Component {
constructor(props) {
super(props);
}
render(){
console.info('TestCom props', this.props);
return (
<div>{this.props.entry.VotesCount}</div>
)
}
}
const mapStateToProps = state => (
{
user: state.user
}
)
export default connect(mapStateToProps)(TestCom);
Здесь я настраиваю redux, но я не думаю, что это актуально, поскольку GalleryItemContentV1 прекрасно видит обновленные данные.
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)
);
}
Итак, подведем итоги.
DisplayItems (подключен, получает обновленные данные из хранилища в порядке)> ГалереяItemContentV1 (не подключен, получает обновления в порядке)> TestCom (подключен, не получает обновления, но получает обновления, если отключен)
Вот мой редуктор, на всякий случай.
import * as ContestActionTypes from '../actiontypes/contest';
const initialState = {
contest: null,
subscriptions: [],
user: null,
page: null
}
export default function Contest(state=initialState, action) {
console.info('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;
}
}
Я также заметил, что то же самое происходит с GalleryItemContentV1. Если я подключу его, он перестанет получать обновления из DisplayItems / хранилища redux.
Любой вклад приветствуется. Спасибо!



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы мутируете свое состояние. Вы делаете неглубокую копию состояния верхнего уровня в этом редукторе среза, но вы не делаете копии всех вложенных уровней внутри. Затем подключенный компонент извлекает state.entriesData, который не изменился по ссылке. Итак, компонент-оболочка connect считает, что состояние вообще не изменилось, и не выполняет повторную визуализацию вашего собственного компонента.
Страница документации Redux "Неизменяемые шаблоны обновления" особо указывает на это как на частую ошибку.
Если постоянное обновление вложенного состояния слишком сложно, я бы посоветовал изучить Библиотека неизменяемых обновлений immer. Кроме того, наш новый redux-starter-kit пакет внутренне использует Immer, чтобы вы могли писать более простые редукторы.
Также см. Мой пост Идиоматический Redux: история и реализация React-Redux для получения информации и подробностей о том, как connect работает внутри.
Да, я тоже думаю, что вы мутируете свое состояние. Ниже представлен корпус TOGGLE_TODO от редуктора. Надеюсь, этот фрагмент кода подойдет для вашего кода.
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
})
})
Итак, в вашем случае, если идентификатор совпадает, то обновление выполняется с помощью action.vote. Также читайте о глубоких и неглубоких копиях объектов.
это должен быть принятый ответ, parse (stringify)) - довольно дорогостоящая операция, если объект слишком большой.
markerikson был прав, но я публикую здесь конкретный ответ, который мне помог.
Проблема заключалась в том, что я пытался обновить массив внутри объекта (entryData - это объект, а entryData.data - массив), используя обычный метод Object.assign ({}, state, {}), когда мне нужно было использовал JSON.parse (JSON.stringify (состояние)). Мне пришлось прочитать объяснение Mozilla (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign), чтобы понять, что происходит. Я видел решение JSON.parse раньше, но я действительно не понимал мелкого и глубокого клонирования, и, каким бы неглубоким клонированием ни было, я не думал, что я виновен в этом. Оказывается, был.
Вот мой обновленный редуктор, который отлично работает.
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;
Спасибо за фрагмент кода! Моя проблема заключалась в том, что я выполнял поверхностное клонирование с помощью Object.assign, когда мне следовало выполнять глубокое клонирование. Я не уверен, учитывает ли это опубликованный вами фрагмент кода, но я не думаю, что это так.