【问题标题】:Context API reducer with fetch() doesn't update state带有 fetch() 的上下文 API 减速器不更新状态
【发布时间】:2019-11-02 03:50:03
【问题描述】:

我正在使用 Context API 在电子商务 React 应用程序中开发登录功能。 因此,当用户输入他的电子邮件和密码时,会在 reducer 中调用一个操作来检查该数据并将用户数据设置为 Context Provider 中的状态。

这是操作(我省略了其他有效的情况):

const reducer = (state, action) => {
...

switch(action.type) {
case 'USER_LOGIN': 
        let user = userLogin(state.user,action.payload);
        return {
          ...state,
          user: user,
          loggedIn: temp.length> 0 ? true: false
        };

...

这是函数userLogin(),它本身工作正常,并返回一个很好的用户数据数组。

var userLogin = (user, payload) => {

  const url = "/api/users/login.php";
  console.log(payload);
  fetch(url,{
    method: "POST",
    body: JSON.stringify(payload)
  })
      .then(response => response.json())
      .then(
      (result) => {
          user.id = result.id;
          user.name = result.name;
          user.email = result.email;
          user.phone = result.phone;
          user.city = result.city;
          user.street = result.street;
          user.building = result.building;
          user.flat = result.flat;        
      },
      (error) => {
          console.log(error);
      }); 
  return user;
}

但是在我的<Provider> 的结果中,user 的状态值在初始化时保持为空数组。

我认为有些东西必须与 fetch() 的异步类型一起使用,但找不到任何关于此的参考。

UPD:这是我的 <Login> 组件

import React, { Component } from 'react'
import {Consumer} from '../../Context'


export default class Login extends Component {
    constructor(props) {
        super(props);
        this.state = {
            email: "",
            password: "",
        };
    }

    userLogin = (dispatch, e) => {
        e.preventDefault();
        dispatch({
            type: 'USER_LOGIN',
            payload: {email: this.state.email,
                      password: this.state.password }
        });
    }

    handleChange = (e) => {
        this.setState({
            [e.target.name] : e.target.value
        })
    }

    render() {
        return (
            <Consumer>
            {
                value=> {
                    const {dispatch} = value;
                    return (
                        <div className="checkout__container">
                        <h5 className="checkout__header">Login</h5>
                        <form>
                            <label className="checkout__label">E-mail:</label><br />
                            <input className="checkout__input" type="email" name="email" onChange={this.handleChange}></input><br /><br />
                            <label className="checkout__label">Password:</label><br />
                            <input className="checkout__input" type="password" name="password" onChange={this.handleChange}></input><br /><br />
                            <button type="button" className="button button-primary" onClick={this.userLogin.bind(this, dispatch)}>Sign In</button>

                        </form> 
                        </div>
                    );
                }
            }


            </Consumer>
        )
    }
}

UPD #2:这是我的 Context.jsProvider 组件的摘录

import React, { Component } from 'react'
import axios from 'axios'

const Context = React.createContext();

const reducer = (state, action) => {
...
switch(action.type) {
case 'USER_LOGIN': 
        let user = userLogin(state.user,action.payload);
        return {
          ...state,
          user: user
        };
  }; 
};

var userLogin = (user, payload) => {
    // Do fetch() here and return user array
}
...
class Provider extends Component {
    constructor(props) {
        super(props);
        this.state = {
          user: [],
          loggedIn: false,
          dispatch: action => this.setState( state => reducer(state,action))         
        };
    }
 render() {
    return (
      <Context.Provider value={this.state}>
          {this.props.children}
      </Context.Provider>
    )
  }
}

const Consumer = Context.Consumer;

export {Provider, Consumer};

【问题讨论】:

  • fetchasync。所以userLogin() 立即返回而不更改user 对象。我不认为 reducer 是异步加载数据的正确位置。使用 thunksagaobservables 之类的东西来处理副作用。
  • thunksagaRedux 一起工作,observables 被称为 RxJS。有没有办法使用Context API实现asyncfetch
  • 为什么你不能fetch 然后dispatch 行动?
  • @AsafAviv 我根本不使用Redux,只使用Context API。在上面添加了&lt;Login&gt; сomponent
  • 我认为我应该为我的userLogin() 函数使用useEffect() 钩子

标签: reactjs react-redux fetch react-context


【解决方案1】:

你的代码有问题

var userLogin = (user, payload) => {
    //...
    fetch(url,{
        method: "POST",
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(
        (result) => {
            user.id = result.id;
            user.name = result.name;
            // ...     
        },
        (error) => {
            console.log(error);
        }); 
    return user;
}

你调用 fetch 返回 Promise。当 Promise 将被解析时,将调用传递给 then 的回调。但是userLogin 在 Promise 解决之前不会停止执行。所以在fetch 调用之后,userLogin 返回。但是user当时并没有改变。未收到响应且 Promise 未解决。

当响应到来时,user 将被更改,但 return 语句已经执行,所以不会返回任何内容。

解决这个问题的唯一方法是遵循 Redux 的理念并将异步代码移至 action creator。在您的情况下,动作创建者是来自Login 组件的userLogin,并使reducer 代码完全同步。您也可以将userLogin 方法从Login 组件中移出。

例如

userLogin = (dispatch, e) => {
    e.preventDefault();

    let payload = {email: this.state.email,
                  password: this.state.password };
    const url = "/api/users/login.php";
    console.log(payload);
    fetch(url,{
        method: "POST",
        body: JSON.stringify(payload)
    })
    .then(response => response.json())
    .then(
        (result) => {
            user.id = result.id;
            user.name = result.name;
            // ...
            // And call dispatch from here
            dispatch (type: 'USER_LOGIN',
                user: user // We don't need payload here as we already got user info
            );
        },
        (error) => {
            console.log(error);
        }); 
}

并相应地修改reducer

const reducer = (state, action) => {
//...
    switch(action.type) {
        case 'USER_LOGIN': 
            return {
                ...state,
                user: [...user, action.user]; // user is array so add new user to existing array
            };
    }; 
}

【讨论】:

  • 谢谢,现在我将用户数组传递给reducer,但state 设置为空数组而不是action.usercase 'USER_LOGIN': return { ...state, user: action.user };我把user换成action.user,因为是登录的用户,应该只有一个。
  • 调度调用前user的内容是什么?
  • 我尝试了两种方法:user: []user:undefined in Provider state。第一个在dispatch 之后不变,第二个将user 设置为空数组
  • 更重要的是传递给调度的内容。请在调用调度前console.log(user)
  • 在调用 dispatch user 之前是一个数组,其中包含从数据库返回的正确用户数据。同样是console.log(action.user)reducer()
猜你喜欢
  • 2021-07-18
  • 1970-01-01
  • 2019-07-05
  • 2018-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多