将 redux 操作添加到登录表单?

Posted

技术标签:

【中文标题】将 redux 操作添加到登录表单?【英文标题】:Add redux actions to login form? 【发布时间】:2020-10-31 06:20:33 【问题描述】:

我从以前的 React 应用程序转换到了新模板。问题是我对如何设置 redux 以及如何实现身份验证感到非常困惑。

登录表单

// validation functions
const required = value => (value === null ? 'Required' : undefined);
const email = value =>
  value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]2,4$/i.test(value) ? 'Invalid email' : undefined;

const LinkBtn = React.forwardRef(function LinkBtn(props, ref) 
  // eslint-disable-line
  return <NavLink to=props.to ...props innerRef=ref />; // eslint-disable-line
);

// eslint-disable-next-line
class LoginForm extends React.Component 
  // state = 
  //   showPassword: false,
  // ;
  constructor() 
    super();
    this.state = 
      email: '',
      password: '',
      errors: ,
      showPassword: false,
    ;
  

  handleClickShowPassword = () => 
    const  showPassword  = this.state;
    this.setState( showPassword: !showPassword );
  ;

  handleMouseDownPassword = event => 
    event.preventDefault();
  ;

  onSubmit = e => 
    e.preventDefault();

    const userData = 
      email: this.state.email,
      password: this.state.password,
    ;

    loginUser(userData);
  ;

  render() 
    console.log(this.props);
    const  classes, handleSubmit, pristine, submitting, deco  = this.props;
    const  showPassword  = this.state;
    return (
      <Fragment>
        <Hidden mdUp>
          <NavLink to="/" className=classNames(classes.brand, classes.outer)>
            <img src=logo alt=brand.name />
            brand.name
          </NavLink>
        </Hidden>
        <Paper className=classNames(classes.paperWrap, deco && classes.petal)>
          <Hidden smDown>
            <div className=classes.topBar>
              <NavLink to="/" className=classes.brand>
                <img src=logo alt=brand.name />
                brand.name
              </NavLink>
              <Button
                size="small"
                className=classes.buttonLink
                component=LinkBtn
                to="/register"
              >
                <Icon className=classes.icon>arrow_forward</Icon>
                Create new account
              </Button>
            </div>
          </Hidden>
          <Typography variant="h4" className=classes.title gutterBottom>
            Sign In
          </Typography>
          <Typography variant="caption" className=classes.subtitle gutterBottom align="center">
            Lorem ipsum dolor sit amet
          </Typography>
          <section className=classes.socmedLogin>
            <div className=classes.btnArea>
              <Button variant="outlined" size="small" className=classes.redBtn type="button">
                <AllInclusive className=classNames(classes.leftIcon, classes.iconSmall) />
                Socmed 1
              </Button>
              <Button variant="outlined" size="small" className=classes.blueBtn type="button">
                <Brightness5 className=classNames(classes.leftIcon, classes.iconSmall) />
                Socmed 2
              </Button>
              <Button variant="outlined" size="small" className=classes.cyanBtn type="button">
                <People className=classNames(classes.leftIcon, classes.iconSmall) />
                Socmed 3
              </Button>
            </div>
            <ContentDivider content="Or sign in with email" />
          </section>
          <section className=classes.formWrap>
            <form onSubmit=handleSubmit>
              <div>
                <FormControl className=classes.formControl>
                  <Field
                    name="email"
                    component=TextFieldRedux
                    placeholder="Your Email"
                    label="Your Email"
                    required
                    validate=[required, email]
                    className=classes.field
                  />
                </FormControl>
              </div>
              <div>
                <FormControl className=classes.formControl>
                  <Field
                    name="password"
                    component=TextFieldRedux
                    type=showPassword ? 'text' : 'password'
                    label="Your Password"
                    InputProps=
                      endAdornment: (
                        <InputAdornment position="end">
                          <IconButton
                            aria-label="Toggle password visibility"
                            onClick=this.handleClickShowPassword
                            onMouseDown=this.handleMouseDownPassword
                          >
                            showPassword ? <VisibilityOff /> : <Visibility />
                          </IconButton>
                        </InputAdornment>
                      ),
                    
                    required
                    validate=required
                    className=classes.field
                  />
                </FormControl>
              </div>
              <div className=classes.optArea>
                <FormControlLabel
                  className=classes.label
                  control=<Field name="checkbox" component=CheckboxRedux />
                  label="Remember"
                />
                <Button
                  size="small"
                  component=LinkBtn
                  to="/reset-password"
                  className=classes.buttonLink
                >
                  Forgot Password
                </Button>
              </div>
              <div className=classes.btnArea>
                <Button variant="contained" color="primary" size="large" type="submit">
                  Continue
                  <ArrowForward
                    className=classNames(classes.rightIcon, classes.iconSmall)
                    disabled=submitting || pristine
                  />
                </Button>
              </div>
            </form>
          </section>
        </Paper>
      </Fragment>
    );
  


