将 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 将相同的表单组件用于添加或编辑模式