Here is the code
//REDUCER.js
import { call, take, fork } from 'redux-saga/effects';
import { request } from '../../utils';
export const LOGIN_REQUEST = "LOGIN_REQUEST";
const LOGIN_SUCCESS = "LOGIN_REQUEST";
const LOGIN_FAILED = "LOGIN_FAILED";
const initialState = { authenticated: false, loading: false };
export default function (state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST:
return { ...state, loading: true };
case LOGIN_SUCCESS:
return { ...state, loading: false, authenticated: true, user: action.payload };
case LOGIN_FAILED:
return { ...state, loading: false, };
default:
return { ...state }
}
}
export function loginRequest(loginData) {
return { type: LOGIN_REQUEST, loginData }
}
export function loginApi(formData) {
return new Promise((resolve, reject) => {
request.post('/login', formData)
.then(response => {
resolve(response)
})
.catch(error => reject(error));
})
}
export function* handleLogin(formData) {
try {
console.log('handleLogin');
const payload = yield call(loginApi, formData);
console.log(payload)
} catch (e) {
console.log('error ', e);
}
}
export function* watchLoginRequest() {
yield take(LOGIN_REQUEST, handleLogin);
}
//ROOTSAGA.js
import { all } from 'redux-saga/effects';
import { watchLoginRequest } from './modules/auth/reducer';
export default function* Root() {
yield all([
watchLoginRequest,
])
}
//store.js
import createHistory from 'history/createBrowserHistory';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../rootReducer';
import rootSaga from '../rootSagas';
const history = createHistory();
const sagaMiddleware = createSagaMiddleware();
const middlewares = [
sagaMiddleware,
thunk,
routerMiddleware(history),
];
/* eslint-disable no-underscore-dangle */
const composeEnhancers = process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(...middlewares)));
/* eslint-enable */
sagaMiddleware.run(rootSaga);
export default store;
Here is my component:
import { Button, Form, Icon, Input, Row } from 'antd';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { config } from '../../../utils';
import { loginRequest } from '../reducer';
import { Div, DivLogo } from '../styles';
const FormItem = Form.Item;
const formDecorator = Form.create();
const reduxConnect = connect(null, { loginRequest });
class Login extends Component {
static propTypes = {};
static defaultProps = {};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log(values);
this.props.loginRequest(values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Div>
<DivLogo>
<span>{config.logoText}</span>
</DivLogo>
<Form onSubmit={this.handleSubmit}>
<FormItem hasFeedback>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username' }],
})(
<Input
size="large"
prefix={<Icon type="user" style={{ fontSize: 13 }} />}
placeholder="Username" />,
)}
</FormItem>
<FormItem hasFeedback>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password' }],
})(
<Input
type="password"
size="large"
prefix={<Icon type="lock" style={{ fontSize: 13 }} />}
placeholder="Password"
/>,
)}
</FormItem>
<Row>
<Button type='primary' htmlType="submit" size='large' loading={false}>
Login
</Button>
</Row>
</Form>
</Div>
);
}
}
export default reduxConnect(formDecorator(Login));
When I submit the form it will execute the loginRequest function. I've checked it execute successfully.
The problem is watchLoginRequest never run. It never get into the handleLogin function. The console.log never shown on console.
Any solution?
In your example, there is no "live process" circuit, and source code can be simplified. So, your snippet does only catch events with specific type and invokes callback. Effect takeEvery is default use case for this snippet
export function handleLogin() {
// Your definitive saga
}
// .....
export default function* Root() {
yield [takeEvery(LOGIN_REQUEST, handleLogin)]
}
More than that, don't mix saga handler and reducer. If you want to invoke some reducer from saga, just segregate actions name to REQUEST/RESPONSE format, and then catch all *_REQUEST events and put *_RESPONSE after Process Manager (saga) worker ends.
Related
I am trying to check if the user is logged in or not in Register.js component but I am getting undefined values of auth state in it. Please look into it if you can find where I am doing wrong. I have tried doing a lot of changes still I was not able to get it right.
Thank you for all of your help.
Ignore the context files
App.js
import { useEffect } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import "./App.css";
import { Navbar } from "./components/layout/Navbar";
import { Home } from "./components/pages/Home";
import { MyList } from "./components/favorites/MyList";
import Register from "./components/auth/Register";
import Login from "./components/auth/Login";
import { Provider } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/auth";
import { setAuthToken } from "./utils/setAuthToken";
if (localStorage.token) {
setAuthToken(localStorage.token);
}
function App() {
useEffect(() => {
store.dispatch(loadUser());
}, []);
return (
<Provider store={store}>
<>
<div>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/mylist' element={<MyList />} />
<Route path='/signin' element={<Login />} />
<Route path='/signup' element={<Register />} />
</Routes>
</div>
</>
</Provider>
);
}
export default App;
Register.js
import React, { useContext, useEffect, useState } from "react";
import { Link, Navigate, useNavigate } from "react-router-dom";
import { connect } from "react-redux";
import { register } from "../../actions/auth";
import axios from "axios";
const Register = ({ register, isAuthenticated, history, location, auth }) => {
const navigate = useNavigate();
const [user, setUser] = useState({
name: "",
email: "",
password: "",
password2: "",
});
const { name, email, password, password2 } = user;
const onFormDataChange = (e) => {
setUser({
...user,
[e.target.name]: e.target.value,
});
};
const onFormSubmit = async (e) => {
e.preventDefault();
register({ name, email, password });
};
console.log(auth);
console.log(isAuthenticated);
return (
<div>
<h1>Sign Up</h1>
<form action='' onSubmit={onFormSubmit}>
<div className='form-group'>
<label htmlFor='name'>Name</label>
<input
type='text'
name='name'
value={name}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='email'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password'>Password</label>
<input
type='password'
name='password'
value={password}
onChange={onFormDataChange}
/>
</div>
<div className='form-group'>
<label htmlFor='password2'>Password</label>
<input
type='password'
name='password2'
value={password2}
onChange={onFormDataChange}
/>
</div>
<input type='submit' value='Signup' />
<p>
Already have an account? <Link to='/signin'>Sign In</Link>
</p>
</form>
</div>
);
};
const mapStateToProps = (state) => {
const { isAuthenticated } = state.auth.isAuthenticated;
const { auth } = state.auth;
return {
isAuthenticated,
};
};
export default connect(mapStateToProps, { register })(Register);
index.js
import { combineReducers } from "redux";
import auth from "./auth";
export default combineReducers({
auth,
});
/auth/types.js
export const REGISTER_SUCCESS = "REGISTER_SUCCESS";
export const REGISTER_FAIL = "REGISTER_FAIL";
export const USER_LOADED = "USER_LOADED";
export const AUTH_ERROR = "AUTH_ERROR";
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGOUT = "LOGOUT";
export const CLEAR_ERRORS = "CLEAR_ERRORS";
actions/auth.js
import axios from "axios";
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "./types";
import { setAuthToken } from "../utils/setAuthToken";
export const loadUser = () => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (error) {
dispatch({
type: AUTH_ERROR,
});
}
};
export const register =
({ name, email, password }) =>
async (dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post("/api/users", body, config);
dispatch({
type: REGISTER_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (error) {
dispatch({
type: REGISTER_FAIL,
});
}
};
export const login = (email, password) => async (dispatch) => {
const config = {
headers: {
"Content-Type": "application/json",
},
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post("/api/auth", body, config);
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch(loadUser());
} catch (error) {
dispatch({
type: LOGIN_FAIL,
});
}
};
reducers/auth.js
/* eslint-disable default-case */
/* eslint-disable import/no-anonymous-default-export */
import {
REGISTER_SUCCESS,
REGISTER_FAIL,
USER_LOADED,
AUTH_ERROR,
LOGIN_SUCCESS,
LOGIN_FAIL,
} from "../actions/types";
const initalState = {
token: localStorage.getItem("token"),
isAuthenticated: false,
loading: true,
user: null,
};
export default function (state = initalState, action) {
const { type, payload } = action;
switch (type) {
case USER_LOADED:
return {
...state,
user: payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_SUCCESS:
case LOGIN_SUCCESS:
localStorage.setItem("token", payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false,
};
case REGISTER_FAIL:
case AUTH_ERROR:
case LOGIN_FAIL:
localStorage.removeItem("token");
return {
...state,
token: null,
isAuthenticated: false,
loading: true,
};
default:
return state;
}
}
store.js
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const intialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
intialState,
composeWithDevTools(applyMiddleware(...middleware))
);
export default store;
I might be wrong, but in you Register.js, in mapStateToProps, you doing following thing:
const { isAuthenticated } = state.auth.isAuthenticated;
const { auth } = state.auth;
It seems like it's not correct, unless your state has following structure:
{
auth: {
isAuthenticated: { isAuthenticated: *value here* },
auth: *value here*
}
}
I think, here might be mistake, and you should destructure values following way:
const { isAuthenticated } = state.auth;
const { auth } = state;
I have a login form (built with Ant Design's Form). This is all hooked up to Redux Saga. In this Saga, I am making an API call, and everything seems to be working, but mysteriously, when I dispatch any other action using put, there seems to be an extra mysterious API call that fails because it's not reading from the form correctly.
My Form:
import React from 'react';
import { connect } from 'react-redux';
import { Form, Icon, Input, Button } from 'antd';
import { FormComponentProps } from 'antd/lib/form';
import { loginRequest, ICredentials } from '../redux/auth';
import { FormContainer, LoginContainer } from './styles';
type Props = {
login: (data: ICredentials) => {};
}
class LoginForm extends React.Component<Props & FormComponentProps> {
handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.login(values)
}
});
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<FormContainer>
<Form className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="Username" />
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="Password" />
)}
</Form.Item>
<LoginContainer>
<Button type="primary" htmlType="submit" className="login-form-button" onClick={this.handleSubmit}>
Log in
</Button>
</LoginContainer>
</Form>
</FormContainer>
);
}
}
const mapDispatchToProps = { login: loginRequest };
const WrappedLoginForm = Form.create()(LoginForm);
export default connect(null, mapDispatchToProps)(WrappedLoginForm);
My Action:
export const loginRequest = (data: ICredentials): Action => ({
type: Actions.LoginRequest,
payload: data,
});
My Reducer:
const initialState: State = {
loading: false,
}
const reducer: Reducer<State> = (state = initialState, action: Actions & any) => {
switch (action.type) {
case Actions.LoginRequest:
return {
...state,
loading: true,
}
case Actions.LoginSuccess:
return {
...state,
loading: false,
}
default:
return state;
}
}
export default reducer;
my Sagas:
import { all, call, takeLatest, put } from 'redux-saga/effects';
import axios from 'axios';
import { push } from 'react-router-redux'
import message from 'antd/lib/message';
import { loginRequest, LoginRequest, loginSuccess } from './actions';
import { ICredentials } from './model';
import { AccessToken } from '../../storage/token';
const login = (payload: ICredentials) => axios({
method: 'POST',
url: //....
data: {
username: payload.username,
password: payload.password,
grant_type: 'password',
scope: 'admin,super_admin',
},
headers: {
'Content-Type': 'application/json',
Authorization: //....
}
});
function* loginSaga({ payload }: LoginRequest) {
try {
const data = yield call(login, payload);
AccessToken.set({ ...data.data, retrieved_at: Date.now() / 1000 })
yield call(message.success, 'Welcome!');
// yield all([
// put(loginSuccess()),
// put(push('/'))
// ]);
// yield put(loginSuccess());
// yield put(push('/'))
} catch (err) {
console.log(err)
}
}
function* watcherSaga() {
yield takeLatest(loginRequest, loginSaga)
}
export default watcherSaga;
In this saga, when the AccessToken is set, I ideally want to use react-router-redux to push the user into another route. However, it seems that there's a mysterious API call that's being made and it fails because there are no credentials being passed.
Pictured here is the API call coming back with a 200, but then comes back with a 400 because it's looking for username again
I suspect that it's the form that may be at fault, and though I don't want to switch to another form library, I feel like I may have to do that. Does anyone have any thoughts?
takeLatest ideally must be provided with a string. In your case a function is passed which returns an object.
takeLatest does not check the contents(i.e keys and values) of the object. That is something you would have to do on your own.
So, no matter what action is dispatched the login saga is started, which calls the login api, with an inappropriate payload and hence the error.
So, to avoid the error you could pass a string to takeLatest or in other words initiate the login saga only when Actions.LoginRequest(an action of type Actions.LoginRequest) is dispatched.
yield takeLatest(Actions.LoginRequest, loginSaga)
I'm using reduxForm 7.4.2 , I want to submit form and display server side validation error , but it gives me below error :
Server response :
Here is LoginForm component :
import React,{Component} from 'react'
import InputGroup from '../ui/InputGroup';
import { bindActionCreators } from "redux"
import { Field , reduxForm,SubmissionError} from 'redux-form';
import {faEnvelope,faLock} from '#fortawesome/free-solid-svg-icons'
import { connect } from 'react-redux';
import { loginUser,loginUserFailure,loginUserSuccess } from '../../actions/authActions';
class LoginForm extends Component {
constructor(){
super();
this.submitLoginForm=this.submitLoginForm.bind(this);
}
submitLoginForm=values=>{
this.props.loginUser(values);
}
render() {
const { handleSubmit, submitting} = this.props;
return(
<form onSubmit={handleSubmit(this.submitLoginForm)}>
<Field component={InputGroup} type="email" placeholder="Email" id="email" name="email" icon={faEnvelope}></Field>
<Field component={InputGroup} type="password" placeholder="Password" id="password" name="password" icon={faLock}></Field>
<button type="submit" className="btn btn-success btn-block" disabled={submitting}>Login</button>
</form>
)
}
}
const mapStateToProps = (state, ownProps) => ({
user:state.user
})
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
loginUser,
loginUserFailure,
loginUserSuccess,
}, dispatch)
}
LoginForm = connect(mapStateToProps,mapDispatchToProps)(LoginForm);
LoginForm = reduxForm({ form: 'loginForm'})(LoginForm);
export default LoginForm;
src/actions/authActions :
import axios from 'axios';
import { SubmissionError} from 'redux-form';
export const LOGIN_USER = 'LOGIN_USER';
export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS';
export const LOGIN_USER_FAILURE = 'LOGIN_USER_FAILURE';
export const loginUser =userdata=>{
return dispatch => {
try {
const request = axios.post("/api/auth/login", userdata);
request
.then(res => {
dispatch(loginUserSuccess(request));
})
.catch(e => {
//dispatch(loginUserFailure(e));
dispatch(loginUserFailure(e.response.data));
throw new SubmissionError(e.response.data);
});
} catch (e) {
dispatch(loginUserFailure(e));
}
};
}
export const loginUserSuccess=user=>{
return {
type:LOGIN_USER_SUCCESS,
payload:user
}
}
export const loginUserFailure=error=>{
return {
type:LOGIN_USER_FAILURE,
payload:error
}
}
src/reducers/authReducer.js :
import { LOGIN_USER,LOGIN_USER_SUCCESS,LOGIN_USER_FAILURE} from './../actions/authActions';
const INITIAL_STATE = {isAuthenticated: false,user: null, status:null, error:null, loading: false};
export default function(state = INITIAL_STATE, action) {
let error;
switch (action.type) {
case LOGIN_USER:
return { ...state, isAuthenticated: false,user: null, status:'login', error:null, loading: true};
case LOGIN_USER_SUCCESS:
return { ...state, isAuthenticated: true,user: action.payload.user, status:'authenticated', error:null, loading: false};
case LOGIN_USER_FAILURE:
error = action.payload.data || {message: action.payload.message};
return { ...state,isAuthenticated: false, user:null, status:'login', error:error, loading: false};
default:
return state;
}
};
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
root reducer
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form'
import authReducer from './authReducer';
export default combineReducers({
form: formReducer,
auth:authReducer
});
There are two things here which you are doing incorrectly. Maybe I am missing other issues, but I am sure about these two:
Bind your action creators in dispatch to be able to dispatch the action in dumbcomponent Drawer. Do it something like this(only changed part, I am editing):
import { bindActionCreators } from "redux"
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
loginUser,
loginUserFailure,
loginUserSuccess,
}, dispatch)
}
You must be using redux-thunk as middleware. It is used to handle asynchronous dispatches when using redux with react. Your api call is asynchronous, so you cannot directly return the response after making axios post call. you need to return a function there, something this way:
export const loginUser = userdata => {
return dispatch => {
try {
const request = axios.post("/api/auth/login", userdata);
return request
.then(res => {
dispatch(loginUserSuccess(request));
})
.catch(e => {
dispatch(loginUserFailure(e));
});
} catch (e) {
dispatch(loginUserFailure(e));
}
};
};
And remove the promise handling in your dumbcomponent. because all your data is now available in redux, which you can get through mapStateToProps.
Note: I may have not taken care of curly braces. Please check that and feedbacks are welcome
Application resets to initial screen after form submitting (redux-form or Formik). The Login screen is the second screen in initial route (first is the Start screen). After submitting to occur a call dispatch and return new state -> { ...state, ...{ isLoading: true } } and on this step my app was resets.
What am I doing wrong? How to make the form work without a reset?
LoginScreen.js
import React, { Component } from 'react'
import { View, Text, Button } from 'react-native'
import { connect } from 'react-redux'
import { LoginForm } from '../../forms'
import { userLogin } from '../../store/modules/account'
import s from './styles'
class LoginScreen extends Component {
onLogin = (values, bag) => {
const { userLogin } = this.props
const { email, password } = values
userLogin({ email, password })
}
render() {
const { account, navigation } = this.props
return (
<View style={s.wrapper}>
<View style={s.mainContainer}>
<Text>This is SignIiiiiiin!</Text>
<LoginForm
handleLogin={this.onLogin}
/>
</View>
</View>
)
}
}
const mapStateToProps = ({ account }) => ({
account,
})
export default connect(mapStateToProps, {
userLogin
})(LoginScreen)
LoginForm.js
import React, { Component } from 'react'
import { View, TouchableOpacity, Button } from 'react-native'
import { Formik } from 'formik'
import { FieldInput } from '../../components'
import s from './styles'
class LoginForm extends Component {
render() {
const {
handleLogin,
} = this.props
return (
<View style={s.wrapper}>
<Formik
initialValues={{ email: '', password: '', confirmPassword: '' }}
onSubmit={handleLogin}
render={({
values,
handleSubmit,
setFieldValue,
errors,
touched,
setFieldTouched,
isValid,
isSubmitting,
}) => (
<React.Fragment>
<FieldInput
label="Email"
autoCapitalize="none"
value={values.email}
onChange={setFieldValue}
onTouch={setFieldTouched}
name="email"
error={touched.email && errors.email}
/>
<FieldInput
label="Password"
autoCapitalize="none"
secureTextEntry
value={values.password}
onChange={setFieldValue}
onTouch={setFieldTouched}
name="password"
error={touched.password && errors.password}
/>
<FieldInput
label="Confirm Password"
autoCapitalize="none"
secureTextEntry
value={values.confirmPassword}
onChange={setFieldValue}
onTouch={setFieldTouched}
name="confirmPassword"
error={touched.confirmPassword && errors.confirmPassword}
/>
<Button
backgroundColor="blue"
title="Submit"
onPress={handleSubmit}
disabled={!isValid || isSubmitting}
loading={isSubmitting}
/>
</React.Fragment>
)}
/>
</View>
)
}
}
export default LoginForm
store/index.js
import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import { persistStore, persistReducer } from 'redux-persist'
import logger from 'redux-logger'
import storage from 'redux-persist/lib/storage'
import makeRootReducer from './reducers'
const persistConfig = {
key: 'root',
storage,
blacklist: ['account']
}
const persistedReducer = persistReducer(persistConfig, makeRootReducer)
const enhancer = compose(applyMiddleware(thunk, logger))
export default function configureStore() {
const store = createStore(persistedReducer, enhancer)
const persistor = persistStore(store)
return { store, persistor }
}
store/modules/account.js
import { AsyncStorage } from 'react-native'
import { cloneDeep, assignIn, merge } from 'lodash'
import axios from 'axios'
import moment from 'moment'
import CNST from '../constants'
import { ENV } from '../../config'
// ------------------------------------
// Actions
// ------------------------------------
export function userLogin(data = {}) {
const username = data.email
const password = data.password
return (dispatch) => {
dispatch({
type: CNST.ACCOUNT.LOGIN.LOADING
})
/*
axios.post(`${ENV.ROOT_URL}/user/login`, { username, password })
.then((response) => {
const { token } = response.data
const validFrom = moment().format()
dispatch({
type: CNST.ACCOUNT.LOGIN.SUCCESS,
response: { token, username, validFrom }
})
return response
})
.catch((error) => {
console.log('error', error.response)
dispatch({
type: CNST.ACCOUNT.LOGIN.FAILED,
response: error
})
})
*/
}
}
// ------------------------------------
// Reducers
// ------------------------------------
const initialState = {
id: '',
email: '',
username: '',
token: '',
error: {},
isLoading: false
}
export default function accountReducer(state = cloneDeep(initialState), action) {
switch (action.type) {
case CNST.ACCOUNT.LOGIN.SUCCESS: {
// console.log('action', action)
const { token, username, validFrom } = action.response
const _items = [
['token', token || ''],
['username', username || ''],
['validFrom', validFrom || ''],
]
AsyncStorage.multiSet(_items)
return { ...state, ...{ error: {}, token, username, email: username, isLoading: false } }
}
case CNST.ACCOUNT.LOGIN.FAILED:
return { ...state, ...{ error: action.response, isLoading: false } }
case CNST.ACCOUNT.LOGIN.LOADING:
return { ...state, ...{ isLoading: true } }
default:
return state
}
}
store/constants/account.js
export default {
LOGIN: {
LOADING: 'LOGIN',
SUCCESS: 'LOGIN_SUCCESS',
FAILED: 'LOGIN_FAILED'
}
}
store/reducers.js
import { combineReducers } from 'redux'
import account from './modules/account'
export default combineReducers({
account,
})
I fixed this issue. The problem was in the main container where I mounting Navigator and pass state for changing routes between different Stacks. I passed all state object and when one of object`s item was changed Navigator resetting navigation states.
. . .
return (
<View style={{ flex: 1 }}>
<Navigator
onNavigationStateChange={() => Keyboard.dismiss()}
/>
</View>
)
}
}
const mapStateToProps = ({ account }) => ({
account // <-- bad idea
token: account.token // <-- good one
})
export default connect(mapStateToProps, {
getSomeData
})(MainContainer)
I'm building react application using react-boilerplate. I have created authentication containers using react-boilerplate generator features and combine it with this public api . It will return token after we passed email & password.
How to redirect to other component / page after set the token to browser localStorage ?
Thank you for your time.
reducers.js
import { fromJS } from 'immutable';
import {
CHANGE_EMAIL,CHANGE_PASSWORD,LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE
} from './constants';
const initialState = fromJS({
email: '',
password: '',
isFetching: false,
isAuthenticated: false,
errorMessage: '',
});
function loginReducer(state = initialState, action) {
switch (action.type) {
case CHANGE_EMAIL:
return state
.set('email', action.email);
case CHANGE_PASSWORD:
return state
.set('password', action.password);
case LOGIN_REQUEST:
return state
.set('isFetching', true)
.set('isAuthenticated', false)
.set('user', action.creds);
case LOGIN_SUCCESS:
return state
.set('isFetching', false)
.set('isAuthenticated', true)
.set('errorMessage', '');
case LOGIN_FAILURE:
return state
.set('isFetching', false)
.set('isAuthenticated', false)
.set('errorMessage', action.message);
default:
return state;
}
}
export default loginReducer;
saga.js
import { call, put, select, takeLatest, takeEvery } from 'redux-saga/effects';
import { withRouter } from 'react-router-dom';
import { LOGIN_REQUEST } from './constants';
import { loginError, receiveLogin } from './actions';
import { postUserLogin } from './api';
import { makeSelectEmail, makeSelectPassword } from './selectors';
export function* loginUser() {
const requestURL = 'https://reqres.in/api/login';
try {
const email = yield select(makeSelectEmail());
const password = yield select(makeSelectPassword());
const creds = {
email: email,
password: password,
}
// test post
const headers = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: password,
}),
};
const users = yield call(postUserLogin,creds);
const raw_token = JSON.stringify(users.token);
const token_id = JSON.parse(raw_token);
console.log("token_id : "+token_id);
yield put(receiveLogin(token_id));
localStorage.setItem('access_token', token_id);
} catch (err) {
console.log(err);
let error;
try {
error = yield err.response.json();
} catch (e) {
error = { errors: [{ detail: `${err.name}: ${err.message}` }] };
}
yield put(loginError(error.error));
}
}
// Individual exports for testing
export default function* defaultSaga() {
// See example in containers/HomePage/saga.js
yield takeEvery(LOGIN_REQUEST, loginUser);
}
index.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import { FormattedMessage } from 'react-intl';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import { Button, FormGroup, FormControl, ControlLabel, Col, PageHeader, HelpBlock } from "react-bootstrap";
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
import { makeSelectLogin,makeSelectEmail, makeSelectPassword, makeSelectErrorMessage, makeSelectIsAuthenticated } from './selectors';
import reducer from './reducer';
import saga from './saga';
import messages from './messages';
import { changeEmail, changePassword, requestLogin } from './actions';
export class Login extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentDidMount() {
if ((this.props.email && this.props.email.trim().length > 0) && (this.props.password && this.props.password.length > 0) ) {
this.props.onSubmitForm();
}
}
renderErrorMessage(){
if(this.props.errorMessage){
return(
<div className="info-red">
{this.props.errorMessage}
</div>
);
}
}
render() {
return (
<div>
<Helmet>
<title>Login</title>
<meta name="description" content="Description of TestPage" />
</Helmet>
<div className="container-fluid">
<div className="row">
<div className="col-md-6 col-md-offset-3">
<div className="login-form">
<form name="loginForm" onSubmit={this.props.onSubmitForm}>
<div className="form-group">
<label htmlFor="email">Email address</label>
<input type='email'
id="email"
className="form-control"
placeholder='Email Address'
required autoFocus
value={this.props.email}
onChange={this.props.onChangeEmail}/>
<FormControl.Feedback />
<HelpBlock>Email cannot be blank</HelpBlock>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input id="password"
type="password"
className="form-control"
placeholder="Password"
required
value={this.props.password}
onChange={this.props.onChangePassword}/>
<FormControl.Feedback />
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary">Submit</button>
</div>
</form>
{this.renderErrorMessage()}
</div>
</div>
</div>
</div>
</div>
);
}
}
Login.propTypes = {
email: PropTypes.string,
password: PropTypes.string,
errorMessage: PropTypes.string,
isAuthenticated: PropTypes.bool.isRequired,
onChangeEmail: PropTypes.func,
onChangePassword: PropTypes.func,
onSubmitForm: PropTypes.func,
};
const mapStateToProps = createStructuredSelector({
login: makeSelectLogin(),
email: makeSelectEmail(),
password: makeSelectPassword(),
errorMessage: makeSelectErrorMessage(),
isAuthenticated: makeSelectIsAuthenticated(),
});
function mapDispatchToProps(dispatch) {
return {
onChangeEmail: (evt) => dispatch(changeEmail(evt.target.value)),
onChangePassword: (evt) => dispatch(changePassword(evt.target.value)),
onSubmitForm: (evt) => {
if (evt !== undefined && evt.preventDefault) evt.preventDefault();
const creds = {
email: email.value,
password: password.value,
}
dispatch(requestLogin(creds));
},
fetchPost: (evt) => {
dispatch(fetchPosts());
},
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'login', reducer });
const withSaga = injectSaga({ key: 'login', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(Login);
action.js
import {
CHANGE_EMAIL,CHANGE_PASSWORD,LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE
} from './constants';
export function changeEmail(email) {
return {
type: CHANGE_EMAIL,
email,
};
}
export function changePassword(password) {
return {
type: CHANGE_PASSWORD,
password,
};
}
export function requestLogin(creds) {
return {
type: LOGIN_REQUEST,
isFetching: true,
isAuthenticated: false,
creds,
};
}
export function receiveLogin(user_token) {
return {
type: LOGIN_SUCCESS,
isFetching: false,
isAuthenticated: true,
token_id : user_token,
};
}
export function loginError(message) {
return {
type: LOGIN_FAILURE,
isFetching: false,
isAuthenticated: false,
message
}
}
See <Redirect/> for more info.
// Render.
render() {
// Is Authenticated.
if (this.props.isAuthenticated) return <Redirect to="/authpath"/>
// Is Not Authenticated.
// ..
}