Не удается установить состояние в API контекста ReactJS после вызова AJAX - PullRequest
0 голосов
/ 19 апреля 2019

Я только начал изучать ReactJS и решил использовать новый контекстный API в ReactJS для управления состоянием в проекте, который я строю во время обучения.

Вот код context.js,

import React, { Component } from "react";
import axios from "axios";

const Context = React.createContext();

const reducer = async (state, action) => {
  switch (action.type) {
    case "USER_LOGIN":
      const { token } = action.payload;

      return { ...state, user: { token } };

    case "GET_USER_DATA":
      const url = "api/users/dashboard";
      const userToken = action.payload.token;

      let res = await axios.get(url, {
          headers: {
            Authorization: userToken
          }
      })


      let urls = res.data.urls;
      urls = urls.map(url => ( { ...url,shortUrl: axios.defaults.baseURL + "/" + url.urlCode} ) )

      return { ...state, user: { token } };

  }
};

export class Provider extends Component {
  state = {
    user: {
      token: "",
      data: [{id: 'adsasd'}]
    },
    dispatch: action => {
      this.setState(state => reducer(state, action));
    }
  };


  render() {
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    );
  }
}

export const Consumer = Context.Consumer;

У меня есть два типа действий: один для входа в систему и один для получения пользовательских данных на основе токена JWT, полученного после успешного входа в систему.

Вот мой логин

import React, { Component } from "react";
import { Row, Col, Input, Icon, CardPanel, Button } from "react-materialize";
import axios from 'axios'
import { Consumer } from '../store/context'

class Login extends Component {
  state = {
    errors: {
      name: "",
      password: ""
    }
  };

  constructor(props) {
    super(props);
    this.emailInputRef = React.createRef();
    this.passwordInputRef = React.createRef();
  }


  login = async (dispatch) => {
    const email = this.emailInputRef.state.value;
    const password = this.passwordInputRef.state.value;

    if (typeof password != "undefined" && password.length < 6) {
      this.setState({ errors: { password: "Password length must be atleast 6 characters!" } })
    }
    else {
      this.setState({ errors: { password: "" } })
    }

    if (typeof email != "undefined") {
      if (!validateEmail(email)) {
        console.log('invalid email');

        this.setState({ errors: { email: "Invalid email address!" } })
      }
      else {
        this.setState({ errors: { email: "" } })
      }
    }
    else {
      this.setState({ errors: { email: "Invalid email address!" } })
    }

    // console.log(this.state.errors);

    if ((email !== "" || typeof email !== "undefined") && (password !== "" || typeof password !== "undefined")) {

      const res = await axios.post('/api/users/login', {
        'email': email,
        'password': password
      })


      dispatch({
        type: 'USER_LOGIN',
        payload: {
          token: res.data.data.token
        }
      })

      this.props.history.push('/dashboard')


    }

  }

  render() {
    const { errors } = this.state;
    return (
      <Consumer>
        {value => {

          const { dispatch } = value

          return (
            <CardPanel className="bg-primary" style={{ padding: "20px 5%" }}>
              <Row className="login">
                <h1 style={{ color: "white" }}>Login</h1>
                <Col s={12} m={12}>
                  <Input
                    s={12}
                    m={12}
                    name="email"
                    error={errors.email}
                    className="error"
                    label="Email"

                    ref={ref => this.emailInputRef = ref}
                  >
                    <Icon>account_circle</Icon>
                  </Input>

                  <Input
                    s={12}
                    m={12}
                    name="password"
                    error={errors.password}
                    label="Password"

                    type="password"
                    ref={ref => this.passwordInputRef = ref}
                  >
                    <Icon>lock</Icon>
                  </Input>
                  <Button onClick={this.login.bind(this, dispatch)} style={{ marginTop: "20px" }} waves="yellow">
                    Login
                </Button>
                </Col>
              </Row>
            </CardPanel>
          )
        }}

      </Consumer>

    );
  }
}

function validateEmail(sEmail) {
  const reEmail = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/

  if (sEmail === "") return false;

  return reEmail.test(sEmail);
}

function isEmpty(obj) {
  if (obj == null) return true;
  return Object.entries(obj).length === 0 && obj.constructor === Object;
}

export default Login;

Чего я хочу добиться, так это того, что когда пользователь пытается войти в систему, я делаю запрос к бэкэнду и получаю токен JWT, а затем отправляю действие входа в context.js, чтобы сохранить токен для будущего использования. После этого я перенаправляю пользователя на панель инструментов, где он может получить сгенерированные данные. Чтобы получить данные, я снова делаю AJAX-запрос к бэкэнду с сохраненным токеном JWT в контексте. Я делаю это внутри метода componentDidMount(), но всегда получаю пустой объект, когда пытаюсь получить доступ к данным контекста. Вот приборная панель

Dashboard.jsx

   import React, { Component } from 'react'
import axios from 'axios'
import 'react-bootstrap-table-next/dist/react-bootstrap-table2.min.css';
import BootstrapTable from 'react-bootstrap-table-next';
import overlayFactory from 'react-bootstrap-table2-overlay';

import { Consumer } from '../store/context'

const columns = [
    {
        dataField: 'url',
        text: 'URLs'
    },

    {
        dataField: 'hits',
        text: 'Hits'
    },
    {
        dataField: 'shortUrl',
        text: 'Short URL'
    },
    {
        dataField: 'createdDate',
        text: 'Date'
    },
];

export default class Dashboard extends Component {

    state = {
        data: []
    }

    componentDidMount() {

        // const url = 'api/users/dashboard'

        const context = this.context

        console.log(context); // always empty

    }

    render() {
        return (
            <Consumer>
                {value => {
                    const { user } = value

                    return (
                        isEmpty(user) ? <h3 className="center-align">Please Login To View Dashboard...</h3> : (
                            < BootstrapTable keyField='shortUrl'
                                data={this.state.data}
                                columns={columns}
                                bordered={true}
                                hover={true}
                            />
                        )
                    )

                }}
            </Consumer>
        )
    }
}

function isEmpty(obj) {
    if (obj == null) return true;
    return Object.entries(obj).length === 0 && obj.constructor === Object;
}

1 Ответ

1 голос
/ 19 апреля 2019

По умолчанию this.context не определено. Для того, чтобы он был заполнен, вы должны указать, как реагировать, чтобы заполнить его. Предполагая, что вы на реагируете на 16,6 или позже , это будет выглядеть так:

// In context.js, you must export the entire context, not just the consumer
export const Context = React.createContext();

// In Dashboard.jsx, you must import the context, and add a static contextType property to your component

import { Context } from '../store/context';

export default class Dashboard extends Component {
  static contextType = Context;

  componentDidMount() {
    console.log(this.context);
  }
}
...