Редуктор контекстного API с fetch () не обновляет состояние - PullRequest
0 голосов
/ 19 июня 2019

Я работаю над функцией входа в приложение React для электронной коммерции, используя Context API.Поэтому, когда пользователь вводит свою электронную почту и пароль, который вызывает действие в редукторе, чтобы проверить эти данные и установить пользовательские данные в качестве состояния в 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.js с Provider компонентом

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};

1 Ответ

0 голосов
/ 21 июня 2019

Проблема с вашим кодом здесь

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 будет решен, будет вызван обратный вызов, переданный then. Но userLogin не остановит выполнение, пока не будет выполнено обещание. Так что сразу после вызова fetch возвращается userLogin. Но user не изменяется в это время. Ответ не получен, и обещание не выполнено.

При получении ответа user будет изменен, но оператор return уже выполнен, поэтому ничего не будет возвращено.

Единственный способ исправить это - следовать идее Redux и переносить асинхронный код в создатель действий. В вашем случае создатель действий - это userLogin из Login компонента и оставьте код редуктора полностью синхронным. Вы также можете переместить метод 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);
        }); 
}

И соответственно изменить редуктор

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
            };
    }; 
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...