【问题标题】:Reducer does not call render functionReducer 不调用渲染函数
【发布时间】:2019-02-20 12:35:05
【问题描述】:

我正在尝试学习和开发 React Redux 应用程序。在应用程序中,我有一些私人路线。如果用户转到私有路由,他应该使用登录组件进行身份验证,然后重定向到初始路由。

问题是用户提交表单并通过身份验证后,reducer 并没有调用 LogIn 组件的 render 方法。

我被卡住了,无法弄清楚这是什么原因。

// ../ClientApp/src/App.js

import React from 'react';
import { Route } from 'react-router';
import { Redirect } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './components/Home';
import Block from './components/Block';
import LogIn from './components/LogIn';

export const auth = {
    isAuthenticated: false
}

const PrivateRoute = ({ component: Component, ...rest }) => (
    <Route {...rest} render={(props) => (
        auth.isAuthenticated
            ? <Component {...props} />
            : <Redirect to={{
                pathname: '/login',
                state: { from: props.location }
            }} />
    )} />    
)

export default () => (
    <Layout>
        <Route exact path='/' component={Home} />
        <PrivateRoute path='/block1' component={Block} />
        <PrivateRoute path='/block2' component={Block} />
        <PrivateRoute path='/block3' component={Block} />
        <PrivateRoute path='/block4' component={Block} />
        <PrivateRoute path='/block5' component={Block} />
        <PrivateRoute path='/block7' component={Block} />
        <Route path='/login' component={LogIn} /> 
    </Layout>
);

// ../ClientApp/src/components/LogIn.js

import React, { Component } from 'react';
import { connect } from "react-redux";
import { bindActionCreators } from 'redux';
import './LogIn.css';
import { actionCreators } from '../store/LogIn';
import { Redirect } from 'react-router-dom';
import { auth } from '../App';

class LogIn extends Component {
    state = {
        credentials: {
            username: '',
            password: ''
        },
        error: ''      
    }

    dismissError = () => {
        this.setState({ error: '' });
    }

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

    handleSubmit = (e) => {
        e.preventDefault();

        if (!this.state.credentials.username) {
            return this.setState({ error: 'This field is required' });
        }

        if (!this.state.credentials.password) {
            return this.setState({ error: 'This field is required' });
        }

        this.props.requestLogIn(this.state.credentials);
    }

