Redux-observable TypeError: невозможно прочитать тип свойства undefined

Я пытался реализовать react рендеринг на стороне сервера, используя далее, и я использую пример с-redux-observable-app, пример работает нормально, но я хотел бы немного улучшить проект, выполнив

  1. модульный шаблон redux
  2. фрактальная структура проекта
  3. Если возможно, я хотел бы реализовать компоненты без сохранения состояния.
  4. Поскольку № 2, я больше не могу использовать жизненный цикл состояния реакции, чтобы решить эту проблему. я обычно пользовался преимуществами React Router onEnter props, но это предполагает, что я должен используйте componentWillMount, что не соответствует моему условию №2

Я разместил проект на github, и эта конкретная проблема зафиксирована на этом филиал

Вот краткое изложение того, что я сделал до сих пор

достичь #1

// ./redux/index.js
...

import rootEpics from './root/epics'
import rootReducers from './root/reducers'

export default function initStore(initialState) {
  const epicMiddleware = createEpicMiddleware(rootEpics)
  const logger = createLogger({ collapsed: true })
  const middlewares = applyMiddleware(thunkMiddleware, epicMiddleware, logger)

  return createStore(rootReducers, initialState, middlewares)
}

// ./redux/root/epics.js
import { fetchCharacterEpic, startFetchingCharactersEpic } from '../ducks/Character/epics'

const rootEpics = combineEpics(
  fetchCharacterEpic,
  startFetchingCharactersEpic,
)

export default rootEpics

// ./redux/root/reducers.js
import { combineReducers } from 'redux'
import Character from '../ducks/Character'

const rootReducers = combineReducers({
  Character,
})

export default rootReducers


// ./redux/ducks/Character/index.js
import * as types from './types'

const INITIAL_STATE = {
  data: {},
  error: {},
  id: 1,
}

const Character = (state = INITIAL_STATE, { type, payload }) => {
  switch (type) {
    case types.FETCH_CHARACTER_SUCCESS:
      return {
        ...state,
        data: payload.response,
        id: state.id + 1,
      }
    case types.FETCH_CHARACTER_FAILURE:
      return {
        ...state,
        error: payload.error,
      }
    default:
      return state
  }
}

export default Character

// ./redux/ducks/Character/types.js
export const FETCH_CHARACTER = 'FETCH_CHARACTER'
export const FETCH_CHARACTER_SUCCESS = 'FETCH_CHARACTER_SUCCESS'
export const FETCH_CHARACTER_FAILURE = 'FETCH_CHARACTER_FAILURE'
export const START_FETCHING_CHARACTERS = 'START_FETCHING_CHARACTERS'
export const STOP_FETCHING_CHARACTERS = 'STOP_FETCHING_CHARACTERS'

// ./redux/ducks/Character/actions.js
import * as types from './types'

export const startFetchingCharacters = () => ({
  type: types.START_FETCHING_CHARACTERS,
})

export const stopFetchingCharacters = () => ({
  type: types.STOP_FETCHING_CHARACTERS,
})

export const fetchCharacter = id => ({
  type: types.FETCH_CHARACTER,
  payload: { id },
})

export const fetchCharacterSuccess = response => ({
  type: types.FETCH_CHARACTER_SUCCESS,
  payload: { response },
})

export const fetchCharacterFailure = error => ({
  type: types.FETCH_CHARACTER_FAILURE,
  payload: { error },
})

// ./redux/ducks/Character/epics.js
import 'rxjs'
import { of } from 'rxjs/observable/of'
import { takeUntil, mergeMap } from 'rxjs/operators'
import { ofType } from 'redux-observable'
import ajax from 'universal-rx-request'

import * as actions from './actions'
import * as types from './types'

export const startFetchingCharactersEpic = action$ => action$.pipe(
  ofType(types.START_FETCHING_CHARACTERS),
  mergeMap(() => action$.pipe(
    mergeMap(() => of(actions.fetchCharacter())),
    takeUntil(ofType(types.STOP_FETCHING_CHARACTERS)),
  )),
)

export const fetchCharacterEpic = (action$, id) => action$.pipe(
  ofType(types.FETCH_CHARACTER),
  mergeMap(() => ajax({
    url: 'http://localhost:8010/call',
    method: 'post',
    data: {
      method: 'get',
      path: `people/${id}`,
    },
  })
    .map(response => actions.fetchCharacterSuccess(
      response.body,
      true,
    ))
    .catch(error => of(actions.fetchCharacterFailure(
      error.response.body,
      false,
    )))),
)

для достижения # 2

// ./pages/index/container/index.js
import React from 'react'
import { connect } from 'react-redux'
import { of } from 'rxjs/observable/of'

import rootEpics from '../../../redux/root/epics'
import { fetchCharacter } from '../../../redux/ducks/Character/actions'

import Index from '../component'

const mapStateToProps = state => ({
  id: state.Character.id,

})

