React/Redux 商店状态未保存
React/Redux store state is not saved
我使用thunk中间件,并将初始状态传递给React,但问题是当我访问其他链接时,React状态没有保存。
登录成功后,应该会渲染dashboard。
当用户尝试转到 /login
页面时,必须将用户重定向到仪表板(这是根路径,/
)。
我也应该使用 redux-router 吗?
我省略了一些代码,但大致如下所示。
init.js
我将商店传递给提供商
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
const store = configureStore();
function requireAuth(nextState, replace) {
const isLoggedIn = store.getState().auth.isLoggedIn;
if (!isLoggedIn) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
});
}
}
<Provider store={store}>
<Router history={browserHistory}>
<Route path='/' component={App} store={store}>
<IndexRoute
components={{
main: MainServices,
aside: Aside
}}
onEnter={requireAuth}
/>
<Route
path="login"
components={{
login: Login
}}
/>
...
</Router>
</Provider>
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';
import initialState from './initialState';
const store = applyMiddleware(thunk)(createStore);
export default function () {
return store(rootReducer, initialState);
}
initialState.js
var initialState = {
auth: {
isLoggedIn: false,
isLoggingIn: false,
response: null,
},
};
export default initialState;
App.jsx
初始应用程序的状态传递给 React 的 props
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import Dashboard from './Dashboard';
import Login from './Login';
import { browserHistory } from 'react-router';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className='appWrapper height'>
{
this.props.auth.isLoggedIn ?
<Dashboard {...this.props} /> : <Login {...this.props} />
}
</div>
);
}
}
let mapStateToProps = function(appState) {
return {
auth: appState.auth,
};
};
let mapDispatchToProps = function(dispatch) {
return {
logoutRequest: function() {
console.log("logoutRequest dispatched!");
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Login.jsx
export default class Login extends React.Component {
constructor(props) {
super(props);
console.log(`componentWillMount in login`);
if (this.props.auth.isLoggedIn) {
console.log(`you already logged in..!`);
browserHistory.push('/');
}
}
render() {
return (
<div className="login-outer">
<Grid className="login-inner">
<Row>
<Col xs={12}>
<LoginHeader />
<LoginContainer />
</Col>
</Row>
</Grid>
</div>
);
}
}
LoginContainer.jsx
export default class LoginContainer extends React.Component {
render() {
return (
<div className="login-container">
<div className="outer">
<div className="inner">
<LoginTitle />
<div className="login-box">
<h2>Sign in</h2>
<LoginInput />
</div>
</div>
</div>
</div>
);
}
}
LoginInput.jsx
import React from 'react';
import { connect } from 'react-redux';
import { Input, ButtonToolbar, Button } from 'react-bootstrap';
import { spring } from 'react-motion';
import Transition from 'react-motion-ui-pack';
import Loader from 'react-loader';
import * as actions from '../../actions/loginActions';
class LoginInput extends React.Component {
constructor(props) {
super(props);
this.state = {
idText: '',
passText: '',
idShow: false,
passShow: false,
loaded: true
};
this.handleIdChange = this.handleIdChange.bind(this);
this.handlePassChange = this.handlePassChange.bind(this);
this.loginRequest = this.loginRequest.bind(this);
}
handleIdChange(e) {
this.setState({
idText: e.target.value
});
if (e.target.value != '') {
this.setState({
idShow: true
});
} else {
this.setState({
idShow: false
});
}
}
handlePassChange(e) {
this.setState({
passText: e.target.value
});
if (e.target.value != '') {
this.setState({
passShow: true
});
} else {
this.setState({
passShow: false
});
}
}
loginRequest(e) {
this.setState({loaded: false});
if (!this.state.idText || !this.state.passText) {
this.setState({loaded: true});
}
if (this.state.idText && this.state.passText) {
this.setState({
loaded: false,
idText: this.state.idText,
passText: this.state.passText,
});
this.props.login(this.state.idText, this.state.passText);
}
e.preventDefault();
}
render() {
return (
<form className="loginForm">
<div className="form-group input-login id">
<input
type="text"
className="form-control"
ref="idText"
placeholder="ID"
value={this.state.idText}
onChange={this.handleIdChange}
/>
<Transition
component={false}
enter={{
opacity: 1
}}
leave={{
opacity: 0
}}
>
{
this.state.idShow &&
<label
htmlFor=""
className="control-label"
>
ID
</label>
}
</Transition>
</div>
<div className="form-group input-login password">
<input
type="password"
className="form-control"
ref="passText"
placeholder="Password"
value={this.state.passText}
onChange={this.handlePassChange}
/>
<Transition
component={false}
enter={{
opacity: 1
}}
leave={{
opacity: 0
}}
>
{
this.state.passShow &&
<label
htmlFor=""
className="control-label"
>
Password
</label>
}
</Transition>
</div>
<Input
type="checkbox"
groupClassName="checkbox-login"
label="Keep me signed in"
/>
<ButtonToolbar>
<Button
href="#"
onClick={this.loginRequest}
>
<div
className="sign-arrow"
hidden={!this.state.loaded}
>
<h6>
ENTER
</h6>
<img src="images/ic-right-arrow-2.svg" alt="" />
</div>
<Loader
className="spinner"
loaded={this.state.loaded}
lines={10}
length={3}
width={2}
radius={4}
corners={1}
rotate={0}
direction={1}
color="#fff"
speed={1.5}
trail={60}
shadow={false}
hwaccel={false}
scale={1}
/>
</Button>
</ButtonToolbar>
</form>
);
}
}
let mapStateToProps = function(appState) {
return {
auth: appState.auth,
};
};
let mapDispatchToProps = function(dispatch) {
return {
login: function(id, pwd) {
dispatch(actions.login(id, pwd));
}
}
};
export default connect(mapStateToProps,mapDispatchToProps)(LoginInput);
loginActions.js
export function loginFailure(error) {
return { error, type: ActionTypes.LOGIN_FAILURE };
}
export function loginSuccess(response) {
return dispatch => {
dispatch({ response, type: ActionTypes.LOGIN_SUCCESS });
browserHistory.push('/');
};
}
export function loginRequest(id, pwd) {
return {
type: ActionTypes.LOGIN_REQUEST,
command: 'login',
lang: 'en',
str: encodeCredentials(id, pwd),
ip: '',
device_id: '',
install_ver: '',
};
}
export function login(id, pwd) {
const credentials = loginRequest(id, pwd);
return dispatch => {
fetchJSON(`${API.ROOT_PATH}${API.END_POINT.LOGIN}`, {
method: 'post',
body: credentials,
}).then(data => {
dispatch(loginSuccess(data));
}).catch(error => {
console.log(`request failed ${error}`);
});
};
}
reducers/index.js
import authReducer from './authReducer';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
auth: authReducer,
});
export default rootReducer;
authReducer.js
import initialState from '../store/initialState';
import * as ActionTypes from '../actionTypes/authActionTypes';
const authReducer = function authReducer(state = initialState.auth, action) {
switch (action.type) {
case ActionTypes.LOGIN_REQUEST:
return Object.assign({}, state, {
isLoggingIn: true,
isLoggedIn: false,
});
case ActionTypes.LOGOUT:
return Object.assign({}, state, {
isLoggedIn: false,
isLoggingIn: false,
});
case ActionTypes.LOGIN_FAILURE:
return Object.assign({}, state, {
error: action.error,
isLoggingIn: false,
isLoggedIn: false,
});
case ActionTypes.LOGIN_SUCCESS:
return Object.assign({}, state, {
isLoggedIn: true,
isLoggingIn: false,
response: action.response,
});
default:
return state;
}
};
export default authReducer;
无法在两个不同的 http 请求之间保存您的商店。唯一的方法不是重新加载页面本身,但是您可以为用户模拟当用户单击 link 时页面被重新加载的操作,历史记录 API 是所有现代浏览器中的本机对象。但最好的方法是采用 react-router which will take care about everything you need in your react App, and if you want to keep state of the router in your redux store you can use redux-simple-router.
我使用thunk中间件,并将初始状态传递给React,但问题是当我访问其他链接时,React状态没有保存。
登录成功后,应该会渲染dashboard。
当用户尝试转到 /login
页面时,必须将用户重定向到仪表板(这是根路径,/
)。
我也应该使用 redux-router 吗?
我省略了一些代码,但大致如下所示。
init.js
我将商店传递给提供商
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
const store = configureStore();
function requireAuth(nextState, replace) {
const isLoggedIn = store.getState().auth.isLoggedIn;
if (!isLoggedIn) {
replace({
pathname: '/login',
state: { nextPathname: nextState.location.pathname }
});
}
}
<Provider store={store}>
<Router history={browserHistory}>
<Route path='/' component={App} store={store}>
<IndexRoute
components={{
main: MainServices,
aside: Aside
}}
onEnter={requireAuth}
/>
<Route
path="login"
components={{
login: Login
}}
/>
...
</Router>
</Provider>
configureStore.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/index';
import initialState from './initialState';
const store = applyMiddleware(thunk)(createStore);
export default function () {
return store(rootReducer, initialState);
}
initialState.js
var initialState = {
auth: {
isLoggedIn: false,
isLoggingIn: false,
response: null,
},
};
export default initialState;
App.jsx
初始应用程序的状态传递给 React 的 props
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import Dashboard from './Dashboard';
import Login from './Login';
import { browserHistory } from 'react-router';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className='appWrapper height'>
{
this.props.auth.isLoggedIn ?
<Dashboard {...this.props} /> : <Login {...this.props} />
}
</div>
);
}
}
let mapStateToProps = function(appState) {
return {
auth: appState.auth,
};
};
let mapDispatchToProps = function(dispatch) {
return {
logoutRequest: function() {
console.log("logoutRequest dispatched!");
}
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Login.jsx
export default class Login extends React.Component {
constructor(props) {
super(props);
console.log(`componentWillMount in login`);
if (this.props.auth.isLoggedIn) {
console.log(`you already logged in..!`);
browserHistory.push('/');
}
}
render() {
return (
<div className="login-outer">
<Grid className="login-inner">
<Row>
<Col xs={12}>
<LoginHeader />
<LoginContainer />
</Col>
</Row>
</Grid>
</div>
);
}
}
LoginContainer.jsx
export default class LoginContainer extends React.Component {
render() {
return (
<div className="login-container">
<div className="outer">
<div className="inner">
<LoginTitle />
<div className="login-box">
<h2>Sign in</h2>
<LoginInput />
</div>
</div>
</div>
</div>
);
}
}
LoginInput.jsx
import React from 'react';
import { connect } from 'react-redux';
import { Input, ButtonToolbar, Button } from 'react-bootstrap';
import { spring } from 'react-motion';
import Transition from 'react-motion-ui-pack';
import Loader from 'react-loader';
import * as actions from '../../actions/loginActions';
class LoginInput extends React.Component {
constructor(props) {
super(props);
this.state = {
idText: '',
passText: '',
idShow: false,
passShow: false,
loaded: true
};
this.handleIdChange = this.handleIdChange.bind(this);
this.handlePassChange = this.handlePassChange.bind(this);
this.loginRequest = this.loginRequest.bind(this);
}
handleIdChange(e) {
this.setState({
idText: e.target.value
});
if (e.target.value != '') {
this.setState({
idShow: true
});
} else {
this.setState({
idShow: false
});
}
}
handlePassChange(e) {
this.setState({
passText: e.target.value
});
if (e.target.value != '') {
this.setState({
passShow: true
});
} else {
this.setState({
passShow: false
});
}
}
loginRequest(e) {
this.setState({loaded: false});
if (!this.state.idText || !this.state.passText) {
this.setState({loaded: true});
}
if (this.state.idText && this.state.passText) {
this.setState({
loaded: false,
idText: this.state.idText,
passText: this.state.passText,
});
this.props.login(this.state.idText, this.state.passText);
}
e.preventDefault();
}
render() {
return (
<form className="loginForm">
<div className="form-group input-login id">
<input
type="text"
className="form-control"
ref="idText"
placeholder="ID"
value={this.state.idText}
onChange={this.handleIdChange}
/>
<Transition
component={false}
enter={{
opacity: 1
}}
leave={{
opacity: 0
}}
>
{
this.state.idShow &&
<label
htmlFor=""
className="control-label"
>
ID
</label>
}
</Transition>
</div>
<div className="form-group input-login password">
<input
type="password"
className="form-control"
ref="passText"
placeholder="Password"
value={this.state.passText}
onChange={this.handlePassChange}
/>
<Transition
component={false}
enter={{
opacity: 1
}}
leave={{
opacity: 0
}}
>
{
this.state.passShow &&
<label
htmlFor=""
className="control-label"
>
Password
</label>
}
</Transition>
</div>
<Input
type="checkbox"
groupClassName="checkbox-login"
label="Keep me signed in"
/>
<ButtonToolbar>
<Button
href="#"
onClick={this.loginRequest}
>
<div
className="sign-arrow"
hidden={!this.state.loaded}
>
<h6>
ENTER
</h6>
<img src="images/ic-right-arrow-2.svg" alt="" />
</div>
<Loader
className="spinner"
loaded={this.state.loaded}
lines={10}
length={3}
width={2}
radius={4}
corners={1}
rotate={0}
direction={1}
color="#fff"
speed={1.5}
trail={60}
shadow={false}
hwaccel={false}
scale={1}
/>
</Button>
</ButtonToolbar>
</form>
);
}
}
let mapStateToProps = function(appState) {
return {
auth: appState.auth,
};
};
let mapDispatchToProps = function(dispatch) {
return {
login: function(id, pwd) {
dispatch(actions.login(id, pwd));
}
}
};
export default connect(mapStateToProps,mapDispatchToProps)(LoginInput);
loginActions.js
export function loginFailure(error) {
return { error, type: ActionTypes.LOGIN_FAILURE };
}
export function loginSuccess(response) {
return dispatch => {
dispatch({ response, type: ActionTypes.LOGIN_SUCCESS });
browserHistory.push('/');
};
}
export function loginRequest(id, pwd) {
return {
type: ActionTypes.LOGIN_REQUEST,
command: 'login',
lang: 'en',
str: encodeCredentials(id, pwd),
ip: '',
device_id: '',
install_ver: '',
};
}
export function login(id, pwd) {
const credentials = loginRequest(id, pwd);
return dispatch => {
fetchJSON(`${API.ROOT_PATH}${API.END_POINT.LOGIN}`, {
method: 'post',
body: credentials,
}).then(data => {
dispatch(loginSuccess(data));
}).catch(error => {
console.log(`request failed ${error}`);
});
};
}
reducers/index.js
import authReducer from './authReducer';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
auth: authReducer,
});
export default rootReducer;
authReducer.js
import initialState from '../store/initialState';
import * as ActionTypes from '../actionTypes/authActionTypes';
const authReducer = function authReducer(state = initialState.auth, action) {
switch (action.type) {
case ActionTypes.LOGIN_REQUEST:
return Object.assign({}, state, {
isLoggingIn: true,
isLoggedIn: false,
});
case ActionTypes.LOGOUT:
return Object.assign({}, state, {
isLoggedIn: false,
isLoggingIn: false,
});
case ActionTypes.LOGIN_FAILURE:
return Object.assign({}, state, {
error: action.error,
isLoggingIn: false,
isLoggedIn: false,
});
case ActionTypes.LOGIN_SUCCESS:
return Object.assign({}, state, {
isLoggedIn: true,
isLoggingIn: false,
response: action.response,
});
default:
return state;
}
};
export default authReducer;
无法在两个不同的 http 请求之间保存您的商店。唯一的方法不是重新加载页面本身,但是您可以为用户模拟当用户单击 link 时页面被重新加载的操作,历史记录 API 是所有现代浏览器中的本机对象。但最好的方法是采用 react-router which will take care about everything you need in your react App, and if you want to keep state of the router in your redux store you can use redux-simple-router.