    render() {   
        auth.isAuthenticated = this.props.isAuthenticated;

        const { credentials } = this.state;

        if (this.props.redirectToReferrer) {
            const { from } = this.props.location.state || {
                from: { pathname: '/' }
            }

            return (
                <Redirect to={from} />
            )
        }

        return (
            <div className="container">
                <div className="row">
                    <div className="col-md-6 col-md-offset-3">
                        <div className="panel panel-login">
                            <div className="panel-heading">
                                <div className="row">
                                    <div className="col-xs-6">
                                        <a href="/" className="active" id="login-form-link">Log in</a>
                                    </div>
                                </div>
                                <hr />
                            </div>
                            <div className="panel-body">
                                <div className="row">
                                    <div className="col-lg-12">
                                        <form id="login-form" onSubmit={this.handleSubmit} style={{ display: 'block' }}>
                                            {
                                                this.state.error &&
                                                <h3 data-test="error" onClick={this.dismissError}>
                                                    <button onClick={this.dismissError}>X</button>
                                                    {this.state.error}
                                                </h3>
                                            }

                                            <div className="form-group">
                                                <input
                                                    type="text"
                                                    name="username"
                                                    tabIndex="1"
                                                    className="form-control"
                                                    placeholder="E-mail"
                                                    value={credentials.username}
                                                    onChange={this.handleChange} />
                                            </div>
                                            <div className="form-group">
                                                <input
                                                    type="password"
                                                    name="password"
                                                    tabIndex="2"
                                                    className="form-control"
                                                    placeholder="Password"
                                                    value={credentials.password}
                                                    onChange={this.handleChange} />
                                            </div>
                                            <div className="form-group">
                                                <div className="row">
                                                    <div className="col-sm-6 col-sm-offset-3">
                                                        <input
                                                            type="submit"
                                                            tabIndex="3"
                                                            className="form-control btn btn-login"
                                                            value="Log in" />
                                                    </div>
                                                </div>
                                            </div>
                                        </form>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

const mapStateToProps = state => {
    return {
        isAuthenticated: state.isAuthenticated,
        redirectToReferrer: state.redirectToReferrer
    }
}

export default connect(
    mapStateToProps,
    dispatch => bindActionCreators(actionCreators, dispatch)
)(LogIn);

// ../ClientApp/src/store/LogIn.js

const authenticated = 'AUTHENTICATED_USER';
const unauthenticated = 'UNAUTHENTICATED_USER';
const authenticationError = 'AUTHENTICATION_ERROR';

const initialState = {
    isAuthenticated: false,
    redirectToReferrer: false,
    error: '',
    token: ''
}

export const actionCreators = {
    requestLogIn: ({ username, password }) => async (dispatch) => {
        try {

            const response = await fetch('api/Authentication/Authenticate',
                {
                    method: 'POST',
                    body: JSON.stringify({
                        username: username,
                        password: password
                    }),
                    headers: { 'Content-Type': 'application/json' },
                    credentials: 'same-origin'
                });
            const token = await response.text();

            dispatch({
                type: authenticated,
                token
            });

        } catch (e) {
            console.log(e);
            dispatch({
                type: authenticationError,
                error: 'Invalid email or password'
            });
        }
    }
}

export const reducer = (state, action) => {
    state = state || initialState;

    switch (action.type) {
        case authenticated:
            return {
                ...state,
                isAuthenticated: true,
                redirectToReferrer: true,
                token: action.token               
            };
        case unauthenticated:
            return { ...state, isAuthenticated: false };
        case authenticationError:
            return { ...state, isAuthenticated: false, error: action.error };
    }
    return state;
}

更新: 感谢remix23 的回答。他是对的,我有几个减速器,我必须像这样在 mapStateToProps 函数中指向 logIn 减速器:

const mapStateToProps = state => {
    return {
        isAuthenticated: state.logIn.isAuthenticated,
        redirectToReferrer: state.logIn.redirectToReferrer,
        error: state.logIn.error,
        token: state.logIn.token
    }
}

仅供参考(也许对某人有用)这是我的减速器配置:

//.. /ClientApp/src/store/configureStore.js:

import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Items from '../reducers/items';
import * as LogIn from './LogIn';

export default function configureStore(history, initialState) {
    const reducers = {
        items: Items.reducer,
        logIn: LogIn.reducer
    };

    const middleware = [
        thunk,
        routerMiddleware(history)
    ];

    // In development, use the browser's Redux dev tools extension if installed
    const enhancers = [];
    const isDevelopment = process.env.NODE_ENV === 'development';
    if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
        enhancers.push(window.devToolsExtension());
    }

    const rootReducer = combineReducers({
        ...reducers,
        routing: routerReducer
    });

    return createStore(
        rootReducer,
        initialState,
        compose(applyMiddleware(...middleware), ...enhancers)
    );
}

【问题讨论】:

  • 因为即使提交了登录表单,auth 变量也始终具有 isAuthenticated = false。
  • 要解决这个问题,在 App 组件中你需要 connect 来存储并从 store 而不是从 auth 变量获取状态 isAuthenticated。

标签: reactjs redux react-redux


【解决方案1】:

路径// ../ClientApp/src/store/LogIn.js 表明您可能在store 文件夹下定义了多个reducer。

这通常意味着您还有一个“应用”reducer(所有reducer 的组合,每个reducer 都有一个键)。

如果这种情况和您的登录 reducer 的键是 login,那么在您提供的 mapStateToProps 中,您可能必须以这种方式访问​​ isAuthenticated 值(否则 state.isAuthenticated 将保持未定义):

const mapStateToProps = state => {
    return {
        isAuthenticated: state.login.isAuthenticated,
        ...
    }
}

正如其他人所建议的那样,从您的 auth 存储的初始值访问 auth 是不好的,即使看起来它可能会起作用,因为您在 Login 组件中设置了它。

您应该像使用 Login 一样连接 App,并通过 props 访问 isAuthenticated(并且永远不要设置商店初始状态的值)。

【讨论】:

    猜你喜欢
    • 2019-10-24
    • 2019-01-29
    • 2019-06-08
    • 2017-10-24
    • 2021-01-07
    • 2018-02-24
    • 1970-01-01
    • 2017-12-27
    • 1970-01-01
    相关资源
    最近更新 更多