React - TypeError:this.props.courses.map不是函数

时间:2017-06-07 16:38:47

标签: javascript reactjs http-headers react-redux

我创建了一个react-redux应用程序。目前它所做的是从服务器(api)加载课程,并将它们显示到课程组件​​。这非常有效。我试图添加一个功能,您可以通过将其发布到服务器来创建课程,然后服务器将成为一个成功对象。但是,当我发布到服务器时,我得到以下错误(见下文)。我认为这是由于我的connect语句监听加载课程操作。显然它认为应该得到一些东西,而不是成功对象。我已经尝试了一些事情来听它们的课程和成功的反应,但是为了节省你阅读我所做的所有奇怪事情的时间,我无法让它发挥作用。任何人都知道如何解决这个问题?

错误

TypeError: this.props.courses.map is not a function

course.component.js

    onSave(){
        // this.props.createCourse(this.state.course); 
        this.props.actions.createCourse(this.state.course); 
    }
    render() {
        return (
            <div>
                <div className="row">
                    <h2>Couses</h2>
                    {this.props.courses.map(this.courseRow)}
                    <input 
                        type="text"
                        onChange={this.onTitleChange}
                        value={this.state.course.title} />
                    <input 
                        type="submit"
                        onClick={this.onSave}
                        value="Save" />
                </div>
            </div>
        );
    }
}
// Error occurs here 
function mapStateToProps(state, ownProps) {
  return {
    courses: state.courses
  };
}
function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(courseActions, dispatch)
    };
}
export default connect(mapStateToProps, mapDispatchToProps)(Course);

course.actions.js

export function loadCourse(response) {
    return {
        type: REQUEST_POSTS,
        response
    };
}
export function fetchCourses() {
    return dispatch => {
        return fetch('http://localhost:3000/api/test')
            .then(data => data.json())
            .then(data => {
                dispatch(loadCourse(data));
            }).catch(error => {
                throw (error);
            });
    };
}
export function createCourse(response) {
    return dispatch => {
        return fetch('http://localhost:3000/api/json', {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                response: response
            })
        })
        .then(data => data.json())
        .then(data => {
            dispatch(loadCourse(data));
        }).catch(error => {
            throw (error);
        });
    };
}

course.reducer.js

export default function courseReducer(state = [], action) {
    switch (action.type) {
        case 'REQUEST_POSTS':
            return action.response;
        default:
            return state;
    }
}

server.js

router.get('/test', function(req, res, next) {
  res.json(courses);
});

router.post('/json', function(req, res, next) {
  console.log(req.body);
  res.json({response: 200});
});

我已经尝试添加对状态的响应,并在地图状态中监听道具,但仍然出于某种原因反应是试图将响应映射到课程。我需要第二种连接方法吗?

function mapStateToProps(state, ownProps) {
  return {
    courses: state.courses, 
    resposne: state.resposne
  };
}

从图片中可以看出,响应被映射为课程而不是响应。

照片 enter image description here

3 个答案:

答案 0 :(得分:3)

