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)
Related
I'm pretty new to Redux, but am trying to add it to my existing project and am getting an infinite refresh loop back to my /login page. I don't see an immediate error and can't locate where the issue might be coming from. I think the loop might be coming from the render in App.js that pulls in the Login component, but can't seem to pinpoint it. I'd really appreciate any help with this one!
App.js:
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import MuiThemeProvider from '#material-ui/core/styles/MuiThemeProvider';
import createMuiTheme from '#material-ui/core/styles/createMuiTheme';
import themeFile from './util/theme';
import jwtDecode from 'jwt-decode';
// Redux
import { Provider } from 'react-redux';
import store from './redux/store';
// Components
import Navbar from './components/Navbar';
import AuthRoute from './util/AuthRoute';
// Pages
import home from './pages/home';
import login from './pages/login';
import signup from './pages/signup';
const theme = createMuiTheme(themeFile);
let authenticated;
const token = localStorage.FBIdToken;
if(token){
const decodedToken = jwtDecode(token);
if(decodedToken.exp * 1000 < Date.now()){
window.location.href='/login'
authenticated = false;
} else {
authenticated = true;
};
}
class App extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<Router>
<Navbar/>
<div className="container">
<Switch>
<Route exact path="/" component={home}/>
<AuthRoute exact path="/login" component={login} authenticated={authenticated}/>
<AuthRoute exact path="/signup" component={signup} authenticated={authenticated}/>
</Switch>
</div>
</Router>
</Provider>
</MuiThemeProvider>
);
}
}
export default App;
Login page:
import React, { Component } from 'react'
import withStyles from '#material-ui/core/styles/withStyles'
import PropTypes from 'prop-types'
import AppIcon from '../images/micrologo.png'
import { Link } from 'react-router-dom'
//MUI Stuff
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography'
import TextField from '#material-ui/core/TextField'
import Button from '#material-ui/core/Button'
import CircularProgress from '#material-ui/core/CircularProgress'
//Redux stuff
import { connect } from 'react-redux';
import { loginUser } from '../redux/actions/userActions';
const styles = (theme) => ({
...theme.spreadThis
})
class login extends Component {
constructor(){
super();
this.state = {
email: '',
password: '',
errors: {}
}
}
componentWillReceiveProps(nextProps){
if(nextProps.UI.errors){
this.setState({ errors: nextProps.UI.errors })
}
}
handleSubmit = (event) => {
event.preventDefault();
const userData = {
email: this.state.email,
password: this.state.password
};
this.props.loginUser(userData, this.props.history)
}
handleChange = (event) => {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
const { classes, UI: { loading } } = this.props;
const { errors } = this.state;
return (
<Grid container className={classes.form}>
<Grid item sm/>
<Grid item sm>
<img src={AppIcon} alt="micrologo" className={classes.image}/>
<Typography variant="h3" className={classes.pageTitle}>
Login
</Typography>
<form noValidate onSubmit={this.handleSubmit}>
<TextField
id="email"
name="email"
type="email"
label="Email"
className={classes.textField}
helperText={errors.email}
error={errors.email ? true : false}
value={this.state.email}
onChange={this.handleChange}
fullWidth
/>
<TextField
id="password"
name="password"
type="password"
label="Password"
className={classes.textField}
helperText={errors.password}
error={errors.password ? true : false}
value={this.state.password}
onChange={this.handleChange}
fullWidth
/>
{errors.general && (
<Typography variant="body2" className={classes.customError}>
{errors.general}
</Typography>
)}
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
disabled={loading}
>
Login
{loading && (
<CircularProgress size={30} className={classes.progress}/>
)}
</Button>
<br />
<small>Don't have an account? Sign up <Link to="/signup">here</Link>
</small>
</form>
</Grid>
<Grid item sm/>
</Grid>
)
}
}
login.propTypes = {
classes: PropTypes.object.isRequired,
loginUser: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
UI: PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
user: state.user,
UI: state.UI
});
const mapActionsToProps = {
loginUser
}
export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(login));
User Details:
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI } from '../type';
import axios from 'axios'
export const loginUser = (userData, history) => (dispatch) => {
dispatch({ type: LOADING_UI});
axios
.post('/login', userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem('FBIdToken', FBIdToken);
axios.defaults.headers.common['Authorization'] = FBIdToken;
dispatch(getUserData());
dispatch({ type: CLEAR_ERRORS });
history.push('/');
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data
})
})
}
export const getUserData = () => (dispatch) => {
axios.get('/user')
.then((res) => {
dispatch({
type: SET_USER,
payload: res.data
})
})
.catch((err) =>
console.log(err)
)
}
EDIT: After making changes to my User Actions file that references my User Reducer, could it possibly be something to do with the User Reducer code?
import { SET_USER, SET_ERRORS, CLEAR_ERRORS, LOADING_UI, SET_AUTHENTICATED, SET_UNAUTHENTICATED } from '../type';
const initialState = {
authenticated: false,
credentials: {},
likes: [],
notifications: []
};
export default function(state = initialState, action){
switch(action.type){
case SET_AUTHENTICATED:
return {
...state,
authenticated: true
};
case SET_UNAUTHENTICATED:
return initialState
case SET_USER:
return {
authenticated: true,
...action.payload
};
default:
return state;
}
}
EDIT 2: Adding copy > copy fetch code from network tab:
fetch("http://localhost:3000/login", {
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-US,en;q=0.9",
"if-none-match": "W/\"717-3FVndTj2FHm3TgZjXTrLARSY62Q\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "same-origin",
"upgrade-insecure-requests": "1"
},
"referrer": "http://localhost:3000/login",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "omit"
});
You can try the following:
export const loginUser = (userData, history) => (
dispatch
) => {
dispatch({ type: LOADING_UI });
axios
.post("/login", userData)
.then((res) => {
const FBIdToken = `Bearer ${res.data.token}`;
localStorage.setItem("FBIdToken", FBIdToken);
axios.defaults.headers.common[
"Authorization"
] = FBIdToken;
//wait for getUserData to finish
return getUserData()(dispatch);
})
.then(() => {
//getUserData is finished
dispatch({ type: CLEAR_ERRORS });
history.push("/");
})
.catch((err) => {
dispatch({
type: SET_ERRORS,
payload: err.response.data,
});
});
};
export const getUserData = () => (dispatch) => {
//return a promise so loginUser action can wait for
// it to finish
return axios.get("/user").then((res) => {
dispatch({
type: SET_USER,
payload: res.data,
});
});
//if you catch it then the catch that set errors is
// pointless
// .catch((err) => console.log(err));
};
I did all the instructions for a redux tutorial correctly. But after running the program, when I try to enter a value in the email or password TextInput, the value is not placed in it and the email input is emptied immediately. I do not know where I did the wrong thing. But I think the action is not working properly. I hope you will guide me.
In Action directory > index.js
import { EMAIL_CHANGED, PASS_CHANGED, USER_LOGIN_ATEMT, USER_LOGIN_SUCCESS, USER_LOGIN_FAILED } from "./types";
export const emailChanged = (text) => {
return {
type: EMAIL_CHANGED,
payload: text
}
}
export const passChanged = (text) => {
return {
type: PASS_CHANGED,
payload: text
}
}
export const loginUser = ({ email, pass }) => {
return (dispatch) => {
dispatch({ type: USER_LOGIN_ATEMT });
fetch('', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
password: pass
})
}).then((response) => response.json()).
then((responseJson) => {
if (responseJson === 'Data Matched') {
loginUserSuccess(dispatch);
} else {
loginUserFailed(dispatch);
}
}).catch((error) => { alert(error) });
}
}
const loginUserSuccess = (dispatch) => {
dispatch({ type: USER_LOGIN_SUCCESS });
}
const loginUserFailed = (dispatch) => {
dispatch({ type: USER_LOGIN_FAILED });
}
In Action directory > types.js
export const EMAIL_CHANGED = 'EMAIL_CHANGED';
export const PASS_CHANGED = 'PASS_CHANGED';
export const USER_LOGIN_ATEMT = 'USER_LOGIN_ATEMT';
export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS';
export const USER_LOGIN_FAILED = 'USER_LOGIN_FAILED';
In components directory > LoginForm.js
import React, { Component } from 'react';
import { View, TextInput, Text, FlatList, ActivityIndicator, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { emailChanged, passChanged, loginUser } from '../actions/index';
class LoginForm extends Component {
onEmailChange(text) {
this.props.emailChanged(text)
}
onPassChange(text) {
this.props.passChanged(text)
}
onLoginUser() {
const { email, pass } = this.props;
this.loginUser({ email, pass });
}
renderButton() {
if (this.props.loading) {
return (<ActivityIndicator size="large" color="#0000ff" />);
} else {
return (
<TouchableOpacity onPress={() => this.onLoginUser.bind(this)} >
<Text>Continue...</Text>
</TouchableOpacity>
)
}
}
render() {
return (
<View>
<TextInput
placeholder="Email"
onChangeText={() => this.onEmailChange.bind(this)}
value={this.props.email}
/>
<TextInput
placeholder="Pass"
onChangeText={() => this.onPassChange.bind(this)}
secureTextEntry={true}
value={this.props.pass}
/>
<Text>{this.props.error}</Text>
{this.renderButton()}
</View>
);
}
}
const mapStateToProps = state => {
return {
email: state.auth.email,
pass: state.auth.pass,
loading: state.auth.loading,
error: state.auth.error
}
}
export default connect(mapStateToProps, { emailChanged, passChanged, loginUser })(LoginForm);
In reducers directory > AuthReducer.js
import { EMAIL_CHANGED, PASS_CHANGED, USER_LOGIN_ATEMT, USER_LOGIN_SUCCESS, USER_LOGIN_FAILED } from '../actions/types';
const initialState = {
email: '',
pass: '',
loading: false,
error: ''
}
const AuthReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case EMAIL_CHANGED:
return { ...state,
email: state.email.concat({
key: Math.random(),
value: action.payload
})
}
case PASS_CHANGED:
return { ...state, pass: action.payload }
case USER_LOGIN_ATEMT:
return { ...state, loading: true }
case USER_LOGIN_SUCCESS:
return { ...state, ...initialState }
case USER_LOGIN_FAILED:
return { ...state, loading: false, pass: '', error: 'اشتباه وارد شده است' }
default:
return state;
}
}
export default AuthReducer;
In reducers directory > index.js
import { combineReducers } from 'redux';
import AuthReducer from '../reducers/AuthReducer';
const reducers = combineReducers({
auth: AuthReducer
});
export default reducers;
In App.js
import React, { Component } from 'react';
import { View, TextInput, Text, FlatList, Pressable } from 'react-native';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import reducers from './reducers/index';
import LoginForm from './components/LoginForm';
import ReduxThunk from 'redux-thunk';
export default class App extends Component {
render() {
return (
<Provider store={createStore(reducers, {}, applyMiddleware(ReduxThunk))}>
<LoginForm />
</Provider>
);
}
}
In index.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
My package.json
"react": "16.13.1",
"react-native": "0.63.4",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0"
Issue
email is initially an empty string, when you dispatch EMAIL_CHANGED you attempt to concatenate an object with keys key and value to it. Your component is accessing only state.auth.email. I'm absolutely sure there should be an error being thrown somewhere. The onChangeText value from the text input is the entire value, not the delta, so you shouldn't concatenate or append anything, just fully replace the existing email state (You got this part right for the password, actually).
There is also an issue with how you are attaching your handler.
onChangeText={() => this.onEmailChange.bind(this)}
This drops the onChangeText value and passes undefined to your handler.
Solution
Update the reducer case to correctly update the email value.
case EMAIL_CHANGED:
return {
...state,
email: action.payload,
}
Update the handler to consume the changed value.
onChangeText={this.onEmailChange.bind(this)}
Hint: to save the binding of this you can either do that in a constructor or use an arrow function.
onEmailChange = (text) => {
this.props.emailChanged(text)
}
...
onChangeText={this.onEmailChange}
Second hint: You can directly attach the passed handler to the input and save a function declaration and this binding.
onChangeText={this.props.onEmailChange}
If for some reason you need the random key value (random values are poor keys/ids as they aren't guaranteed to be unique) then update as follows:
Provide valid initial state
const initialState = {
email: {}, // <-- empty object, provides object to destructure from in UI
...
}
Fully replace the email object
case EMAIL_CHANGED:
return { ...state,
email: {
key: Math.random(),
value: action.payload
}
}
Access the email value correctly in the component
const mapStateToProps = state => {
return {
email: state.auth.email.value, // <-- get the email value
...
}
}
Two components are rendered with conditional rendering with a redux state but after changing redux state from one component warning appears that can't perform state update on unmounted component. In LoginWrapper.js Login and Redirect are two components with conditional rendering using isLoggedIn state of redux.
LoginWrapper.js
import React from 'react';
import Login from 'containers/Login';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { PropTypes } from 'prop-types';
const LoginWrapper = ({ isLoggedIn }) => {
return (
<div>
{
!isLoggedIn
?
<Login />
:
<Redirect to="/profile" />
}
</div>
)
}
LoginWrapper.defaultProps = {
isLoggedIn: false
}
LoginWrapper.propTypes = {
isLoggedIn: PropTypes.bool
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.auth.isLoggedIn
}
}
export default connect(mapStateToProps)(LoginWrapper);
Login.js
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter, Link } from 'react-router-dom';
import { withFormik, Form, Field } from 'formik';
import { connect } from 'react-redux';
import { logInUser } from 'actions/auth';
import { logInUrl } from "apis";
import ModalLayout from "shared/ModalLayout";
import * as Yup from 'yup';
const loginPost = (history, values, setSubmitting, setErrors, resetForm, logIn) => {
const { username, password } = values;
window.fetch(logInUrl, {
method: 'POST',
credentials: "same-origin",
headers: {
'Content-Type': "application/json"
},
body: JSON.stringify({
"username": username,
"password": password
})
})
.then((results) => {
return results.json();
})
.then((data) => {
if(data.errors) {
setErrors({ 'username': data.errors[0].msg });
} else {
logIn(data.user, history);
resetForm();
}
setSubmitting(false);
})
.catch((err) => {
console.log(err);
})
}
const LogInForm = ({
touched,
errors,
isSubmitting,
}) => (
<ModalLayout>
<Form className="login-form">
{touched.username && errors.username && <p className="login-error">{errors.username}</p>}
<div className="login-username">
<Field type="input" placeholder="Username" name="username" />
</div>
{touched.password && errors.password && <p className="login-error">{errors.password}</p>}
<div className="login-password">
<Field type="password" placeholder="Password" name="password" />
</div>
<div className="login-button">
<button className="modal-button login-button" type="submit" disabled={isSubmitting}>
Log in
</button>
</div>
<div className="login-redirect">
<Link to="/signup">Don't have an account.Create one</Link>
</div>
</Form>
</ModalLayout>
);
LogInForm.propTypes = {
isSubmitting: PropTypes.bool.isRequired,
errors: PropTypes.object.isRequired,
touched: PropTypes.object.isRequired,
}
const FormikApp = withFormik({
mapPropsToValues() {
return {
username: '',
password: '',
}
},
handleSubmit(values, { resetForm, setErrors, setSubmitting, props }) {
const { logIn, history } = props;
loginPost(history, values, setSubmitting, setErrors, resetForm, logIn);
},
validationSchema: Yup.object().shape({
username: Yup.string().required('Username is required'),
password: Yup.string().required('Password is required'),
})
})(LogInForm);
export default withRouter(connect(null, { logIn: logInUser })(FormikApp));
actions
import {
LOG_IN,
LOG_OUT,
} from 'actions/types';
import { logInUrl } from 'apis';
export const logInUser = (user) => {
return (dispatch) => {
dispatch({
type: LOG_IN,
payload: user
})
}
}
export const logOutUser = () => {
return {
type: LOG_OUT
}
}
reducers
import { LOG_IN, LOG_OUT } from 'actions/types';
const INITIAL_STATE = {
isloggedIn: null,
user: null,
uid: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case LOG_IN:
return { ...state, isLoggedIn: true, user: action.payload, uid: action.payload.id }
case LOG_OUT:
return { ...state, isLoggedIn: false, user: null, uid: null };
default:
return state;
}
};
My Login Component:
import React, { Component } from 'react'
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity
} from 'react-native'
export class Login extends Component {
onChangeText = (key, value) => {
this.props.setUserDetails({
...this.props.user,
[key]: value
})
}
render() {
const { user, onSubmitForm } = this.props
console.log(this.props.user) // user undefined here
return (
<View style={styles.container}>
<Text style={styles.heading}>Login</Text>
<TextInput
placeholder='Email'
onChangeText={val => this.onChangeText('email', val)}
style={styles.input}
value={user.email}
/>
<TextInput
placeholder='Password'
onChangeText={val => this.onChangeText('password', val)}
style={styles.input}
value={user.password}
/>
<TouchableOpacity onPress={() => onSubmitForm(user)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Submit</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
My Login container:
import React, { Component } from 'react'
import { setUserDetails } from '../actions/loginActions'
import { connect } from 'react-redux'
import loginReducer from '../reducers/loginReducer'
import { Login } from '../components/login'
export class LoginContainer extends Component {
onSubmitForm = () => {
// Checking Validations
const { name, email } = this.props;
if (!name || !email) {
alert('Please fill the form')
console.log(this.props.user) // It says undefined
return;
}
}
render () {
return (
<Login
user={this.props.user}
setUserDetails={this.props.setUserDetails}
onSubmitForm={this.onSubmitForm}
/>
)
}
}
const mapStateToProps = (state) => ({
user: state.loginReducer.user,
})
const mapDispatchToProps = dispatch => ({
setUserDetails: payload => dispatch(setUserDetails(payload)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Login)
My login Reducer:
const initialState = {
user: {
email: '',
password: '',
}
}
const loginReducer = (state = initialState, action) => {
switch(action.type) {
case SET_USER_DETAILS:
return Object.assign({}, state, {
user: action.user
})
default:
return state
}
return state
}
export default loginReducer
My root Reducer:
import { combineReducers } from 'redux';
import loginReducer from './loginReducer'
const rootReducer = combineReducers({
loginReducer
})
export default rootReducer
MY store configuration:
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import rootReducer from './reducers'
const persistConfig = {
key: 'mykey',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer)
const persistedStore = persistStore(store)
export default store
I am learning React native and trying to implement some features.
The problem is that I just can't access my this.props.user in the Login container when the submit is called. What am I missing in this scenario?
Any help is appreciated.
I've noticed some weird thing. Your default export of LoginContainer.js is connected Login component. I guess what you really meant is instead of this:
// ...imports
export class LoginContainer extends Component {
// ...
}
//...
export default connect(mapStateToProps, mapDispatchToProps)(Login)
to use this:
// ...imports
// no need to 'export class ...' here.
class LoginContainer extends Component {
// ...
}
// ...
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
In the container you used:
const mapStateToProps = (state) => ({
user: state.loginReducer.user,
})
But in your initial state there is not loginReducer. Maibe it works:
const mapStateToProps = (state) => ({
user: state.user
})
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.
// ..
}