简体   繁体   English

如何在React组件中订阅Redux动作

[英]How to subscribe to a Redux action in a React component

I'm trying to figure out how I could subscribe to a Redux action in a React component. 我试图弄清楚如何在React组件中订阅Redux动作。 I didn't find anything on Google so I'm opening an issue here to see if someone can help me out. 我没有在Google上找到任何东西,因此在这里打开一个问题,看看是否有人可以帮助我。

When an user tries to log in I dispatch an loginRequestAction() then I handle it using redux-saga (check out my saga.js file below), finally if everything is OK I dispatch LOGIN_REQUEST_SUCCESS action. 当用户尝试登录时,我调度一个loginRequestAction()然后使用redux-saga处理它(请查看下面的saga.js文件),如果一切正常,我将调度LOGIN_REQUEST_SUCCESS操作。

What I would like to do here find a way to subscribe to LOGIN_REQUEST_SUCCESS action in my React component so once the action is received I can update my React component local state and use history.push() to redirect the user to dashboard page. 我在这里想做的是找到一种在React组件中订阅LOGIN_REQUEST_SUCCESS动作的方法,因此一旦收到该动作,我就可以更新我的React组件的本地状态,并使用history.push()将用户重定向到仪表板页面。

This is my component code: 这是我的组件代码:

/**
 *
 * LoginPage
 *
 */

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { Container, Row, Col, Button, Alert } from 'reactstrap';
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
import { ReactstrapInput } from 'reactstrap-formik';
import reducer from './reducer';
import saga from './saga';
import { loginRequestAction } from './actions';
import { makeSelectLoginPage } from './selectors';
import { makeSelectIsLogged } from '../Auth/selectors';

const LoginSchema = Yup.object().shape({
  userIdentifier: Yup.string().required('Required'),
  password: Yup.string().required('Required'),
});

/* eslint-disable react/prefer-stateless-function */
export class LoginPage extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      formMsg: {
        color: '',
        text: '',
      },
    };
  }

  componentDidMount() {
    const { history, isLogged } = this.props;

    if (isLogged) history.push('/dashboard/index');
  }

  render() {
    const { formMsg } = this.state;
    const { onLoginFormSubmit } = this.props;

    return (
      <div>
        <Helmet>
          <title>Sign in</title>
          <meta name="description" content="Description of LoginPage" />
        </Helmet>
        <Container className="auth-container">
          <div className="form-page">
            <Row>
              <Col className="text-center">
                <img
                  className="mb-4"
                  src="https://getbootstrap.com/docs/4.1/assets/brand/bootstrap-solid.svg"
                  alt=""
                  width="72"
                  height="72"
                />
              </Col>
            </Row>
            <Row>
              <Col className="text-center">
                {' '}
                <h1 className="h3 mb-3 font-weight-normal">Authentication</h1>
                <Alert
                  color={formMsg.color}
                  role="alert"
                  className={formMsg.text ? '' : 'd-none'}
                >
                  <strong>{formMsg.text}</strong>
                </Alert>
              </Col>
            </Row>

            <Formik
              initialValues={{
                userIdentifier: '',
                password: '',
              }}
              validationSchema={LoginSchema}
              onSubmit={onLoginFormSubmit}
            >
              {({ isSubmitting }) => (
                <Form>
                  <Field
                    component={ReactstrapInput}
                    name="userIdentifier"
                    type="userIdentifier"
                    placeholder="john@acme.com"
                    label="E-mail address"
                  />
                  <Field
                    component={ReactstrapInput}
                    name="password"
                    type="password"
                    placeholder="Password"
                    label="Password"
                  />
                  <div>
                    <Button
                      type="submit"
                      block
                      size="lg"
                      color="primary"
                      disabled={isSubmitting}
                    >
                      <FontAwesomeIcon
                        pulse
                        icon={faSpinner}
                        className={isSubmitting ? 'mr-2' : 'd-none'}
                      />
                      Log in to access
                    </Button>
                  </div>
                </Form>
              )}
            </Formik>

            <Link to="/auth/reset">
              <Button size="sm" color="secondary" block className="mt-2">
                Forgot password?
              </Button>
            </Link>
            <p className="mt-5 mb-3 text-center">
              <Link to="/auth/register">
                Don&#39;t have an account? Sign up
              </Link>
            </p>
          </div>
        </Container>
      </div>
    );
  }
}

