Редуктор не вызывает функцию рендера - PullRequest
0 голосов
/ 16 сентября 2018

Я пытаюсь изучить и разработать приложение React Redux.В приложении у меня есть несколько частных маршрутов.Если пользователь переходит на частные маршруты, он должен пройти аутентификацию с помощью компонента LogIn, а затем перенаправляться на начальный маршрут.

Проблема в том, что после того, как пользователь отправил форму и прошел аутентификацию, редуктор не вызывает метод рендеринга компонента LogIn.

Я застрял и не могу выяснить причину этого.

// ../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 .Он был прав, что у меня было несколько редукторов, и я должен был указать logIn редуктор в функции mapStateToProps следующим образом:

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

1 Ответ

0 голосов
/ 16 сентября 2018

Путь // ../ClientApp/src/store/LogIn.js предполагает, что в папке store могут быть определены несколько редукторов.

Обычно это означает, что у вас также есть редуктор "app" (комбинация всех ваших редукторов с ключом для каждого из них).

Если в этом случае и ключ для входа в системуравняется login, тогда в предоставленном вами mapStateToProps вам, возможно, придется получить доступ к значению isAuthenticated таким образом (в противном случае state.isAuthenticated останется неопределенным):

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

Также, как и другие предлагали, доступ к authИсходное значение вашего хранилища аутентификации плохое, даже если оно выглядит так, как будто оно работает, потому что вы установили его в компоненте Login.

Вы должны подключить приложение, как вы это сделали, с Login, и доступ isAuthenticated через props (иникогда не устанавливайте значение начальных состояний ваших магазинов).

...