const mapDispatchToProps = dispatch => (
  init: bindActionCreators(loginUser, dispatch),
  loginUser:

);

LoginForm.propTypes = 
  classes: PropTypes.object.isRequired,
  handleSubmit: PropTypes.func.isRequired,
  pristine: PropTypes.bool.isRequired,
  submitting: PropTypes.bool.isRequired,
  loginUser: PropTypes.func.isRequired,
  deco: PropTypes.bool.isRequired,
;

const LoginFormReduxed = reduxForm(
  form: 'immutableExample',
  enableReinitialize: true,
)(LoginForm);

const reducerLogin = 'login';
const reducerUi = 'ui';
const FormInit = connect(
  state => (
    force: state,
    initialValues: state.getIn([reducerLogin, 'usersLogin']),
    deco: state.getIn([reducerUi, 'decoration']),
  ),
  mapDispatchToProps,
)(LoginFormReduxed);

export default withStyles(styles)(FormInit);

login.js

import React from 'react';
import  Helmet  from 'react-helmet';
import brand from 'dan-api/dummy/brand';
import PropTypes from 'prop-types';
import  withStyles  from '@material-ui/core/styles';
import  LoginForm  from 'dan-components';
import styles from 'dan-components/Forms/user-jss';

class Login extends React.Component 
  state = 
    valueForm: [],
  ;

  submitForm(values) 
    const  valueForm  = this.state;
    setTimeout(() => 
      this.setState( valueForm: values );
      console.log(`You submitted:\n\n$valueForm`);
      window.location.href = '/app';
    , 500); // simulate server latency
  

  render() 
    const title = brand.name + ' - Login';
    const description = brand.desc;
    const  classes  = this.props;
    return (
      <div className=classes.root>
        <Helmet>
          <title>title</title>
          <meta name="description" content=description />
          <meta property="og:title" content=title />
          <meta property="og:description" content=description />
          <meta property="twitter:title" content=title />
          <meta property="twitter:description" content=description />
        </Helmet>
        <div className=classes.container>
          <div className=classes.userFormWrap>
            <LoginForm onSubmit=values => this.submitForm(values) />
          </div>
        </div>
      </div>
    );
  


Login.propTypes = 
  classes: PropTypes.object.isRequired,
;

export default withStyles(styles)(Login);

我正在尝试添加我的身份验证。

import axios from 'axios';
import jwt_decode from 'jwt-decode';
import setAuthToken from '../../utils/setAuthToken';

import  GET_ERRORS, SET_CURRENT_USER, USER_LOADING  from '../constants/authConstants';

// Login - get user token
export const loginUser = userData => dispatch => 
  axios
    .post('/api/total/users/login', userData)
    .then(res => 
      // Save to localStorage

      // Set token to localStorage
      const  token  = res.data;
      localStorage.setItem('jwtToken', JSON.stringify(token));
      // Set token to Auth header
      setAuthToken(token);
      // Decode token to get user data
      const decoded = jwt_decode(token);
      // Set current user
      dispatch(setCurrentUser(decoded));
    )
    .catch(err =>
      dispatch(
        type: GET_ERRORS,
        payload: err.response.data,
      ),
    );
;

// Set logged in user
export const setCurrentUser = decoded => 
  return 
    type: SET_CURRENT_USER,
    payload: decoded,
  ;
;

// User loading
export const setUserLoading = () => 
  return 
    type: USER_LOADING,
  ;
;

// Log user out
export const logoutUser = history => dispatch => 
  // Remove token from local storage
  localStorage.removeItem('jwtTokenTeams');
  // Remove auth header for future requests
  setAuthToken(false);
  // Set current user to empty object  which will set isAuthenticated to false
  dispatch(setCurrentUser());

  history.push('/dashboard');
;

和授权器

