无法将 Next.js 应用与 Redux 连接,无法呈现错误
Can't connect Next.js App with Redux, fails to render with err
我把它放在 C9 上是因为它有很多依赖关系,在那里阅读代码可能比在这里展示它更容易。
https://ide.c9.io/noman2000/showfer-media
参见 MainWrapper.jsx
、LibraryView.jsx
、Library.jsx
和 connect.js
。
应用程序本身在这里运行:
https://showfer-media-noman2000.c9users.io/
nextjs 团队在这里有一个示例应用程序:
https://github.com/zeit/next.js/tree/master/examples/with-redux
但我需要转换我的应用程序,这有点不同。当我这样做时它给我的错误是:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
in Unknown (created by Container)
in AppContainer (created by Container)
in Container (created by App)
in App
重要的一点。我有一条显示此组件的路线 Library.jsx
.
// @flow
import React from "react"
import PropTypes from "prop-types"
import routes from "./../../../server/routes/routes"
import commonApi from "./../../../app/common/common"
import LibraryItem from "./../../../app/components/library/LibraryItem"
import detectEnvironment from "./../../../app/common/detect-environment"
const { categoryContent } = commonApi
const { isMobileFromWidth } = detectEnvironment
const { Router } = routes
class Library extends React.Component<Props> {
componentWillMount() {
this.props.actions.setActiveCategory(null, "", -1)
}
authLibraryItems = ["favorites", "history", "queue"]
advtLabel = (label: string) => {
const { library } = this.props.state.toJS()
const sourceUrl = library.serviceTypes[label].source_url
const slashOrder = sourceUrl.substr(8).search("/") + 1
return sourceUrl.substr(8).slice(slashOrder)
}
render() {
const { auth, library, layout } = this.props.state.toJS()
const titles = library.serviceTypes.heading
const categoryClasses = isMobileFromWidth()
? categoryContent(layout.orientation)
: "category-content"
return (
<React.StrictMode>
<section>
<div className={categoryClasses}>
<div className="library-title-container">
<div className="library-title">{titles && titles.title}</div>
<div className="library-subtitle">{titles && titles.subtitle}</div>
</div>
<div className="library-menu scrolling-wrapper">
<h1>I can display!</h1>
</div>
</div>
</section>
</React.StrictMode>
)
}
}
export default Library
该组件包含在页面 LibraryView.jsx
中。
import connectWrapper from "./../app/redux/utils/connect"
import root from "./../app/redux/rootReducer"
import Library from './../app/components/main/Library'
import MainWrapper from './../app/components/main/MainWrapper'
const { rootActions } = root
export default MainWrapper(connectWrapper(rootActions, Library))
MainWrapper.jsx
看起来像这样:
// @flow
/* eslint-disable */
import root from "window-or-global"
import React from "react"
import { fromJS } from "immutable"
import { connect, Provider } from "react-redux"
import "babel-polyfill"
import storeConfiguration from "../../redux/configureStore"
import rootReducer from "../../redux/rootReducer"
import commonApi from "./../../common/common"
const { getOrCreateStore } = storeConfiguration
export default (...args) => (Component) => {
// First argument is initStore, the rest are redux connect arguments and get passed
const [initStore, ...connectArgs] = args
console.log(Component)
const ComponentWithRedux = (props = {}) => {
const { store, initialProps, initialState } = props
// Connect page to redux with connect arguments
const ConnectedComponent = connect.apply(null, connectArgs)(Component)
// Wrap with redux Provider with store
// Create connected page with initialProps
return React.createElement(
Provider,
{ store: store && store.dispatch ? store : getOrCreateStore(initStore, initialState) },
React.createElement(ConnectedComponent, initialProps)
)
}
ComponentWithRedux.getInitialProps = async (props = {}) => {
const isServer = checkServer()
const store = getOrCreateStore(initStore)
// Run page getInitialProps with store and isServer
const initialProps = Component.getInitialProps
? await Component.getInitialProps({ ...props, isServer, store })
: {}
return {
store,
initialState: store.getState(),
initialProps
}
}
return ComponentWithRedux
}
最后,connect.js
文件。
// @flow
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
import immutable from "immutable"
const connectWrapper = (actions: {}, view: any) => {
const mapStateToProps = state => ({ state })
const mapDispatchToProps = (dispatch: Function) => ({
actions: bindActionCreators(actions, dispatch),
})
return connect(mapStateToProps, mapDispatchToProps)(view)
}
export default connectWrapper
我想做的是能够将任何组件传递到 MainWrapper(connectWrapper(rootActions, <ANY REACT COMPONENT>))
并将其包装在 redux 存储中,但这是行不通的。
更新中:
这已由 MainWrapper()(connectWrapper(rootActions, <Any React Component>))
部分解决,但它非常不稳定。
如果我没理解错的话,您是在尝试为 next.js 构建自己的 redux-wrapper HOC。我建议使用 next-redux-wrapper 库,可以找到 here 和一个很好的例子。
您只需使用 next-redux-wrapper 提供的 withRedux HOC 包装所有页面组件,所有子组件都可以包装在 react-redux 提供的连接函数中,如文档中所述:
Use withRedux to wrap only top level pages! All other components should keep using regular connect function of React Redux.
我把它放在 C9 上是因为它有很多依赖关系,在那里阅读代码可能比在这里展示它更容易。
https://ide.c9.io/noman2000/showfer-media
参见 MainWrapper.jsx
、LibraryView.jsx
、Library.jsx
和 connect.js
。
应用程序本身在这里运行:
https://showfer-media-noman2000.c9users.io/
nextjs 团队在这里有一个示例应用程序:
https://github.com/zeit/next.js/tree/master/examples/with-redux
但我需要转换我的应用程序,这有点不同。当我这样做时它给我的错误是:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
in Unknown (created by Container)
in AppContainer (created by Container)
in Container (created by App)
in App
重要的一点。我有一条显示此组件的路线 Library.jsx
.
// @flow
import React from "react"
import PropTypes from "prop-types"
import routes from "./../../../server/routes/routes"
import commonApi from "./../../../app/common/common"
import LibraryItem from "./../../../app/components/library/LibraryItem"
import detectEnvironment from "./../../../app/common/detect-environment"
const { categoryContent } = commonApi
const { isMobileFromWidth } = detectEnvironment
const { Router } = routes
class Library extends React.Component<Props> {
componentWillMount() {
this.props.actions.setActiveCategory(null, "", -1)
}
authLibraryItems = ["favorites", "history", "queue"]
advtLabel = (label: string) => {
const { library } = this.props.state.toJS()
const sourceUrl = library.serviceTypes[label].source_url
const slashOrder = sourceUrl.substr(8).search("/") + 1
return sourceUrl.substr(8).slice(slashOrder)
}
render() {
const { auth, library, layout } = this.props.state.toJS()
const titles = library.serviceTypes.heading
const categoryClasses = isMobileFromWidth()
? categoryContent(layout.orientation)
: "category-content"
return (
<React.StrictMode>
<section>
<div className={categoryClasses}>
<div className="library-title-container">
<div className="library-title">{titles && titles.title}</div>
<div className="library-subtitle">{titles && titles.subtitle}</div>
</div>
<div className="library-menu scrolling-wrapper">
<h1>I can display!</h1>
</div>
</div>
</section>
</React.StrictMode>
)
}
}
export default Library
该组件包含在页面 LibraryView.jsx
中。
import connectWrapper from "./../app/redux/utils/connect"
import root from "./../app/redux/rootReducer"
import Library from './../app/components/main/Library'
import MainWrapper from './../app/components/main/MainWrapper'
const { rootActions } = root
export default MainWrapper(connectWrapper(rootActions, Library))
MainWrapper.jsx
看起来像这样:
// @flow
/* eslint-disable */
import root from "window-or-global"
import React from "react"
import { fromJS } from "immutable"
import { connect, Provider } from "react-redux"
import "babel-polyfill"
import storeConfiguration from "../../redux/configureStore"
import rootReducer from "../../redux/rootReducer"
import commonApi from "./../../common/common"
const { getOrCreateStore } = storeConfiguration
export default (...args) => (Component) => {
// First argument is initStore, the rest are redux connect arguments and get passed
const [initStore, ...connectArgs] = args
console.log(Component)
const ComponentWithRedux = (props = {}) => {
const { store, initialProps, initialState } = props
// Connect page to redux with connect arguments
const ConnectedComponent = connect.apply(null, connectArgs)(Component)
// Wrap with redux Provider with store
// Create connected page with initialProps
return React.createElement(
Provider,
{ store: store && store.dispatch ? store : getOrCreateStore(initStore, initialState) },
React.createElement(ConnectedComponent, initialProps)
)
}
ComponentWithRedux.getInitialProps = async (props = {}) => {
const isServer = checkServer()
const store = getOrCreateStore(initStore)
// Run page getInitialProps with store and isServer
const initialProps = Component.getInitialProps
? await Component.getInitialProps({ ...props, isServer, store })
: {}
return {
store,
initialState: store.getState(),
initialProps
}
}
return ComponentWithRedux
}
最后,connect.js
文件。
// @flow
import { connect } from "react-redux"
import { bindActionCreators } from "redux"
import immutable from "immutable"
const connectWrapper = (actions: {}, view: any) => {
const mapStateToProps = state => ({ state })
const mapDispatchToProps = (dispatch: Function) => ({
actions: bindActionCreators(actions, dispatch),
})
return connect(mapStateToProps, mapDispatchToProps)(view)
}
export default connectWrapper
我想做的是能够将任何组件传递到 MainWrapper(connectWrapper(rootActions, <ANY REACT COMPONENT>))
并将其包装在 redux 存储中,但这是行不通的。
更新中:
这已由 MainWrapper()(connectWrapper(rootActions, <Any React Component>))
部分解决,但它非常不稳定。
如果我没理解错的话,您是在尝试为 next.js 构建自己的 redux-wrapper HOC。我建议使用 next-redux-wrapper 库,可以找到 here 和一个很好的例子。
您只需使用 next-redux-wrapper 提供的 withRedux HOC 包装所有页面组件,所有子组件都可以包装在 react-redux 提供的连接函数中,如文档中所述:
Use withRedux to wrap only top level pages! All other components should keep using regular connect function of React Redux.