я новичок в реагировании и редукции, я пытался использовать 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
. Я потерялся, пожалуйста, помогите!