Массив становится неопределенным - React Redux

я новичок в React и Redux. Пытаюсь понять основы и сделать несколько простых примеров, но я застрял в этой проблеме более одного дня и не могу найти решение. Я думаю, что моя ошибка — глупая ошибка.

Проблема в том, что я не могу распечатать массив пользователей. При отладке переменная users загружается со всеми исправленными идентификаторами и пользователями, но после трехкратного выполнения <li key={id}>{name}</li> возвращается к forEach и выдает следующее исключение: Uncaught TypeError: Невозможно прочитать свойство forEach для undefined, где users не определено. И я также получаю сообщение об ошибке, соответствующее PropTypes: Недопустимый пользователь реквизита массива типов, указанный на домашней странице, ожидаемый объект

Вот код:

сохранить/настроитьStore.js

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';

const initialState = {};

const middleware = [thunk];

const store = createStore(rootReducer, initialState, compose(
    applyMiddleware(...middleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
));

export default store;

редукторы/index.js

import { combineReducers } from 'redux';
import userReducer from './userReducer';
//import groupReducer from './groupReducer';

export default combineReducers({
    user: userReducer
});

редьюсеры/userReducer.js

import { GET_USERS, ADD_USER, DELETE_USER } from '../actions/types';

const initialState = {
    users: [
        { id: 1, name: 'brunao'},
        { id: 2, name: 'flavio'},
        { id: 3, name: 'dudu'}
    ]
};

export default function(state = initialState, action) { 
    switch (action.type) {
        case GET_USERS:
            return [
                ...state
            ];
        default:
            return state;
    }
}

действия/usersAction.js

import { GET_USERS, ADD_USER, DELETE_USER } from './types';

export const getUsers = () => {
    return {
        type: GET_USERS
    };
};

components/HomePage.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUsers } from '../actions/usersActions';
import PropTypes from 'prop-types';

class HomePage extends Component {

    componentDidMount() {
        this.props.getUsers();
    }

    render() {
        const { users } = this.props.user;
        return(
            <div>
                <h3>Users</h3>
                <ul>
                    {users.forEach(({id, name}) => (
                        <li key={id}>{name}</li>
                    ))}
                </ul>
            </div>
        );
    }

}

HomePage.propTypes = {
    getUsers: PropTypes.func.isRequired,
    user: PropTypes.object.isRequired
}

const mapStateToProps = (state) => ({
    user: state.user
});

export default connect(mapStateToProps, { getUsers })(HomePage);

person Bruno Paiva    schedule 21.07.2018    source источник


Ответы (2)


Вы возвращаете неправильную форму состояния в своем редукторе. Ваш связанный код:

export default function(state = initialState, action) { 
    switch (action.type) {
        case GET_USERS:
            return [
                ...state
            ];
        default:
            return state;
    }
}

Здесь состояние — это объект, но вы возвращаете его в массиве, распространяя его. Итак, ваше состояние нарушено.

Попробуйте так:

case GET_USERS:
    return state;

Как указал в своем ответе @Idan Dagan, на самом деле мы не изменяем состояние в наших редюсерах. Я просто дал это предложение, поскольку вы просто играете, чтобы изучить Redux, и здесь мы возвращаем исходное состояние, не более того. Но это подходящий и лучший способ вернуть состояние:

case GET_USERS:
    return { ...state };

Вот рабочий код: https://codesandbox.io/s/k5ymxwknpv

Я также снова изменил forEach на map, как предложил @Idan Dagan. Я этого не понимал. forEach здесь не подходит, так как на самом деле он ничего не возвращает. Вы хотите сопоставить свои массивы в React и визуализировать их.

Кроме того, ваше название штата сбивает с толку :) user.users немного странно, вы можете придумать что-то получше.

Редактировать после комментариев

Ваше действие GET_USERS на самом деле выполняется, но вы неправильно проверяете его в своем коде. Ты делаешь:

export default function(state = initialState, action) {
  switch (action.type) {
    case GET_USERS:
      return Object.assign({}, state);
    default:
      return {
        users: [
          { id: 1, name: "TEST" },
          { id: 2, name: "TEST1" },
          { id: 3, name: "TEST2" }
        ]
      };
  }
}

Что здесь происходит? Первое действие Redux — INIT. Это инициализация вашего состояния. Теперь, поскольку нет определенного действия, ваш редуктор попадает в случай по умолчанию и возвращает TEST. Теперь ваше состояние становится данными TEST. Затем ваш GET_USERS попадает, и вы возвращаете новый объект, который объединяет состояние, которое является TEST. Вот шаги:

First, state is the `initialState` -> `INIT` runs and hits the default case
State is now TEST one -> GET_USERS hit and returns the `state` which is TEST one
You see the TEST one.

Как проверить свои действия? Просто поместите console.log в свой редуктор:

export default function(state = initialState, action) {
  console.log("state",state);
  console.log("action",action);
  .....

и увидеть, что GET_USERS на самом деле попал. Другой вариант — вместо того, чтобы возвращать объединенный объект с помощью state, попробуйте объединить его с initialState или с помощью оператора распространения вернуть новый объект, используя initialState:

return return Object.assign({}, initialState);

or

return {...initialState}

Последний вариант дает вам немного больше понимания того, как работает мое первое объяснение. Попробуйте вернуть это для вашего GET_USERS:

return {...state, users:[...state.users, {id:4, name: "foo"}]};

Вы увидите список пользователей с данными TEST, но последним будет ваш foo. Это объясняет, как вы теряете initialState, если возвращаете что-то помимо state в случае по умолчанию.

Последнее предложение: вы можете отлаживать свою разработку Redux с помощью инструментов разработки Redux. Это отличный инструмент, который делает гораздо больше, чем отладка. Вы можете легко отслеживать все свои операции для Redux.

person devserkan    schedule 21.07.2018
comment
Да, это исправление внутри return остановило ошибку, теперь я получаю пользователей. Но я сделал тест, и я получаю пользователей из-за опции по умолчанию внутри переключателя, я не отправляю действие GET_USERS.. Я попытался изменить ComponentDidMount() на ComponentWillMount(), но безуспешно. - person Bruno Paiva; 21.07.2018
comment
@BrunoPaiva ComponentWillMount считается устаревшим, и вам следует избегать их. - person Idan Dagan; 21.07.2018
comment
DidMount или WillMount на это никак не влияют. И, как уже было сказано, WillMount теперь является наследием. Как вы отслеживаете свои действия? Вы уверены, что не попал? Можем ли мы увидеть ваш файл types? Кроме того, не могли бы вы разместить все свое приложение на codesandbox.io, чтобы мы могли его изучить. - person devserkan; 21.07.2018
comment
Я отредактировал свой ответ и добавил пример рабочего кода и коробки. Кроме того, я изменил forEach на map в соответствии с предложением @IdanDagan. Хороший улов! - person devserkan; 21.07.2018
comment
codesandbox.io/s/my6k6o037p Вот код.. я изменил возврат по умолчанию внутри переключателя и убедитесь, что действие GET_USERS не вызывается.. И спасибо вам обоим за помощь!! Очень ценю! - person Bruno Paiva; 22.07.2018
comment
Я отредактировал свой ответ и попытался объяснить, как на самом деле попадает ваш GET_USERS. - person devserkan; 22.07.2018
comment
@devserkan лучше отлаживать действия через Redux DevTool для хрома. В любом случае, я дал вам голосование :). - person Idan Dagan; 22.07.2018
comment
@IdanDagan, я полностью с тобой согласен. Я использую Redux Dev Tools всякий раз, когда использую Redux, это отличный инструмент. Здесь я просто хочу показать простой способ, поскольку OP пытается отладить проблему в редюсере. В любом случае, я отредактировал свой ответ и упомянул о Redux Dev Tools. Спасибо. - person devserkan; 22.07.2018
comment
Спасибо вам, ребята, за этот невероятный урок :) Я не знал, что действие INIT вызывается первым! Теперь редукс мне более понятен. Еще раз спасибо и хорошего дня. - person Bruno Paiva; 22.07.2018

Я не уверен, что вы пытаетесь сделать с новым состоянием.

Но есть пара изменений, которые вам нужно сделать:

  1. Используйте map вместо forEach (потому что вы хотите вернуть новый массив).
  2. В редукторе вы можете вернуть состояние следующим образом:

export default function(state = initialState, action) { 
    switch (action.type) {
        case GET_USERS:
            return Object.assign({}, state);
        default:
            return state;
    }
}

как упоминается в документах redux:

Мы не изменяем состояние. Создаем копию с Object.assign().

person Idan Dagan    schedule 21.07.2018
comment
Спасибо @Idan за помощь. Как я уже говорил выше, по-прежнему не вызывается действие GET_USERS.. - person Bruno Paiva; 22.07.2018