import  Map, fromJS  from 'immutable';
import  INIT  from '../constants/reduxFormConstants';
import  SET_CURRENT_USER, USER_LOADING  from '../constants/authConstants';

const isEmpty = require('is-empty');

const initialState = 
  usersLogin: Map(
    isAuthenticated: false,
    user: ,
    loading: false,
    remember: false,
  ),
;
const initialImmutableState = fromJS(initialState);
export default function reducer(state = initialImmutableState, action = ) 
  switch (action.type) 
    case INIT:
      return state;
    case SET_CURRENT_USER:
      return 
        ...state,
        isAuthenticated: !isEmpty(action.payload),
        user: action.payload,
      ;
    case USER_LOADING:
      return 
        ...state,
        loading: true,
      ;
    default:
      return state;
  

我很难理解如何才能让这一切一起工作。

添加 app.js

/**
 * app.js
 *
 * This is the entry file for the application, only setup and boilerplate
 * code.
 */

// Needed for redux-saga es6 generator support
import '@babel/polyfill';

// Import all the third party stuff
import React from 'react';
import ReactDOM from 'react-dom';
import  Provider  from 'react-redux';
import  ConnectedRouter  from 'connected-react-router/immutable';
import FontFaceObserver from 'fontfaceobserver';
import history from 'utils/history';
import 'sanitize.css/sanitize.css';

// Import root app
import App from 'containers/App';
import './styles/layout/base.scss';

// Import Language Provider
import LanguageProvider from 'containers/LanguageProvider';

// Load the favicon and the .htaccess file
import '!file-loader?name=[name].[ext]!../public/favicons/favicon.ico'; // eslint-disable-line
import 'file-loader?name=.htaccess!./.htaccess'; // eslint-disable-line

import configureStore from './redux/configureStore';

// Import i18n messages
import  translationMessages  from './i18n';

// Observe loading of Open Sans (to remove open sans, remove the <link> tag in
// the index.html file and this observer)
const openSansObserver = new FontFaceObserver('Open Sans', );

// When Open Sans is loaded, add a font-family using Open Sans to the body
openSansObserver.load().then(() => 
  document.body.classList.add('fontLoaded');
);

// Create redux store with history
const initialState = ;
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');

const render = messages => 
  ReactDOM.render(
    <Provider store=store>
      <LanguageProvider messages=messages>
        <ConnectedRouter history=history>
          <App />
        </ConnectedRouter>
      </LanguageProvider>
    </Provider>,
    MOUNT_NODE,
  );
;

if (module.hot) 
  // Hot reloadable React components and translation json files
  // modules.hot.accept does not accept dynamic dependencies,
  // have to be constants at compile-time
  module.hot.accept(['./i18n', 'containers/App'], () => 
    ReactDOM.unmountComponentAtNode(MOUNT_NODE);
    render(translationMessages);
  );


// Chunked polyfill for browsers without Intl support
if (!window.Intl) 
  new Promise(resolve => 
    resolve(import('intl'));
  )
    .then(() =>
      Promise.all([import('intl/locale-data/jsonp/en.js'), import('intl/locale-data/jsonp/de.js')]),
    ) // eslint-disable-line prettier/prettier
    .then(() => render(translationMessages))
    .catch(err => 
      throw err;
    );
 else 
  render(translationMessages);

// Install ServiceWorker and AppCache in the end since
// it's not most important operation and if main code fails,
// we do not want it installed
if (process.env.NODE_ENV === 'production') 
  require('offline-plugin/runtime').install(); // eslint-disable-line global-require

应用组件

import React from 'react';
import jwt_decode from 'jwt-decode';
import  Switch, Route  from 'react-router-dom';
import NotFound from 'containers/Pages/Standalone/NotFoundDedicated';
import store from '../../redux/configureStore';
import  setCurrentUser, logoutUser  from '../../redux/actions/authActions';
import setAuthToken from '../../utils/setAuthToken';
import Auth from './Auth';
import Application from './Application';
import ThemeWrapper,  AppContext  from './ThemeWrapper';
window.__MUI_USE_NEXT_TYPOGRAPHY_VARIANTS__ = true;



class App extends React.Component 
  render() 
    console.log(this.props);

    return (
      <ThemeWrapper>
        <AppContext.Consumer>
          changeMode => (
            <Switch>
              <Route path="/" exact component=Auth />
              <Route
                path="/app"
                render=props => <Application ...props changeMode=changeMode />
              />
              <Route component=Auth />
              <Route component=NotFound />
            </Switch>
          )
        </AppContext.Consumer>
      </ThemeWrapper>
    );
  