LoginPage.propTypes = {
  isLogged: PropTypes.bool,
  history: PropTypes.object,
  onLoginFormSubmit: PropTypes.func,
};

const mapStateToProps = createStructuredSelector({
  loginpage: makeSelectLoginPage(),
  isLogged: makeSelectIsLogged(),
});

function mapDispatchToProps(dispatch) {
  return {
    onLoginFormSubmit: values => dispatch(loginRequestAction(values)),
  };
}

const withConnect = connect(
  mapStateToProps,
  mapDispatchToProps,
);

const withReducer = injectReducer({ key: 'loginPage', reducer });
const withSaga = injectSaga({ key: 'loginPage', saga });

export default compose(
  withReducer,
  withSaga,
  withConnect,
)(LoginPage);

This is my saga.js file: 这是我的saga.js文件:

import { put, call, takeLatest } from 'redux-saga/effects';
import {
  LOGIN_REQUEST,
  LOGIN_REQUEST_SUCCESS,
  LOGIN_REQUEST_FAILED,
} from './constants';
import { AuthApi } from '../../api/auth.api';

export function* loginRequest(action) {
  const { userIdentifier, password } = action.values;

  try {
    const tokens = yield call(AuthApi.login, userIdentifier, password);
    yield put({ type: LOGIN_REQUEST_SUCCESS, tokens });
  } catch (err) {
    let errMsg;

    switch (err.status) {
      case 403:
        errMsg = 'Invalid credentials';
        break;
      case 423:
        errMsg = 'Account desactivated';
        break;
      default:
        errMsg = `An server error ocurred. We have been notified about this error, our devs will fix it shortly.`;
        break;
    }

    yield put({ type: LOGIN_REQUEST_FAILED, errMsg });
  }
}

export default function* defaultSaga() {
  yield takeLatest(LOGIN_REQUEST, loginRequest);
}

PS I'm coming from this GitHub issue: https://github.com/react-boilerplate/react-boilerplate/issues/2360 (Please take a look at it as there is a possible solution but IMHO I think it isn't the correct way to go). PS我来自这个GitHub问题: https : //github.com/react-boilerplate/react-boilerplate/issues/2360 (请看一下它,因为有可能的解决方案,但恕我直言,我认为不是正确的方法)。

As a rule you never "subscribe" to Redux actions in components, since it breaks some of React's declarative benefits. 通常,您永远不会“订阅”组件中的Redux操作,因为它破坏了React的一些声明性好处。 Most of the time you'll just connect() to Redux state via props and simply render based off those. 在大多数情况下,您只是通过props将connect()为Redux状态,然后根据这些属性进行渲染。 Subscribing to actions, and then calling routing functions in response is imperative programming, rather than declarative, so you need to avoid that within components. 订阅操作,然后调用路由函数作为响应是命令式编程,而不是声明式,因此您需要在组件中避免这种情况。

So when working with sagas you have a couple of options for this sort of scenario. 因此,在使用Sagas时,您可以针对这种情况选择两个选项。 Instead of emitting LOGIN_REQUEST_SUCCESS from your saga you could instead just push a new route from inside the saga itself: 除了从传奇中发出LOGIN_REQUEST_SUCCESS ,您还可以从传奇本身内部推送一条新路线:

yield call(push, '/foo')

Or alternatively maintain a authenticated: boolean property in your Redux state, flip it to true in the saga when the user logs in, and then use it to conditionally render a react-router <Redirect /> in your component (or perhaps conditionally render the entire authenticated tree of your app, up to you). 或者在您的Redux状态下保持一个authenticated: boolean属性,当用户登录时将其在saga中翻转为true ,然后使用它在您的组件中有条件地渲染react-router <Redirect /> (或者可能有条件地渲染您的应用的整个经过身份验证的树,具体取决于您)。

To be honest, I think the answer in the github make sense. 老实说,我认为github中的答案很有意义。 You subscribe the dispatch action in redux by using the reducer. 您可以通过使用reducer在redux中预订分派操作。 and the reducer responsible for setting redux state and return it to your component by using connect function. 而reducer负责设置redux状态,并通过使用connect函数将其返回到您的组件。

Adding a flag property called 'loginSuccess' or whatever and pass to you component would not cause performance issues from my point of view because it is the way how redux work with react 从我的角度来看,添加一个名为“ loginSuccess”或其他内容的标记属性并将其传递给您不会导致性能问题,因为这是redux与react一起工作的方式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM