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));
};
Related
So im making an app with authentication flow and followed the docs
now even though there was no user in the state the navigation didn't move to login screen so i changed the condition for rendering and now the i'm on Login page except there's no output and following error
Got a component with the name 's' for the screen 'Settings'.Got a component with the name 'l' for the screen 'Login'.Got a component with the name 'p' for the screen 'Home'. React Components must start with an uppercase letter. If you're passing a regular function and not a component, pass it as children to 'Screen' instead. Otherwise capitalize your component's name.
App.js
import React, {createContext, useContext, useEffect, useState} from 'react';
import {NativeBaseProvider, Box} from 'native-base';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {createNativeStackNavigator} from '#react-navigation/native-stack';
import {theme} from './theme';
import {NavigationContainer} from '#react-navigation/native';
import {
signInWithEmailPassword,
signOutFunc,
signUpWithEmailPassword,
signInWithGoogle,
} from './components/auth/helper';
import Login from './components/auth/Login';
import Settings from './components/core/Settings';
import Home from './components/core/Home';
import {createMaterialBottomTabNavigator} from '#react-navigation/material-bottom-tabs';
const Tab = createMaterialBottomTabNavigator();
const Stack = createNativeStackNavigator();
export const AuthContext = createContext();
export default function App({navigation}) {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_USER':
return {...prevState, user: action.user, isLoading: false};
case 'SIGN_IN':
return {...prevState, isSignout: false, user: action.user};
case 'SIGN_OUT':
return {...prevState, isSignout: true, user: null};
}
},
{
isLoading: true,
isSignout: false,
user: null,
},
);
const authContext = React.useMemo(
() => ({
signIn: async (data, type) => {
let user;
if (type === 'email') {
user = await signInWithEmailPassword(data.email, data.password);
} else {
user = await signInWithGoogle();
}
if (user) {
try {
await AsyncStorage.setItem('user', JSON.stringify(user));
dispatch({type: 'SIGN_IN', user: user});
} catch (e) {
console.log(e);
}
}
},
signOut: async type => {
try {
signOutFunc('google');
} catch (e) {
console.log(e);
}
try {
signOutFunc('email');
} catch (e) {
console.log(e);
}
dispatch({type: 'SIGN_OUT'});
},
signUp: async (data, type) => {
let user;
if (type === 'email') {
user = await signUpWithEmailPassword(data.email, data.password);
} else {
user = await signInWithGoogle();
}
if (user) {
try {
const user = await AsyncStorage.setItem(
'user',
JSON.stringify(data),
);
dispatch({type: 'SIGN_IN', user: user});
} catch (e) {
console.log(e);
}
}
},
}),
[],
);
useEffect(() => {
const bootstrapAsync = async () => {
let user;
try {
user = await AsyncStorage.getItem('user');
console.log(user);
} catch (e) {
console.log(e);
}
dispatch({type: 'RESTORE_USER', user: user});
};
bootstrapAsync();
}, [state.user]);
return (
<NativeBaseProvider theme={theme}>
<AuthContext.Provider value={authContext}>
<NavigationContainer
screenOptions={{
headerShown: false,
}}>
{state.user !== null ? (
<Tab.Navigator>
<Tab.Screen name="Login" component={Login} />
</Tab.Navigator>
) : (
<Tab.Navigator screenOptions={{headerShown: false}}>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Settings" component={Settings} />
</Tab.Navigator>
)}
</NavigationContainer>
</AuthContext.Provider>
</NativeBaseProvider>
);
}
Login.js
import {View, Text} from 'react-native';
import React from 'react';
import EmailSignUp from './EmailSingUp';
import GoogleSignin from './GoogleSignIn';
const Login = () => {
return (
<View>
<GoogleSignin />
<EmailSignUp />
</View>
);
};
export default Login;
Setting.js
import {View, Text} from 'react-native';
import React from 'react';
import {AuthContext} from '../../App';
import {Button} from 'react-native-paper';
const Settings = () => {
const {signOut} = React.useContext(AuthContext);
return (
<View>
<Text>Settings</Text>
<Button onPress={() => signOut()}>Sign Out </Button>
</View>
);
};
export default Settings;
I had the same issue on Android. I realised that my "JS Minify" setting was checked which loads the JavaScript bundle with minify=true.
It might be the same issue in your case.
In order to disable this settings, open the Developer menu, select "Settings" and then unselect "JS Minify" option.
Close your app/uninstall it first to ensure that you no longer have this issue.
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;
So, I (finally) can fetching data from jsonplaceholder using redux only. The problem is, my login won't let me logged in, saying "user not found". Before using redux, it works well. Please take a minute to see my code (and help me):
body.js
import React, {Component} from "react";
import "./style.css";
import { Switch, Route } from "react-router-dom"
import {About, Login, Register} from "../../pages"
import { connect } from "react-redux";
class Body extends Component {
constructor(props) {
super(props);
this.state = {
userData: [],
admin: [{
name: "admin",
role: "adm",
email: "adm#adm.com",
username: "admin",
password: "123"
}]
}
}
componentDidMount = () => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => {
const dataUser = data.map(user =>({
...user,
password: "pass",
role: "user",
}));
this.props.doFetch({
userData: [...this.state.admin, ...dataUser]
})
})
}
showPage = () => {
return (
<Switch>
<Route path="/about" children={(props) => <About {...props} listUsers={this.state.userData} />} />
<Route path="/login">
<Login />
</Route>
<Route path="/register">
<Register listUsers={this.state.userData} tambahUser={this.addUsers} />
</Route>
</Switch>
)
}
render() {
return (
<>
{
this.showPage()
}
</>
);
}
}
const mapStateToProps = (state) => ({
userList: state.data.userData
})
const mapDispatchToProps = (dispatch) => ({
doFetch: (userData) => dispatch({ type: "FETCH", payload: userData })
})
export default connect(mapStateToProps, mapDispatchToProps)(Body);
login.js
import React, { Component } from 'react';
import { RowInput, Button } from '../../components';
import { connect } from "react-redux";
import { Redirect } from "react-router-dom"
class Login extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
}
}
onChangeInput = e => {
this.setState({
[e.target.name]: e.target.value
})
}
onLogin = async () => {
const { username, password } = this.state
const exist = this.props.userList.find(user => user.username === username && user.password === password)
if (exist) {
alert(`Welcome ${username}`)
this.props.doLogin(username, password)
} else alert("User not found.")
}
render() {
if (this.props.statusLogin){
return <Redirect to="/about" />
}
return (
<div className="login">
<form className="login-form" method="POST">
<div className="container-log">
<h1 className="judul">Login</h1>
<RowInput value={this.state.username} label="Username" placeholder="Username" type="text" name="username" onChange={this.onChangeInput}/>
<RowInput value={this.state.password} label="Password" placeholder="Password" type="password" name="password" onChange={this.onChangeInput}/>
<Button onClickInput={this.onLogin}>Masuk</Button>
</div>
</form>
</div>
);
}
}
const mapStateToProps = (state) => ({
statusLogin: state.auth.isLoggedIn,
usernameLogin: state.auth.username,
userList: state.data.userData
})
const mapDispatchToProps = (dispatch) => ({
doLogin: (users) => dispatch({ type: "LOGIN", payload: {username: users} })
})
export default connect(mapStateToProps, mapDispatchToProps)(Login)
getdata.js
const initialState = {
userData: []
}
const dataReducer = (state = initialState, action) => {
console.log("state: ", state);
console.log("action: ", action);
switch (action.type) {
case "FETCH":
return {
userData: [action.payload.userData]
}
default:
return state
}
}
export default dataReducer
Console log:
The array has been stored................right?
I'm seriously have no idea why my login not work? Is there something on my code that make it not work? Thank you before!
So, after three hours searching and trying, I only need to comment/delete function mapStateToProps in body.js. I assume it's because I'm calling and store data at the same time? Idk for sure either, so, if anyone know the exactly why, feel free to correct me. Thankies. Have a good day, everyone!
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;
}
};
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)