Consider the code below:
Ignore all the axios request:
Login.js
const login = async () => {
let newLogin = {
email,
password,
};
await axios
.post("http://localhost:5000/login", newLogin)
.then((response) => {
if (response.data === 404) {
setUserError("User not found. Please Sign Up");
} else if (response.data === 403) {
setPasswordError("Incorrect Password");
} else {
dispatch({type:'LOG_IN',payload:{LoggedIn:true , currentUser:"Curent user"}})
navigate("/");
}
})
.catch((err) => console.log(err));
};
This pages updates the GlobalContext by changing loggedIn to true and currentUser to "fa".
Profile.js
import React , {useContext} from 'react'
import '../App.css'
import {Context} from '../GlobalState/Store'
const Profile = () => {
const [state,dispatch] = useContext(Context);
console.log(state);
return (
<div className="profile-page">
<div className="personal">
<h2>First Name:</h2>
<h2>Last Name:</h2>
<h2>Resistered Email:</h2>
<h2>User Name:</h2>
</div>
<div className="info">
<h2>Father Name:</h2>
<h2>Gender:</h2>
<h2>CNIC:</h2>
<h2>Blood Group:</h2>
<h2>Contact:</h2>
</div>
<div className="uni-info">
<h2>Designation:</h2>
<h2>Department:</h2>
<h2>Batch:</h2>
<h2>Roll No:</h2>
<h2>Enrolement:</h2>
</div>
</div>
);
}
export default Profile
This gets the state and logs it.
Here are the Reducer.js and GlobalContext.js:
const Reducer = (state,action) =>{
switch(action.type){
case 'LOG_IN':
return {
userEmail: action.payload.currentUser,
loggedIn: action.payload.LoggedIn
}
case 'LOG_OUT':
return {
userEmail: action.payload.currentUser,
loggedIn: action.payload.LoggedIn
}
default:
return state;
}
}
export default Reducer;
import React,{useReducer , createContext} from 'react'
import Reducer from './Reducer';
const initialState ={
loggedIn: false,
currentUser:''
}
const Store = ({children}) => {
const [state , dispatch] = useReducer(Reducer,initialState)
return (
<Context.Provider value={[state , dispatch]} >
{children}
</Context.Provider>
)
}
export const Context = createContext(initialState)
export default Store
Everything works beautifully and once I log in, I'm redirected to homepage as I'm supposed to and the correct state is being logged.
But once I refresh on the home page everything is reverted back to the initial state.
I need to retain the state as it will be used to apply the 'IfLoggedIn' logic.
Thanks in advance.
Cookie time (local storage)
Your login request will give your site a cookie that should be stored. when loading the page before anything check if the cookie/session is valid and set the state accoridingly.
Your react state cannot be staved (directly) between hard refreshes
Related
I'm coding a web react app with sign in. In my server side I'm using express, jwt and sending a httpOnly cookie with the token when succesfully log in. When user logs in, I'm trying to keep state in the client (e.x, loggedIn = true) inside of a context, but every time that context is rendered it comes back to default state (undefined). How could i keep that state in memory?
My user route, that works as expected (backend):
users.post('/login',async (req, res) => {
try {
const {userName,userPass} = req.body
const u = await models.User.findOne({
userid: userName
})
if (!u) res.status(404).end()
if (bcrypt.compare(userPass,u.password)) {
// JWT TOKEN
const t = c_auth(u._id)
res.status(200).cookie("session",t,{
httpOnly:true
}).end()
} else {
res.status(404).end()
}
} catch (e) {
console.log({'ERROR':e})
res.status(500).end()
}
})
My user provider that returns true when request is ok (client):
get: async (user,pass) => {
try {
const req = await axios.post('/users/login',{
userName: user,
userPass: pass
})
if (req.status === 200) {
return true
} else {
return false
}
} catch (e) {
console.log({'ERROR':e})
return false
}
}
Login submit function (client):
import {useAuth} from '../../../../contexts/AuthContext.js'
const {setLoggedIn} = useAuth()
const handleLogin = async (e) => {
if (await users.get(data.userName,data.userPass)) {
setLoggedIn(true)
// ^---> Trying to set loggedIn state to true in context
window.location.replace('/')
} else {
alert(`Incorrect.`)
}
}
Auth context:
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider ({children}) {
const [loggedIn,setLoggedIn] = useState()
console.log(loggedIn)
// ^---> Getting true after login,
// undefined (default useState) after re-render
const value = {
loggedIn,
setLoggedIn
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
App.js:
import {AuthProvider} from './contexts/AuthContext'
function App() {
return (
<AuthProvider>
<div className="App">
<Navigation/>
<main>
<Router>
<Routes>
...Routes
</Routes>
</Router>
</main>
</div>
</AuthProvider>
)
}
I suppose that react memo could be the solution, but I don't understand quite well how it works. Also, is it correct not to use the setLoggedIn in the AuthContext itself? I tried to call the login or sign up function (second piece of code) from the AuthContext, but can't set state since its unmounted. Would need to do that inside a useEffect and that's not what I'm looking for since I wouldn`t be able to export that function. All help is appreciated.
EDIT: fixed
The problem solved after changing window.location.replace to the useNavigate hook from react-router-dom, causing a refresh:
import {useAuth} from '../../../../contexts/AuthContext.js'
import {useNavigate} from 'react-router-dom'
const {setLoggedIn} = useAuth()
const navigate = useNavigate()
const handleLogin = async (e) => {
if (await users.get(data.userName,data.userPass)) {
setLoggedIn(true)
navigate('/')
} else {
alert(`Incorrect.`)
}
}
Also in my navbar I was using <a href> tags instead of <Link to> from 'react-router-dom'. That fixes the problem when I go to a different page from the navbar, so it doesn't 'refresh'.
I am working with React and Jwt Tokens and so far a user can stay logged in after going to a login page. I have a problem when I reload the page that the user won't stay logged in. Currently when a user logs in, I store the jwt Token in localStorage. For some reason, I don't understand why I'm not returning a valid user even when I have the jwt Token in localStorage. Any help is appreciated!
import React, { useReducer, createContext } from 'react'
import jwtDecode from "jwt-decode";
const initialState = {
user: null
}
if(localStorage.getItem('jwtToken')) {
const decodedToken = jwtDecode(localStorage.getItem('jwtToken'))
if(decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('jwtToken')
}
else {
initialState.user = null;
}
}
const AuthContext = createContext({
user:null,
login: (userData) => {},
logout: () => {}
})
function authReducer(state, action) {
switch(action.type) {
case 'LOGIN':
return {
...state,
user: action.payload
}
case 'LOGOUT':
return {
...state,
user:null
}
default: return state
}
}
function AuthProvider(props) {
const [state, dispatch] = useReducer(authReducer, initialState)
function login(userData) {
localStorage.setItem("jwtToken", userData.token)
dispatch({
type:'LOGIN',
payload:userData
})
}
function logout() {
localStorage.removeItem("jwtToken")
dispatch({type:'LOGOUT'})
}
return (<AuthContext.Provider
value={{ user: state.user, login, logout}}
{...props} /> )
}
export { AuthContext, AuthProvider }
^^^ My Authentication file
^^ My menu bar component renders dynamically based on if there is a user or not. It's not rendering a logged-in user after reloading so my authentication file must be doing something wrong.
Thanks! Any help is appreciated.
I'm using the context api in a Gatsby setup to keep track of a state called userIsLoggedIn. I'm using Firebase for authentication.
This is my context file:
import { createContext } from "react"
export const AppContext = createContext(null)
This is my AppWrapper component:
import React, { useState, useEffect } from "react"
import firebase from "../../config/firebase"
import { AppContext } from "../../context/AppContext"
const AppWrapper = ({ children }: any) => {
const [userIsLoggedIn, setUserIsLoggedIn] = useState(false)
const authListener = () => {
firebase.auth().onAuthStateChanged(user => {
if (user && user.emailVerified) {
setUserIsLoggedIn(true)
} else {
setUserIsLoggedIn(false)
}
})
}
useEffect(() => {
authListener()
}, [])
return (
<>
<AppContext.Provider
value={{
userIsLoggedIn,
}}
>
<main>{children}</main>
</AppContext.Provider>
</>
)
}
export default AppWrapper
This is my index page where I want to keep track if the user is logged in so I can show/hide certain content:
import React, { useContext } from "react"
import { AppContext } from "../context/AppContext"
const IndexPage = () => {
const app = useContext(AppContext)
console.log("app", app)
return (
<>
{app && app.userIsLoggedIn && (
<>
<h1>Hello dearest user</h1>
<p>Welcome to your page.</p>
</>
)}
</>
)
}
export default IndexPage
The outcome of my console.log inside the my IndexPage component is the following when I first load the page or whenever the page is reloaded:
app {userIsLoggedIn: false}
app {userIsLoggedIn: true}
This means my page is re-rendering and my content is flickering between content which is hidden/shown when a user is logged in. Is there a way to avoid this and make the state more instant? I'm open for any suggestions :)
Okay so I found out what helps my specific case. Instead of using the context api to keep an app state (which will be reset to it's default value when reloaded, hence the flickering between states) I use localStorage to save if a user is logged in in combination with my authListener function.
This is the auth util I added:
// Firebase
import firebase from "../config/firebase"
export const isBrowser = () => typeof window !== "undefined"
export const getUser = () => {
if (isBrowser()) {
const user = window.localStorage.getItem("user")
if (user !== null) {
return JSON.parse(user)
} else {
return ""
}
}
}
export const setUser = (email: string | null) =>
isBrowser() && window.localStorage.setItem("user", JSON.stringify(email))
export const isLoggedIn = () => {
const user = getUser()
return !!user
}
export const logout = () => {
return new Promise(resolve => {
firebase
.auth()
.signOut()
.then(() => {
setUser("")
resolve()
})
})
}
and inside my AppWrapper my authListener function now looks like this:
import { setUser, logout } from "../../utils/auth"
const authListener = () => {
firebase.auth().onAuthStateChanged(user => {
if (user && user.emailVerified) {
setUser(user.email)
} else {
logout()
}
})
}
useEffect(() => {
authListener()
})
Anywhere in my app I can use the isLoggedIn() util to check if the user is actually logged in without having the flickering content.
If I manually delete or alter the localStorage user this will instantly be refreshed by the authListener function when anything changes in the app.
When trying to check if a user is signed in via firebase.auth().currentUser like this:
if (firebase.auth().currentUser === null) {
console.log('User not signed in');
}
Whenever I refresh the page, or navigate around the above returns null (even though I have just logged in).
The weird thing is, that if I log
console.log(firebase.auth().currentUser) // This returns null
console.log(firebase.auth()) // Here I can inspect the object and currentUser exists...!
I don't really know what's going on here. I'm using React and Redux, but it shouldn't really matter I'd say.
Is there a small delay where the firebase initialises and you can't access the currentUser? If so, how can I see it in the log output of firebase.auth()?
This is a commonly asked question.
https://firebase.google.com/docs/auth/web/manage-users
You need to add an observer to onAuthStateChanged to detect the initial state and all subsequent state changes,
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
A simple way is to add a pending state.
Here is a react example using hooks:
// useAuth.ts
import { useState, useEffect } from 'react'
import { auth } from 'firebase'
export function useAuth() {
const [authState, setAuthState] = useState({
isSignedIn: false,
pending: true,
user: null,
})
useEffect(() => {
const unregisterAuthObserver = auth().onAuthStateChanged(user =>
setAuthState({ user, pending: false, isSignedIn: !!user })
)
return () => unregisterAuthObserver()
}, [])
return { auth, ...authState }
}
// SignIn.tsx
import React from 'react'
import { StyledFirebaseAuth } from 'react-firebaseui'
import { useAuth } from '../hooks'
export default function SignIn() {
const { pending, isSignedIn, user, auth } = useAuth()
const uiConfig = {
signInFlow: 'popup',
signInOptions: [
auth.GoogleAuthProvider.PROVIDER_ID,
auth.FacebookAuthProvider.PROVIDER_ID,
],
}
if (pending) {
return <h1>waiting...</h1>
}
if (!isSignedIn) {
return (
<div>
<h1>My App</h1>
<p>Please sign-in:</p>
<StyledFirebaseAuth uiConfig={uiConfig} firebaseAuth={auth()} />
</div>
)
}
return (
<div>
<h1>My App</h1>
<p>Welcome {user.displayName}! You are now signed-in!</p>
<a onClick={() => auth().signOut()}>Sign-out</a>
</div>
)
}
The best way to always have access to currentUser is to use vuex and vuex-persistedstate
//Configure firebase
firebase.initializeApp(firebaseConfig);
//When ever the user authentication state changes write the user to vuex.
firebase.auth().onAuthStateChanged((user) =>{
if(user){
store.dispatch('setUser', user);
}else{
store.dispatch('setUser', null);
}
});
The only issue above is that if the user presses refresh on the browser the vuex state will be thrown away and you have to wait for onAuthStateChange to fire again, hence why you get null when you try to access currentUser.
The secret to the above code working all the time is to use vuex-persisted state.
In your store.js file
import Vue from 'vue'
import Vuex from 'vuex'
import firebase from 'firebase/app'
Vue.use(Vuex)
import createPersistedState from "vuex-persistedstate";
export default new Vuex.Store({
plugins: [createPersistedState()],
state: {
user: null
},
getters:{
getUser: state => {
return state.user;
}
},
mutations: {
setUser(state, user){
state.user = user;
}
},
actions: {
setUser(context, user){
context.commit('setUser', user);
},
signIn(){
let provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider).then(function (result) {
})
},
signOut(){
firebase.auth().signOut();
}
}
})
You can now protect routes in your router as in the code example below.
import Vue from 'vue'
import Router from 'vue-router'
import Home from '#/components/Home'
import Search from '#/components/Search/Search'
import CreateFishingSite from '#/components/FishingSites/CreateFishingSite'
Vue.use(Router);
import store from './store'
import firebase from 'firebase'
let router = new Router({
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/search/:type',
name: 'Search',
component: Search
},
{
path: '/fishingsite/create',
name: 'CreateFishingSite',
component: CreateFishingSite,
meta: {
requiresAuth: true
}
}
]
})
router.beforeEach(async (to, from, next)=>{
let currentUser = store.state.user;
console.log(currentUser);
let requriesAuth = to.matched.some(record => record.meta.requiresAuth);
if(requriesAuth && !currentUser){
await store.dispatch('signIn');
next('/')
}else{
next()
}
})
If you are looking for a copy and paste Auth route for react with firebase:
const AuthRoute = ({ component: Component, ...rest }) => {
const [authenticated, setAuthenticated] = useState(false)
const [loadingAuth, setLoadingAuth] = useState(true)
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
setAuthenticated(true)
} else {
setAuthenticated(false)
}
setLoadingAuth(false)
})
}, [])
return loadingAuth ? 'loading...' : (
<Route
{...rest}
render={props =>
authenticated ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/user/login' }} />
)}
/>
)
}
Promise-wise, there are three options:
UPDATE: 11/26/22
For Firebase 9+, you could do:
Note: (this.auth) is the Auth object and depends on your framework.
const user1 = await firstValueFrom(authState(this.afa));
const user2 = await firstValueFrom(
new Observable(observer => onAuthStateChanged(this.afa, observer))
);
const user3 = this.afa.currentUser;
// best option
const user1 = await new Promise((resolve: any, reject: any) =>
firebase.auth().onAuthStateChanged((user: any) =>
resolve(user), (e: any) => reject(e)));
console.log(user1);
// sometimes does not display correctly when logging out
const user2 = await firebase.auth().authState.pipe(first()).toPromise();
console.log(user2);
// technically has a 3rd state of 'unknown' before login state is checked
const user3 = await firebase.auth().currentUser;
console.log(user3);
// On component load.
componentDidMount = () => this.getAuthStatus();
// Get firebase auth status.
getAuthStatus = () => {
firebase.auth().onAuthStateChanged((resp) => {
// Pass response to a call back func to update state
this.updateUserState(resp);
});
}
// update state
updateUserState = (resp) => {
this.setState({
user: resp
})
}
// Now you can validate anywhere within the component status of a user
if (this.state.user) { /*logged in*/}
Best approach for this is to use a promise and only instantiate the router after the response, something along the lines of:
store.dispatch('userModule/checkAuth').then(() => {
// whatever code you use to first initialise your router, add it in here, for example
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
})
inside the checkAuth action is where you would have your promise, like so:
checkAuth ({ commit }) {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged(async (_user) => {
if (_user) {
commit('setUser', _user)
} else {
commit('setUser', null)
}
console.log('current user in checkAuth action:', _user)
resolve(true)
})
})
h/t to aaron k saunders - the source of this solution for me.
If you'd like the user to access to a certain page only if he is authenticated and to redirect to the home page if he is not, the following codes might help:
in React:
make a component with the following code:
import { onAuthStateChanged } from "#firebase/auth";
import { Route, Redirect } from "react-router-dom";
import { auth } from "../firebase/config";
import { useState, useEffect } from "react";
const GuardedRoute = ({ component, path }) => {
const [authenticated, setAuthenticated] = useState(false);
const [authCompleted, setAuthCompleted] = useState(false);
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setAuthenticated(true);
} else {
setAuthenticated(false);
}
setAuthCompleted(true);
});
}, []);
return authCompleted ? (
authenticated ? (
<Route path={path} component={component} />
) : (
<Redirect to="/" />
)
) : (
""
);
};
export default GuardedRoute;
and in app.js use:
import RouterPage from "./pages/RouterPage";
<GuardedRoute path="/router-page" component={RouterPage} />
in Vue:
at the router file use:
const guardSuccess = (to, from, next) => {
let gUser = auth.currentUser
if (gUser) {
next()
} else {
next({ name: "Home" })
}
}
and in the routes of the page you want to restrict access to add:
{
path: "/router-page",
name: "routerPage",
component: () => import("../views/routerPage.vue"),
beforeEnter: guardSuccess
}
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var user = firebase.auth().currentUser;
if(user != null){
var io=user.uid;
window.alert("success "+io);
}
} else {
// No user is signed in.
Window.reload();
}
});
first check if user exist then get it id by
firebase.auth().currentUser.uid
I'm creating my first React-Redux App. I'm using yo generator-redux and following this repo and official documenation. I have rendered de SignIn Presentational Component and it works fine, show errors if inputs are blanks. The problem is at dispatching. I use Thunk Middleware but the repo doesn't.
I have used console.log() to explore how deeper is working my code and I found that the Component Actions are being called, the AJAX request (with axios) is working fine, but the .then() (I think) is not working but doesn't throw errors.
This is my code:
Action
actions/UsersActions.js
import axios from 'axios';
//sign in user
export const SIGNIN_USER = 'SIGNIN_USER';
export const SIGNIN_USER_SUCCESS = 'SIGNIN_USER_SUCCESS';
export const SIGNIN_USER_FAILURE = 'SIGNIN_USER_FAILURE';
//Get current user(me) from token in localStorage
export const ME_FROM_TOKEN = 'ME_FROM_TOKEN';
export const ME_FROM_TOKEN_SUCCESS = 'ME_FROM_TOKEN_SUCCESS';
export const ME_FROM_TOKEN_FAILURE = 'ME_FROM_TOKEN_FAILURE';
export const RESET_TOKEN = 'RESET_TOKEN';
//log out user
export const LOGOUT_USER = 'LOGOUT_USER';
axios.defaults.baseURL = location.href.indexOf('10.1.1.33') > 0 ? 'http://10.1.1.33:8080/api/v1' : 'http://10.1.1.33:8080/api/v1';
export function signInUser(formValues) {
const request = axios.post('/login', formValues);
console.log(request);
// It works fine and receives the resposen when is invoked from Container
return {
type: SIGNIN_USER,
payload: request
};
}
export function signInUserSuccess(user) {
return {
type: SIGNIN_USER_SUCCESS,
payload: user
}
}
export function signInUserFailure(error) {
return {
type: SIGNIN_USER_FAILURE,
payload: error
}
}
export function meFromToken(tokenFromStorage) {
//check if the token is still valid, if so, get me from the server
const request = axios.get('/me/from/token?token=${tokenFromStorage}');
return {
type: ME_FROM_TOKEN,
payload: request
};
}
export function meFromTokenSuccess(currentUser) {
return {
type: ME_FROM_TOKEN_SUCCESS,
payload: currentUser
};
}
export function meFromTokenFailure(error) {
return {
type: ME_FROM_TOKEN_FAILURE,
payload: error
};
}
export function resetToken() {//used for logout
return {
type: RESET_TOKEN
};
}
export function logOutUser() {
return {
type: LOGOUT_USER
};
}
Component
components/SignInForm.js
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
class SignInForm extends Component {
static contextTypes = {
router: PropTypes.object
};
componentWillUnmount() {
// Invoked immediately before a component is unmounted from the DOM.
// Perform any necessary cleanup in this method, such as invalidating timers or
// cleaning up any DOM elements that were created in componentDidMount.
// Important! If your component is navigating based on some global state(from say componentWillReceiveProps)
// always reset that global state back to null when you REMOUNT
this.props.resetMe();
}
componentWillReceiveProps(nextProps) {
// Invoked when a component is receiving new props. This method is not called for the initial render.
if(nextProps.user && nextProps.user.status === 'authenticated' && nextProps.user.user && !nextProps.user.error) {
this.context.router.push('/');
}
//error
//Throw error if it was not already thrown (check this.props.user.error to see if alert was already shown)
//If u dont check this.props.user.error, u may throw error multiple times due to redux-form's validation errors
if(nextProps.user && nextProps.user.status === 'signin' && !nextProps.user.user && nextProps.user.error && !this.props.user.error) {
alert(nextProps.user.error.message);
}
}
render() {
const { asyncValidating, fields: { email, password }, handleSubmit, submitting, user } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.props.signInUser.bind(this))}>
<div>
<label>Email</label>
<input type="text" placeholder="email#4geeks.com.ve" {...email} />
<div>{email.touched ? email.error : ''}</div>
<div>{ asyncValidating === 'email' ? 'validating...' : ''}</div>
</div>
<div>
<label>Password</label>
<input type="password" {...password} />
<div>{password.touched ? password.error : ''}</div>
<div>{ asyncValidating === 'password' ? 'validating...' : ''}</div>
</div>
<button type="submit" disabled={submitting}>Submit</button>
</form>
</div>
);
}
}
export default SignInForm;
Container
containers/SignInFormContainer.js
import { reduxForm } from 'redux-form';
import SignInForm from '../components/SignInForm';
import { signInUser, signInUserSuccess, signInUserFailure } from '../actions/UsersActions';
// Client side validation
function validate(values) {
var errors = {};
var hasErrors = false;
if(!values.email || values.email.trim() == '') {
errors.email = "Enter a registered email.";
hasErrors = true;
}
if(!values.password || values.password.trim() == '') {
errors.password = "Enter password.";
hasErrors = true;
}
return hasErrors && errors;
}
// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
console.log('this is showed');
dispatch(signInUser(values))
.then((response) => {
console.log('this console.log is not showed');
let data = response.payload.data;
// if any one of these exist, then there is a field error
if(response.payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(response.payload));
reject(data); // this is for redux-form itself
} else {
// store JWT Token to browser session storage
// If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
// sessionStorage = persisted only in current tab
sessionStorage.setItem('dhfUserToken', response.payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(response.payload));
resolve(); // this is for redux-form itself
}
});
});
}
const mapDispatchToProps = (dispatch) => {
return {
signInUser: validateAndSignInUser
}
}
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
// connect: first argument is mapStateToProps, 2nd is mapDispatchToProps
// reduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps
export default reduxForm({
form: 'SignInForm',
fields: ['email', 'password'],
null,
null,
validate
}, mapStateToProps, mapDispatchToProps)(SignInForm);
Presentational/Page/View
presentational/SignIn.js
import React, { Component } from 'react';
import HeaderContainer from '../containers/HeaderContainer';
import SignInFormContainer from '../containers/SignInFormContainer';
class SignIn extends Component {
render() {
return (
<div>
<HeaderContainer />
<SignInFormContainer />
</div>
);
}
}
export default SignIn;
Reducers
reducres/UserReducer.js
import {
ME_FROM_TOKEN, ME_FROM_TOKEN_SUCCESS, ME_FROM_TOKEN_FAILURE, RESET_TOKEN,
SIGNIN_USER, SIGNIN_USER_SUCCESS, SIGNIN_USER_FAILURE,
LOGOUT_USER
} from '../actions/UsersActions';
const INITIAL_STATE = {user: null, status:null, error:null, loading: false};
export default function(state = INITIAL_STATE, action) {
let error;
switch(action.type) {
case ME_FROM_TOKEN:// loading currentUser("me") from jwttoken in local/session storage storage,
return { ...state, user: null, status:'storage', error:null, loading: true};
case ME_FROM_TOKEN_SUCCESS://return user, status = authenticated and make loading = false
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case ME_FROM_TOKEN_FAILURE:// return error and make loading = false
error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors
return { ...state, user: null, status:'storage', error:error, loading: false};
case RESET_TOKEN:// remove token from storage make loading = false
return { ...state, user: null, status:'storage', error:null, loading: false};
case SIGNIN_USER:// sign in user, set loading = true and status = signin
return { ...state, user: null, status:'signin', error:null, loading: true};
case SIGNIN_USER_SUCCESS://return authenticated user, make loading = false and status = authenticated
return { ...state, user: action.payload.data.user, status:'authenticated', error:null, loading: false}; //<-- authenticated
case SIGNIN_USER_FAILURE:// return error and make loading = false
error = action.payload.data || {message: action.payload.message};//2nd one is network or server down errors
return { ...state, user: null, status:'signin', error:error, loading: false};
case LOGOUT_USER:
return {...state, user:null, status:'logout', error:null, loading: false};
default:
return state;
}
}
reducers/index.js
import { combineReducers } from 'redux';
import { UserReducer } from './UserReducer';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
user: UserReducer,
form: formReducer // <-- redux-form
});
export default rootReducer;
Store
import {createStore, applyMiddleware, combineReducers, compose} from 'redux';
import thunkMiddleware from 'redux-thunk';
import {devTools, persistState} from 'redux-devtools';
import rootReducer from '../reducers/index';
let createStoreWithMiddleware;
// Configure the dev tools when in DEV mode
if (__DEV__) {
createStoreWithMiddleware = compose(
applyMiddleware(thunkMiddleware),
devTools(),
persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore);
} else {
createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);
}
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import configureStore from './store/configureStore';
import {renderDevTools} from './utils/devTools';
const store = configureStore();
ReactDOM.render(
<div>
{/* <Home /> is your app entry point */}
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>
{/* only renders when running in DEV mode */
renderDevTools(store)
}
</div>
, document.getElementById('main'));
I hope you can help me! I don't know if something is wrong because I'm using Thunk and the example don't, or if something is missging.
Thank you guys!
It looks like you are using redux-thunk where as I am using redux-promise middlewares. They are totally different. You should change redux-thunk to redux-promise if you want to use the repo
I solved my issue. The difference is that I need to process de Promise received from signInUser throug the attribute that has it.
I had to receive the response in response and then access to the Promise in response.payload. In addition, I had to use .then() and .catch() to handle it.
// For any field errors upon submission (i.e. not instant check)
const validateAndSignInUser = (values, dispatch) => {
return new Promise ((resolve, reject) => {
let response = dispatch(signInUser(values));
response.payload.then((payload) => {
// if any one of these exist, then there is a field error
if(payload.status != 200) {
// let other components know of error by updating the redux` state
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
} else {
// store JWT Token to browser session storage
// If you use localStorage instead of sessionStorage, then this w/ persisted across tabs and new windows.
// sessionStorage = persisted only in current tab
sessionStorage.setItem('dhfUserToken', payload.data.token);
// let other components know that we got user and things are fine by updating the redux` state
dispatch(signInUserSuccess(payload));
resolve(); // this is for redux-form itself
}
}).catch((payload) => {
// let other components know of error by updating the redux` state
sessionStorage.removeItem('dhfUserToken');
dispatch(signInUserFailure(payload));
reject(payload.data); // this is for redux-form itself
});
});
}