You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中使用Provider标签引发空白页面问题求助

问题描述

使用React.js结合Redux开发网页,用<Provider>标签包裹组件后页面变为空白,React开发者工具显示空白,控制台报错:

Uncaught Error: Expected the reducer to be a function.
at createStore (createStore.js:55:1)
at ./src/index.js (index.js:9:1)
at options.factory (react refresh:6:1)
at webpack_require (bootstrap:24:1)
at startup:7:1
at startup:7:1

相关代码如下:

Main.js

import Title from './Title'
import Photowall from './Photowall'
import AddPhoto from './AddPhoto'
import { Route } from 'react-router-dom'
class Main extends Component{

constructor(){
 super()
this.state= {
    posts : [{
        id: "0",
        description: "beautiful landscape",
        imageLink: "https://image.jimcdn.com/app/cms/image/transf/none/path/sa6549607c78f5c11/image/i4eeacaa2dbf12d6d/version/1490299332/most-beautiful-landscapes-in-europe-lofoten-european-best-destinations-copyright-iakov-kalinin.jpg" +
        "3919321_1443393332_n.jpg"
        }, {
        id: "1",
        description: "Aliens???",
        imageLink: "https://s3.india.com/wp-content/uploads/2017/12/rocket.jpg"
        }, {
        id: "2",
        description: "On a vacation!",
        imageLink: "https://fm.cnbc.com/applications/cnbc.com/resources/img/editorial/2017/08/24/104670887-VacationExplainsTHUMBWEB.1910x1000.jpg"
        }],
      
    }
    this.removePhoto=this.removePhoto.bind(this)
    }

removePhoto(postRemoved){
    console.log(postRemoved.description)
   this.setState((state) => ({
        posts: state.posts.filter(post => post !== postRemoved)
      }))
}

addPhoto(postSubmitted){
    this.setState(state => ({
        posts:state.posts.concat([postSubmitted])
    }))
}

componentDidMount(){
}
componentDidUpdate(prevProps,prevState){
    console.log(prevState.posts)
    console.log(this.state)
    }
   render() {        
   return (

    <div> 
    <Route exact path="/" render={() => (
        <div>
            <Title title={'Photowall'}/>
            <Photowall posts={this.state.posts} onRemovePhoto={this.removePhoto} onNavigate={this.navigate}/>
         </div>
   
   )}/>
 
  <Route path="/AddPhoto" render={({history})=>(
     <AddPhoto onAddPhoto={(addedPost)=>{
         this.addPhoto(addedPost)
        history.push('/')
     }}/>
    )}/>
      </div>

)
}
}    
export default Main

AddPhoto.js

import React, { Component } from "react"

class AddPhoto extends Component{
constructor(){
super()
this.handleSubmit=this.handleSubmit.bind(this)
}

handleSubmit(event){
event.preventDefault();
const imageLink = event.target.elements.link.value
const description = event.target.elements.description.value
const post = {
id:0,
description:description,
imageLink:imageLink          
}
if(description && imageLink){
this.props.onAddPhoto(post)
}
}

render(){
return(
<div>
<h1>Photowall</h1>
<div className="form">
 <form onSubmit={this.handleSubmit}>
 <input type="text" placeholder="Link" name="link"/>
 <input type="text" placeholder="Description" name="description"/>
 <button>Post</button>

 </form>

 </div>
 </div>
 )
 }
 }

export default AddPhoto

Photo.js

import React, {Component} from "react";
import PropTypes from 'prop-types'
function Photo(props){
const post =props.post
return <figure className="figure">
        <img className="photo" src={post.imageLink} alt={post.description}/>
        <figcaption><p>{post.description}</p></figcaption>
        <div className="button-container">
        <button className="remove-button" onClick={()=>{ 
           props.onRemovePhoto(post);
       }}> Remove </button>
        </div>
 </figure>

 }

 Photo.propTypes={
 post: PropTypes.object.isRequired,
 onRemovePhoto:PropTypes.func.isRequired
 }
 export default Photo

Photowall.js

import React,{ Component} from 'react'
import Photo from './Photo'
import PropTypes from 'prop-types'
import {Link} from 'react-router-dom'
function Photowall(props){
return <div>
<Link className='addIcon' to='/AddPhoto' ><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgMGMtNi42MjcgMC0xMiA1LjM3My0xMiAxMnM1LjM3MyAxMiAxMiAxMiAxMi01LjM3MyAxMi0xMi01LjM3My0xMi0xMi0xMnptNyAxNGgtNXY1aC00di01aC01di00aDV2LTVoNHY1aDV2NHoiLz48L3N2Zz4="></img> </Link>
{/*<button onClick={props.onNavigate} className='addIcon'> + </button>*/}
    <div className='photo-grid'>
    {props.posts.map((post, index) => <Photo key={index} post={post} onRemovePhoto={props.onRemovePhoto}/>)}
   </div>
   </div>
   }
   Photowall.propTypes={
   posts: PropTypes.array.isRequired,
   onRemovePhoto:PropTypes.func.isRequired
   }
   export default Photowall

index.js

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Main from './Components/Main'
import './styles/stylesheet.css'
import {BrowserRouter} from 'react-router-dom'
import {createStore} from 'redux'
import rootReducer from './redux/reducer'
import {Provider} from "react-redux";
const store = createStore(rootReducer)



ReactDOM.render(<Provider store={store}><BrowserRouter><Main/></BrowserRouter></Provider>, document.getElementById('root'));

reducer.js

import posts from '../data/posts'

 const postReducer = function posts(state = posts,action){
 return state

 }

 export default postReducer

问题分析与解决

报错Expected the reducer to be a function的核心原因是createStore接收到的rootReducer并非预期的函数,具体问题出在reducer.js中:

  1. 变量命名冲突:你导入了名为posts的数据数组,同时又将reducer函数命名为posts,这会导致函数被数组覆盖,最终导出的是数组而非reducer函数。

修正步骤:

修改reducer.js中的函数名,避免与导入的数组重名:

import posts from '../data/posts'

// 修改函数名,避免和导入的posts数组冲突
const postReducer = function(state = posts, action){
  return state
}

export default postReducer

同时确认../data/posts文件存在且正确导出数组,若该文件缺失或导出错误,也会导致reducer初始化失败。

额外优化建议:

  • 后续可逐步将Main组件的state迁移至Redux store,通过connect或useSelector/useDispatch来管理数据;
  • 给新增的post生成唯一ID(如时间戳、uuid库),避免ID重复问题。

内容的提问来源于stack exchange,提问作者ronin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 15:27:48