假设:

  1. state.courses最初是一个空数组 - 来自course.reducer.js
  2. 第一次渲染视图时,不要调用fetchCourses()操作
  3. 即使你调用fetchCourses()也没有问题,只要server.js中的courses是一个数组(响应中的数组替换初始{{1} }})
  4. <强>流量:
    现在我假设第一个渲染成功,React显示state.courses<input type="text">按钮。现在当您输入标题并单击submit按钮时,submit方法会触发onSave()操作,其参数与createCourse()或多或少相似。

    然后你序列化上面提到的param并发送到服务器(在course.actions.js - &gt; { title: 'something' }),然后返回一个看起来像createCourse()的响应(在server.js中) )。响应字段是一个整数,不是数组!继续使用对象{response: 200}调用loadCourses(),该对象会在课程中触发{response: 200}。reducer.js

    courseReducer 用整数替换 courseReducer()(假设为[]]。并且此状态更新会触发重新呈现,并最终在整数上调用state.courses,而不是在数组上调用,从而生成map()

    可能的解决方案:

    1. 从serve.js返回有效响应(即返回调用端点的TypeError: this.props.courses.map is not a function对象)或
    2. 更新您的reducer以将新课程对象添加到现有course数组中,例如state.courses
    3. 更新

      根据OP的评论,如果您要做的是将新课程对象发送到服务器,验证它并发送成功(或错误),并根据响应将相同的课程对象添加到上一个课程列表,然后您可以简单地使用与return [...state, action.response]对象loadData()调用course对象并使用(如上所述)在reducer中调用createCourse(),而不是替换或改变旧数组创建一个新数组并追加课程对象,在es6中,您可以执行类似return [...state, course]

      的操作

      更新2:

      我建议你通过Redux's Doc。引自Redux Actions' Doc

        

      操作是信息的有效负载,用于将数据从您的应用程序发送到您的商店。它们是商店唯一的信息来源。

      使用有效负载调用createCourse()操作,或多或少, {title: 'Thing you entered in Text Field'},然后使用AJAX请求调用服务器并将 payload 传递给服务器,然后服务器验证有效负载并根据您的逻辑发送成功(或错误)响应。服务器响应看起来像{response: 200}。这是createCourse()操作的结束。现在,来自dispatch()的{​​{1}} loadCourses()操作,以及您从服务器收到的响应,这不是您想要的(根据您的评论)。所以,请尝试createCorse()这样的操作(尝试重命名dispatch() param,这有点令人困惑)

      response

      现在,//..... .then(data => { dispatch(loadCourse(response)); // the same payload you called createCourse with }) //..... 是一个非常基本的操作,它只是转发参数,Redux用它来调用loadCourse()。现在,如果您按照上一次讨论并更新了如何致电reducer,那么loadCourse()的回复就像

      loadCourse()

      然后传递到您的{ type: REQUEST_POSTS, response: { title: 'Thing you entered in Text Field', } } ,特别是您的reducer

      再次引用Redux Reducers' Doc

        

      操作描述了事情发生的事实,但未指定应用程序的状态更改的响应方式。这是减速器的工作。

      courseReducer()必须定义操作应如何影响reducer内的数据的逻辑。
      store中,您只需返回courseReducer()对象中的response字段,然后[期待] Redux即可自动神奇地改变您的状态!不幸的是,这不是发生的事情:(
      无论你从减速器返回什么,完全取代之前的任何东西/对象,比如,如果你的状态看起来像这样

      action

      你从reducer那里返回这样的东西

      { courses: [{...}, {...}, {...}] }
      

      然后redux会将状态更新为

      { title: 'Thing you entered in Text Field'}
      

      state.courses不再是数组!

      <强>解决方案:
      将您的减速器改为这样的

      { courses: { title: 'Thing you entered in Text Field'} }
      

      旁注:这可能会让人感到困惑,所以仅仅为了记录,export default function courseReducer(state = [], action) { switch (action.type) { case 'REQUEST_POSTS': return [...state, action.response] default: return state } } state内的courseReducer()不是完整的状态,而是{{1}在property管理的state上。您可以阅读有关此here

      的更多信息

答案 1 :(得分:1)

- 在用不同的答案阅读你的评论后编辑,我已经删除了我以前的答案 -

您目前对行动和减少者采取的措施是,您在获取初始课程时正在调用loadCourse。当您创建新课程时,也可以拨打loadCourse

在您的reducer中,您将直接返回API调用的响应。因此,当您获取所有课程时,您将获得所有课程的完整列表。但是如果你创建一个新的,你当前会收到一个说response: 200的对象。 Object没有地图功能,这可以解释您的错误。

如果您可以验证响应状态,我建议您在API上使用res.status(200).json()并在前端切换响应状态(或使用thencatch validateStatus 3}}具有此功能(let initialState = { courses: [], createdCourse: {}, } export default (state = initialState, action) => { switch (action.type) { case 'REQUEST_POSTS': return { ...state, courses: action.response } case 'CREATE_COURSE_SUCCESS': return { ...state, createdCourse: action.response, } default: return state; } } ))。

接下来,我将创建一个单独的动作类型来创建帖子,并在成功时分发。

我会将你的减速器更改为

      if (manufactXiaomi.equalsIgnoreCase(android.os.Build.MANUFACTURER)) {
        if (!session.getVisibilityOfAutoStartDialog()) {Intent intent = new Intent();
            intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity"));
            startActivity(intent);}}

我不介意调查你的项目并给你一些关于如何改进一些事情的反馈(ES6,最佳实践,一般的东西)

答案 2 :(得分:1)

基于问题&amp;答案到目前为止,看起来你需要做这样的事情:

1)添加新操作并从createCourse函数

发送
export function courseAdded(course, response) {
    return {
        type: 'COURSE_ADDED',
        course
        response
    };
}

export function createCourse(course) {
    return dispatch => {
        return fetch('http://localhost:3000/api/json', {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                course
            })
        })
            .then(response => response.json())
            .then(response => {
                dispatch(courseAdded(course, response));
            }).catch(error => {
                throw (error);
            });
    };
}

2)更改缩减器以处理提取课程和添加新课程(我们在这里使用combineReducers来处理此问题)

import { combineReducers } from "redux";

function response(state = null, action) {
    switch (action.type) {
        case 'COURSE_ADDED':
            return action.response;
        default:
            return state;
    }
}

function courses(state = [], action) {
    switch (action.type) {
        case 'COURSE_ADDED':
            return [...state, action.course];
        case 'REQUEST_POSTS':
            return action.response;
        default:
            return state;
    }
}

export default combineReducers({
    courses,
    response
});

3)连接到response组件

中的新connect
function mapStateToProps(state, ownProps) {
    return {
        courses: state.courses,
        response: state.response
    };
}

4)如果你想展示它,请在你的组件中使用这个新的response道具做一些事情。

// this is assuming response is a string
<span className="create-course-response">
    Create Course Response - {this.props.response}
</span>

<强>更新

我已添加支持将新课程添加到现有课程列表的末尾,以及处理响应。你如何塑造状态完全取决于你,它可以相应地重新设置。

为了使此代码有效,您需要添加对spread operator的支持。如果您使用babel,可以像this那样完成。创建新对象对于确保不改变现有状态非常重要。这也意味着react-redux知道状态已发生变化。扩展运算符不是必需的,可以使用Object.assign来完成,但该语法是丑陋的IMO。