django, среагируйте, токен аутентификации с избыточностью равен нулю - PullRequest
0 голосов
/ 22 апреля 2020

я новичок в реагировании и редукции, я пытался использовать django в качестве моего бэкэнда, реагировать в качестве внешнего интерфейса и в качестве локального хранилища для хранения состояний аутентификации. Однако я столкнулся с ошибкой, и я не слишком уверен, как отладить это. Нет сообщений об ошибках, просто я не могу передать токен в мои компоненты.

index. js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore, compose, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import reducer from './store/reducers/auth';

const composeEnhances = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

const store = createStore(reducer, composeEnhances(
    applyMiddleware(thunk)
));

const app = (
    <Provider store={store}>
        <App />
    </Provider>
)

ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();

Приложение. js

import React, { Component } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { connect } from 'react-redux';
import BaseRouter from './routes';
import 'antd/dist/antd.css';
import * as actions from './store/actions/auth';

import CustomLayout from './containers/Layout';

class App extends Component {

  componentDidMount() {
    this.props.onTryAutoSignup();
  }

  render() {
    return (
      <div>
        <Router>
          <CustomLayout {...this.props}>
              <BaseRouter />
          </CustomLayout>
        </Router>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    isAuthenticated: state.token !== null
  }
}

const mapDispatchToProps = dispatch => {
  return {
    onTryAutoSignup: () => dispatch(actions.authCheckState())
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App);

Компонент макета (родительский)

import React from 'react';
import { Layout, Menu, Breadcrumb } from 'antd';
import { Link, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import * as actions from '../store/actions/auth';

const { Header, Content, Footer } = Layout;

class CustomLayout extends React.Component {
    render() {
        return (
            <Layout className="layout">
                <Header>
                <div className="logo" />
                <Menu
                    theme="dark"
                    mode="horizontal"
                    defaultSelectedKeys={['2']}
                    style={{ lineHeight: '64px' }}
                >

                {
                    this.props.isAuthenticated ?

                    <Menu.Item key="2" onClick={this.props.logout}>
                        Logout
                    </Menu.Item>

                    :

                    <Menu.Item key="2">
                        <Link to="/login">Login</Link>
                    </Menu.Item>
                }

                    <Menu.Item key="1">
                        <Link to="/">Posts</Link>
                    </Menu.Item>

                </Menu>
                </Header>
                <Content style={{ padding: '0 50px' }}>
                <Breadcrumb style={{ margin: '16px 0' }}>
                    <Breadcrumb.Item><Link to="/">Home</Link></Breadcrumb.Item>
                    <Breadcrumb.Item><Link to="/">List</Link></Breadcrumb.Item>
                </Breadcrumb>
                    <div style={{ background: '#fff', padding: 24, minHeight: 280 }}>
                        {this.props.children}
                    </div>
                </Content>
                <Footer style={{ textAlign: 'center' }}>
                Ant Design ©2016 Created by Ant UED
                </Footer>
            </Layout>
        );
    }
}

const mapDispatchToProps = dispatch => {
    return {
        logout: () => dispatch(actions.logout()) 
    }
}

export default withRouter(connect(null, mapDispatchToProps)(CustomLayout));

Компонент (Дочерний, который заинтересован в получении токена)

import React from "react";
import axios from "axios";
import Articles from "../components/Article";
import CustomForm from "../components/Form";
import { connect } from "react-redux";


class ArticleList extends React.Component {
  state = {
    articles: []
  };

  fetchArticles = () => {
    axios.get("http://127.0.0.1:8000/api/").then(res => {
      this.setState({
        articles: res.data
      });
    });
  }

  componentDidMount() {
    console.log(this.props)
    console.log(this.props.token)
    this.fetchArticles();
  }

  componentDidUpdate(newProps) {
    console.log(newProps.token)
    if (newProps.token) {
      this.fetchArticles();      
    }
  }

  render() {
    return (
      <div>
        <Articles data={this.state.articles} /> <br />
        <h2> Create an article </h2>
        <CustomForm requestType="post" articleID={null} btnText="Create" />
      </div>
    );
  }
}


const mapStateToProps = state => {
  return {
    token: state.token
  };
};

export default connect(mapStateToProps)(ArticleList);

Артикул

import React from "react";
import { List, Avatar, Icon } from "antd/lib";


const Articles = props => {
  return (
    <List
      itemLayout="vertical"
      size="large"
      pagination={{
        onChange: page => {
          console.log(page);
        },
        pageSize: 3
      }}
      dataSource={props.data}
      renderItem={item => (
        <List.Item
          key={item.title}
          extra={
            <img
              width={272}
              alt="logo"
              src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"
            />
          }
        >
          <List.Item.Meta
            avatar={<Avatar src={item.avatar} />}
            title={<a href={`/articles/${item.id}`}> {item.title} </a>}
            description={item.description}
          />
          {item.content}
        </List.Item>
      )}
    />
  );
};

export default Articles;

CustomForm

    import React from "react";
    import { Form, Input, Button } from "antd";
    import { connect } from "react-redux";
    import axios from "axios";

    const FormItem = Form.Item;


    class CustomForm extends React.Component {

      handleFormSubmit = async (event, requestType, articleID) => {
        event.preventDefault();

        const postObj = {
          title: event.target.elements.title.value,
          content: event.target.elements.content.value
        }

        axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
        axios.defaults.xsrfCookieName = "csrftoken";
        axios.defaults.headers = {
          "Content-Type": "application/json",
          Authorization: `Token ${this.props.token}`,
        };

        if (requestType === "post") {
          await axios.post("http://127.0.0.1:8000/api/create/", postObj)
            .then(res => {
              if (res.status === 201) {
                this.props.history.push(`/`);
              }
            })
        } else if (requestType === "put") {
          await axios.put(`http://127.0.0.1:8000/api/${articleID}/update/`, postObj)
            .then(res => {
              if (res.status === 200) {
                this.props.history.push(`/`);
              }
            })
        }
      };

      render() {
        return (
          <div>
            <Form
              onSubmit={event =>
                this.handleFormSubmit(
                  event,
                  this.props.requestType,
                  this.props.articleID
                )
              }
            >
              <FormItem label="Title">
                <Input name="title" placeholder="Put a title here" />
              </FormItem>
              <FormItem label="Content">
                <Input name="content" placeholder="Enter some content ..." />
              </FormItem>
              <FormItem>
                <Button type="primary" htmlType="submit">
                  {this.props.btnText}
                </Button>
              </FormItem>
            </Form>
          </div>
        );
      }
    }

    const mapStateToProps = state => {
      return {
        token: state.token
      };
    };

    export default connect(mapStateToProps)(CustomForm);

Связанный с REDUX (действия / аутентификация. js)

import axios from 'axios';
import * as actionTypes from './actionTypes';

export const authStart = () => {
    return {
        type: actionTypes.AUTH_START
    }
}

export const authSuccess = token => {
    return {
        type: actionTypes.AUTH_SUCCESS,
        token: token
    }
}

export const authFail = error => {
    return {
        type: actionTypes.AUTH_FAIL,
        error: error
    }
}

export const logout = () => {
    localStorage.removeItem('token');
    localStorage.removeItem('expirationDate');
    return {
        type: actionTypes.AUTH_LOGOUT
    };
}

export const checkAuthTimeout = expirationTime => {
    return dispatch => {
        setTimeout(() => {
            dispatch(logout());
        }, expirationTime * 1000)
    }
}

export const authLogin = (username, password) => {
    return dispatch => {
        dispatch(authStart());
        axios.post('http://127.0.0.1:8000/rest-auth/login/', {
            username: username,
            password: password
        })
        .then(res => {
            const token = res.data.key;
            const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
            localStorage.setItem('token', token);
            localStorage.setItem('expirationDate', expirationDate);
            dispatch(authSuccess(token));
            dispatch(checkAuthTimeout(3600));
        })
        .catch(err => {
            dispatch(authFail(err))
        })
    }
}

export const authSignup = (username, email, password1, password2) => {
    return dispatch => {
        dispatch(authStart());
        axios.post('http://127.0.0.1:8000/rest-auth/registration/', {
            username: username,
            email: email,
            password1: password1,
            password2: password2
        })
        .then(res => {
            const token = res.data.key;
            const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
            localStorage.setItem('token', token);
            localStorage.setItem('expirationDate', expirationDate);
            dispatch(authSuccess(token));
            dispatch(checkAuthTimeout(3600));
        })
        .catch(err => {
            dispatch(authFail(err))
        })
    }
}

export const authCheckState = () => {
    return dispatch => {
        const token = localStorage.getItem('token');
        if (token === undefined) {
            dispatch(logout());
        } else {
            const expirationDate = new Date(localStorage.getItem('expirationDate'));
            if ( expirationDate <= new Date() ) {
                dispatch(logout());
            } else {
                dispatch(authSuccess(token));
                dispatch(checkAuthTimeout( (expirationDate.getTime() - new Date().getTime()) / 1000) );
            }
        }
    }
}

Редукторы / аутентификация. js

import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../utility';

const initialState = {
    token: null,
    error: null, 
    loading: false
}

const authStart = (state, action) => {
    return updateObject(state, {
        error: null,
        loading: true
    });
}

const authSuccess = (state, action) => {
    return updateObject(state, {
        token: action.token,
        error: null,
        loading: false
    });
}

const authFail = (state, action) => {
    return updateObject(state, {
        error: action.error,
        loading: false
    });
}

const authLogout = (state, action) => {
    return updateObject(state, {
        token: null
    });
}

const reducer = (state=initialState, action) => {
    switch (action.type) {
        case actionTypes.AUTH_START: return authStart(state, action);
        case actionTypes.AUTH_SUCCESS: return authSuccess(state, action);
        case actionTypes.AUTH_FAIL: return authFail(state, action);
        case actionTypes.AUTH_LOGOUT: return authLogout(state, action);
        default:
            return state;
    }
}

export default reducer;

Утилита. js

export const updateObject = (oldObject, updatedProperties) => {
return {
    ...oldObject,
    ...updatedProperties
}
}

Похоже, что в моей конфигурации с избыточностью нет ничего плохого, поскольку расширение с избыточностью на chrome ясно показывает, что токен присутствует при аутентификации.

Однако при выполнении console.log в дочернем компоненте возвращается значение null. Я потерялся, пожалуйста, помогите!

1 Ответ

0 голосов
/ 22 апреля 2020

Я выяснил, в чем проблема с моим кодом, это действительно ошибка новичка. Метод:

  componentDidUpdate(newProps) {
    if (newProps.token) {
      this.fetchArticles();      
    }
  }

принимает в качестве аргумента предыдущий реквизит. Вот почему токен был Нулевым, поскольку мне еще предстояло пройти аутентификацию и получить токен в моем предыдущем состоянии. По этой же причине после нажатия кнопки выхода из системы токен показывался в моей консоли. Изменение newProps.token на this.props.token решило проблему.

...