redux-observable TypeError: Cannot read property 'type' of undefined
redux-observable TypeError: Cannot read property 'type' of undefined
我一直在尝试实现 react server-side-rendering using next, and i am using the with-redux-observable-app example,该示例工作正常,但我想通过
稍微改进项目
- redux modular pattern
- fractal project structure
- 如果可能的话,我想实现stateless components
- 因为#2,我不能再使用react state lifecycle, to solve that i usually took advantage of react router onEnter props, but this建议我应该使用
componentWillMount
,这不符合我的#2条件
我已经把项目放在github, with this particular problem committed on this branch
这是我到目前为止所做的总结
实现#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)
对于上面的那些,获取工作正常,但是...
我不希望提取保持 运行ning,我希望它 运行 一次 onEnter
.
作为实现这一目标的尝试,我写了一个名为 startFetchingCharactersEpic()
的史诗和一个名为 startFetchingCharacters()
的动作,最后在 fetchCharacterEpic()
的末尾添加 mergeMap(() => of(actions.stopFetchingCharacters())),
pipe
个参数,考虑以下场景
- 调度actions.startFetchingCharacters(),在容器
- 这将触发
startFetchingCharactersEpic()
- 这将持续到
types.STOP_FETCHING_CHARACTERS
- 将调度 actions.fetchCharacter()
- 这将触发
fetchCharacterEpic()
- 将调度 actions.stopFetchingCharacters()
- 这将触发#3
设置初始字符
// ./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
尝试用谷歌搜索问题,但没有找到与我的问题相关的内容
更新
我设法做到了 work again based on @jayphelps' 下面,这让我回到了我原来的一些问题,这些问题是
- 如何在不利用 react state lifecycle 的情况下充分使用无状态组件,尤其是替换
onEnter
- 如何在页面加载时只调用一次
fetchCharacterEpic
但我猜这 2 个值另一个 post,因为我意识到我在这个问题上问了太多问题 post
这里完全是猜测,但错误可能来自于您在这里发送 Promise:
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
你的问题没有提到,但这意味着你必须有拦截承诺的中间件,因为 redux(和 redux-observable)只需要 POJO { type: string }
.
也有可能 promise 没有解决 undefined
以外的任何问题,在这种情况下,您的 epics 中的 ofType
运算符将会阻塞,因为它只适用于那些POJO 操作 { type: string }
.
抱歉,我无法提供更具体的帮助,很难理解其意图。
例如这个 await rootEpics(epic, id)
看起来很奇怪,因为 rootEpics 是根史诗并且期望参数是 (action$, store)
并且 UI 组件不应该直接调用 epics?
我一直在尝试实现 react server-side-rendering using next, and i am using the with-redux-observable-app example,该示例工作正常,但我想通过
稍微改进项目- redux modular pattern
- fractal project structure
- 如果可能的话,我想实现stateless components
- 因为#2,我不能再使用react state lifecycle, to solve that i usually took advantage of react router onEnter props, but this建议我应该使用
componentWillMount
,这不符合我的#2条件
我已经把项目放在github, with this particular problem committed on this branch
这是我到目前为止所做的总结
实现#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)
对于上面的那些,获取工作正常,但是...
我不希望提取保持 运行ning,我希望它 运行 一次 onEnter
.
作为实现这一目标的尝试,我写了一个名为 startFetchingCharactersEpic()
的史诗和一个名为 startFetchingCharacters()
的动作,最后在 fetchCharacterEpic()
的末尾添加 mergeMap(() => of(actions.stopFetchingCharacters())),
pipe
个参数,考虑以下场景
- 调度actions.startFetchingCharacters(),在容器
- 这将触发
startFetchingCharactersEpic()
- 这将持续到
types.STOP_FETCHING_CHARACTERS
- 将调度 actions.fetchCharacter()
- 这将触发
fetchCharacterEpic()
- 将调度 actions.stopFetchingCharacters()
- 这将触发#3
设置初始字符
// ./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
尝试用谷歌搜索问题,但没有找到与我的问题相关的内容
更新
我设法做到了 work again based on @jayphelps'
- 如何在不利用 react state lifecycle 的情况下充分使用无状态组件,尤其是替换
onEnter
- 如何在页面加载时只调用一次
fetchCharacterEpic
但我猜这 2 个值另一个 post,因为我意识到我在这个问题上问了太多问题 post
这里完全是猜测,但错误可能来自于您在这里发送 Promise:
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
你的问题没有提到,但这意味着你必须有拦截承诺的中间件,因为 redux(和 redux-observable)只需要 POJO { type: string }
.
也有可能 promise 没有解决 undefined
以外的任何问题,在这种情况下,您的 epics 中的 ofType
运算符将会阻塞,因为它只适用于那些POJO 操作 { type: string }
.
抱歉,我无法提供更具体的帮助,很难理解其意图。
例如这个 await rootEpics(epic, id)
看起来很奇怪,因为 rootEpics 是根史诗并且期望参数是 (action$, store)
并且 UI 组件不应该直接调用 epics?