export default App;

身份验证组件

import React from 'react';
import  Switch, Route  from 'react-router-dom';
import Outer from '../Templates/Outer';
import 
  Login,
  LoginV2,
  LoginV3,
  Register,
  RegisterV2,
  RegisterV3,
  ResetPassword,
  LockScreen,
  ComingSoon,
  Maintenance,
  NotFound,
 from '../pageListAsync';

class Auth extends React.Component 
  render() 
    return (
      <Outer>
        <Switch>
          <Route path="/login" component=Login />
          /* <Route path="/login-v2" component=LoginV2 />
          <Route path="/login-v3" component=LoginV3 />
          <Route path="/register" component=Register />
          <Route path="/register-v2" component=RegisterV2 />
          <Route path="/register-v3" component=RegisterV3 /> */
          <Route path="/reset-password" component=ResetPassword />
          <Route path="/lock-screen" component=LockScreen />
          /* <Route path="/maintenance" component=Maintenance />
          <Route path="/coming-soon" component=ComingSoon /> */
          <Route component=NotFound />
        </Switch>
      </Outer>
    );
  


export default Auth;

【问题讨论】:

你有一个主要的App组件吗?它通常涉及在第一页加载时检查本地存储中的令牌,如果用户未登录,则阻止对除登录页面之外的所有路由的访问。棘手的部分是处理加载状态,直到您验证用户是否登录登录,如果需要这样做(取决于您的身份验证提供者)。 @timotgl 是的,我添加了 app.js、app 组件和 auth 组件的代码 【参考方案1】:

使用 PLAin redux 来维护身份验证是不太可能的,因为每当您重新加载时,商店都会刷新。但是,redux 有一个称为 Persisted store

的功能

持久化存储将数据存储在内存中,不会因页面重新加载或类似的操作而刷新。

你可以查看这个Link

没有持久存储的更新:

在这种情况下,从存储中获取 IsLoggedin 状态。

在应用组件中

const App = () => 
    const isLoggedIn = localStorage.getItem("jwtToken") !== null ? true: false

    return (
    <Router>
        
        <Switch>
            <PrivateRoute isLoggedIn=isLoggedIn path="/dashboard">
                <Dashboard />
            </PrivateRoute>
            <Route path="/login">
                <Login authToken=authToken />
            </Route>
        </Switch>
    </Router>
);

然后在私有路由组件中:

const PrivateRoute = (props) => 
    const  children, IsLoggedin, ...rest  = props;

    return (
        <Route
            ...rest
            render=( location ) =>
                IsLoggedin ? (
                    children
                ) : (
                    <Redirect
                        to=
                            pathname: '/login',
                            state:  from: location ,
                        
                    />
                )
            
        />
    );
;

【讨论】:

我之前有过简单的 Redux 身份验证。它检查 jwt 令牌以保持用户登录,否则重定向到登录。 在这种情况下,请检查我更新的答案。希望对您有所帮助。 我的问题和问题与我正在使用的模板有关,并且我在我的问题中发布了该模板。问题是考虑到 redux 和模板路由的设置方式,这种方法不起作用,我不知道如何使它起作用。 这不是“在内存中”存储数据,您的 sn-p 使用 localStorage。而且这个方法和redux没有直接关系,甚至没有react。您可以使用大量库来处理 JWT。 OP 已经在他们的示例中使用了 localStorage。 “持久化存储”不是一个 redux 概念。您的意思是预加载状态吗?将 store 持久化到 localStorage(或其他)是由 redux 生态系统中的几个库完成的,而不是由 redux 本身完成的。 是的,你完全正确。在我的代码 sn-p 中,它使用本地存储,因为我提到它是没有持久存储的更新。

以上是关于将 redux 操作添加到登录表单?的主要内容,如果未能解决你的问题,请参考以下文章

无法通过 React 和 Redux 将相同的表单组件用于添加或编辑模式

更新 redux 状态/提交表单时页面重新加载

将项目添加到 redux-toolkit 中的嵌套数组

将异步操作分派到 redux-saga 后如何捕获 redux 存储中的更改

在成功的异步 redux 操作上转换到另一条路线

如何导出 mapStateToProps 和 Redux 表单?