const mapDispatchToProps = dispatch => ({
  async setInitialCharacter(id) {
    const epic = of(fetchCharacter({ id }))
    const resultAction = await rootEpics(
      epic,
      id,
    ).toPromise()
    dispatch(resultAction)
  },
})

export default connect(mapStateToProps, mapDispatchToProps)((props) => {
  props.setInitialCharacter(props.id)
  return (<Index />)
})


// ./pages/index/component/index.js
import React from 'react'
import Link from 'next/link'
import Helmet from 'react-helmet'

import Info from '../container/info'

const Index = () => (
  <div>
    <Helmet
      title="Ini index | Hello next.js!"
      meta={[
        { property: 'og:title', content: 'ini index title' },
        { property: 'og:description', content: 'ini index description' },
      ]}
    />
    <h1>Index Page</h1>
    <Info />
    <br />
    <nav>
      {/* eslint-disable jsx-a11y/anchor-is-valid */}
      <Link href="/other"><a>Navigate to other</a></Link><br />
      <Link href="/about"><a>Navigate to about</a></Link>
      {/* eslint-enable jsx-a11y/anchor-is-valid */}
    </nav>
  </div>
)

export default Index


// ./pages/index/container/info.js
import { connect } from 'react-redux'

import Info from '../../../components/Info'

const mapStateToProps = state => ({
  data: state.Character.data,
  error: state.Character.error,
})

export default connect(mapStateToProps)(Info)

с теми, что выше, выборка работает нормально, но...

я не хочу, чтобы выборка продолжалась, я хочу, чтобы она выполнялась только один раз onEnter.

В попытке добиться этого я написал эпопею под названием startFetchingCharactersEpic() и действие под названием startFetchingCharacters() и, наконец, добавил mergeMap(() => of(actions.stopFetchingCharacters())), в конце аргументов fetchCharacterEpic() pipe, имея в виду следующий сценарий.

  1. отправить action.startFetchingCharacters() в контейнере
  2. это вызовет startFetchingCharactersEpic()
  3. так будет до types.STOP_FETCHING_CHARACTERS
  4. который отправит action.fetchCharacter()
  5. это вызовет fetchCharacterEpic()
  6. который отправит action.stopFetchingCharacters()
  7. это вызовет #3

setInitialCharacter

// ./pages/index/container/index.js
const mapDispatchToProps = dispatch => ({
  async setInitialCharacter(id) {
    const epic = of(startFetchingCharacters())
    const resultAction = await rootEpics(
      epic,
      id,
    ).toPromise()
    dispatch(resultAction)
  },
})

но, сделав это, я получил TypeError: Cannot read property 'type' of undefined, консоль не дает мне достаточно информации, кроме как сказать, что ошибка исходит от setInitialCharacter

Пробовал гуглить проблему, но не нашел ничего, связанного с моей проблемой

ОБНОВЛЕНИЕ

Мне удалось заставить его снова работать на основе @jayphelps' ответ ниже , что вернуло меня к некоторым из моих первоначальных проблем, которые

  1. Как полностью использовать компонент без сохранения состояния без использования жизненного цикла состояния реакции, особенно замены onEnter
  2. Как просто вызвать fetchCharacterEpic только один раз при загрузке страницы

но я думаю, что эти 2 заслуживают отдельного поста, так как я понял, что задаю слишком много вопросов в этом посте.


person littlechad    schedule 12.02.2018    source источник


Ответы (1)


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

const resultAction = await rootEpics(
  epic,
  id,
).toPromise()
dispatch(resultAction)

В вашем вопросе не упоминается, но это означает, что у вас должно быть промежуточное программное обеспечение, которое перехватывает это обещание, поскольку избыточность (и наблюдаемая избыточность) ожидала только POJO { type: string }.

Также возможно, что обещание не разрешается ни к чему, кроме undefined, и в этом случае оператор ofType в ваших эпиках захлебнется, потому что он работает только с этими действиями POJO { type: string }.


Извините, я не могу помочь более конкретно, трудно понять, в чем заключается намерение.

например это await rootEpics(epic, id) кажется странным, поскольку rootEpics является корневым эпиком и ожидает, что аргументы будут (action$, store), а компоненты пользовательского интерфейса не должны напрямую вызывать эпики?

person jayphelps    schedule 12.02.2018
comment
да, я сам был немного смущен реализацией диспетчеризации и эпиков здесь, так как я использовал только пример nextjs (github.com/zeit/next.js/tree/canary/examples/), и я не собираюсь менять исходный код. Я пытался вызвать dispatch(startFetchingCharacters()) без вызова эпиков, но, похоже, он не вызывает startFetchingCharactersEpic , так как actions.fetchCharacter() не отправляется, я неправильно пишу startFetchingCharactersEpic? ps: спасибо, что указали, что компоненты пользовательского интерфейса не должны напрямую вызывать эпики - person littlechad; 13.02.2018