Will Redux store be cleaned after reloading the browser?
And can i use redux instead of cookie to save user info and token?
Redux is a state management library so,on refresh ,the redux store contains only the initialstate of the reducers.If you want to save tokens or authenticated user info then save it in localStorage.And also make sure,you un set the local storage after logging out of the app.
Use Redux Persist or build a middleware that will save the store everytime an action is dispatched and then create a HOC that when the app is reloaded (a.k.a the page is refreshed), it'll check the local storage for the item and then restore it to the store before the app is loaded and then render the application.
Depending on the complexity and if you want to blacklist certain reducers, I would use Redux-Persist. If you want to something simple and built by yourself use the middleware option.
Redux store gets initial state upon app reload.
Try this:
Make a dump component for local storage and use it anywhere you want.
Constants.js
export const USER_MODEL = {
set: ({ token, userInfo }) => {
localStorage.setItem('token', token);
localStorage.setItem('userInfo', userInfo);
},
remove: () => {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
},
get: () => ({
token: localStorage.getItem('token'),
userInfo: localStorage.getItem('userInfo’),
})
};
User.js
import { USER_MODEL } from './Constants';
// just an example how you can set localStorage anywhere in your component
USER_MODEL.set({
token: “abc”,
userInfo: {name: ‘foo’, city: ‘bar’},
});
// get user model from localStorage
const token = localStorage.get().token;
const userInfo = localStorage.get().userInfo;
Related
Every time I reload the my account page, it will go to the log in page for a while and will directed to the Logged in Homepage. How can I stay on the same even after refreshing the page?
I'm just practicing reactjs and I think this is the code that's causing this redirecting to log-in then to home
//if the currentUser is signed in in the application
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
unsubscribe();
resolve(userAuth); //this tell us if the user is signed in with the application or not
}, reject);
})
};
.....
import {useEffect} from 'react';
import { useSelector } from 'react-redux';
const mapState = ({ user }) => ({
currentUser: user.currentUser
});
//custom hook
const useAuth = props => {
//get that value, if the current user is null, meaning the user is not logged in
// if they want to access the page, they need to be redirected in a way to log in
const { currentUser } = useSelector(mapState);
useEffect(() => {
//checks if the current user is null
if(!currentUser){
//redirect the user to the log in page
//we have access to history because of withRoute in withAuth.js
props.history.push('/login');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},[currentUser]); //whenever currentUser changes, it will run this code
return currentUser;
};
export default useAuth;
You can make use of local storage as previously mentioned in the comments:
When user logs in
localStorage.setItem('currentUserLogged', true);
And before if(!currentUser)
var currentUser = localStorage.getItem('currentUserLogged');
Please have a look into the following example
Otherwise I recommend you to take a look into Redux Subscribers where you can persist states like so:
store.subscribe(() => {
// store state
})
There are two ways through which you can authenticate your application by using local storage.
The first one is :
set a token value in local storage at the time of logging into your application
localStorage.setItem("auth_token", "any random generated token or any value");
you can use the componentDidMount() method. This method runs on the mounting of any component. you can check here if the value stored in local storage is present or not if it is present it means the user is logged in and can access your page and if not you can redirect the user to the login page.
componentDidMount = () => { if(!localStorage.getItem("auth_token")){ // redirect to login page } }
The second option to authenticate your application by making guards. You can create auth-guards and integrate those guards on your routes. Those guards will check the requirement before rendering each route. It will make your code clean and you do not need to put auth check on every component like the first option.
There are many other ways too for eg. if you are using redux you can use Persist storage or redux store for storing the value but more secure and easy to use.
I am setting my Redux state to be the user object of the user that is logged in (coming from my backend database).
This is what that reducer looks like:
const initialState = {
user: {},
}
export default function(state = initialState, action) {
switch(action.type) {
case LOGIN_USER:
return {
...state,
user: action.payload
}
...
With the payload from LOGIN_USER being the user object of the user who logged in.
Now using this state tree, every time I refresh or hit a new route the state is wiped and I am left with an empty object for the 'user' state. I know that there is middleware like redux-persist that aims to solve this issue but I can recall seeing/hearing of projects that do not use this middleware to persist their logged in user state.
Is there any way that I can persist my logged in user throughout refresh/route changes using my current build? Or would I need to somehow change my build to get it to work?
Thanks a lot!
The simplest and most trivial approach is:
store.subscribe(() => {
const {user} = store.getState();
localStorage.set("user", JSON.stringify(user));
});
I use React Context API to store the information that a user is authenticated.
In development mode when I type in any URL that redirects to the 404 error page the context data is lost. When I navigate to a valid page a previously logged in user is not logged in any more.
EDIT: I just tested this with gatsby build and gatsby serve. A built gatsby site keeps the context when redirecting to 404 error page. But the context is still lost when navigating to completely different URL such as www.google.com.
Now my question is: How do I resupply the context with the login information without having the user be manually log in again?
Here is my AuthContextProvider wrapper class:
export class AuthContextProvider extends React.Component {
constructor(props) {
super(props);
this.state = { user: {} };
}
// ...
render() {
return (
<AuthContext.Provider value={{ getUser: this.getUser, setUser: this.setUser }}>
{this.props.children}
</AuthContext.Provider>
);
}
}
I wrap my whole app with the Context Provider in a root layout:
const RootLayout = ({ children }) => {
return (
<AuthContextProvider>
{children}
</AuthContextProvider>
);
}
React Context is about providing some data to one or more child components without having to pass the data down through intermediary components. There's no built-in mechanism for persisting state between page loads, so you'll need to reach for another tool for that.
If you haven't already implemented your authentication layer, you'll want to look into how that will work. There are a number of strategies for maintaining that state, even just within using cookie-based storage. JWT (JSON Web Token) are a popular method that will let you store signed user and client-readable data in the cookie at the cost of requiring a bit more work to manage expiration/renewal and having a larger payload. Assuming that's the approach you took, you might do something like this:
import React from "react";
import jwt from "jsonwebtoken"; // Add jsonwebtoken via npm/yarn
function getCookieValue(a) {
var b = document.cookie.match('(^|[^;]+)\\s*' + a + '\\s*=\\s*([^;]+)');
return b ? b.pop() : '';
}
const AUTH_PUBLIC_KEY = "your JWT public key here"
export const AuthContext = React.createContext();
export class AuthContextProvider extends React.Component {
state = {
authenticated: false,
userid: null,
};
componentDidMount() {
jwt.verify(getCookieValue("session"), AUTH_PUBLIC_KEY, (err, session) => {
if (!err && session.userid) {
this.setState({ userid: session.userid, authenticated: true })
}
})
}
// Important: REMOVE THIS AFTER TESTING/DEV
toggleLogin = () => {
this.setState(state => ({
authenticated: !state.authenticated,
userid: 2,
}));
}
render() {
return (
<AuthContext.Provider
value={{
...this.state,
toggleLogin: this.toggleLogin,
}}
>
{this.props.children}
</AuthContext.Provider>
);
}
}
This will parse the JWT token in the session cookie when the AuthContextProvider is mounted and update the state with the userid value stored in the JWT if one is present.
You will probably want to wrap the Gatsby App with this component, which you can do from gatsby-browser.js and gatsby-ssr.js files (create them in the root of your repo if you don't have them yet):
// gatsby-browser.js
import React from "react"
import AuthContextProvider from "components/AuthContextProvider"
export const wrapRootElement = ({ element }) =>
<AuthContextProvider>{element}</AuthContextProvider>
// gatsby-ssr.js
import React from "react"
export { wrapRootElement } from "./gatsby-browser"
You will still need to handle generating the JWT token (probably from a backend that is handling authentication) and if it's not already being persisted in a cookie you can access from the browser you will need to handle creation of that cookie at the relevant point in your application lifecycle.
I hope this helps you or others. The blog post below describes how you need to use gatsby-browser.js to wrap the root element in the provider so that it doesn't reset it on page change.
https://www.gatsbyjs.org/blog/2019-01-31-using-react-context-api-with-gatsby/
You have 3 possibilities:
web storage aka localStorage or sessionStorage (easiest, least secure)
session cookies (secure, requires backend server)
json web tokens (JWT) (most secure, requires backend server)
An excellent read about background infromation is this blog on dev.to.
1. web storage such as localStorage
This is considered to be the least secure option secure option. Do not save personal data such as email adresses here. Never ever save sensitive information such as credit card information and such.
This question describes how to use it:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('retrievedObject: ', JSON.parse(retrievedObject));
2. cookies or session cookies
For Express you can use express-ession. Other web servers have similar middleware. The point is to supply the user info within a cookie as described on MDN.
3. json web tokens
This is similar to cookies but uses JSON web tokens. #coreyward gave an excellent answer. You can also read more in this blog post.
Basic setup:
1) App is React and Redux,
2) App is served by a front facing NGINX serving static files like html, pictures and of course the app itself. It also forwards all relevant requests (web sockets and/or AJAX) to the back end (phoenix/elixir).
3) Users are required to authenticate. I'm using redux-oidc library, which is client side only and it works fine.
4) After user logs on is when I get hazy on what to do next.
Question(s):
1) I can't send the state along with the first request because I don't know who the user is and, thus, don't know which state to send. Meanwhile application is already booted (empty store created, login component displayed),
2) After user logs on I can't show anything (like user specific nav bar, timeline , mailbox) I have to saturate the store and let react do its job. What approach should I take?
3) Server rendering is out because a) I'm not using Node and rendering react components using chosen framework is messy and complicated at best and b) I won't be able to export the app to NGinx since it only serves static assets and there is no server logic run there. I could, theoretically, get rid of NGinx, have server based login on the API server and send down HTML along with JSON state which could be used to render the app on the client. However, NGinx does not only serve static assets but also load balances few instances and, thus, getting rid of it is not something I want to do.
Any advice would be appreciated.
Hydrating the state after the store was created, can be achieved by creating a main reducer that can bypass the top level reducers, and replace the whole state.
Reducers are functions that get the current state, combine it with the payload of an action, and return a new state. Usually the main reducer is a combination of all top reducers using combineReducers, and the state is the combination of state pieces returned by the top level reducers.
However, the main reducer can react to actions directly. If the main reducer receives a certain action (hydrate), instead of calling the combined reducers, it returns the action's payload (the saved state). Other actions are passed to the combined reducers.
const mainReducer = (state = {}, action) =>
action.type === 'hydrate' ?
action.payload // hydrate the state
:
reducers(state, action); // create new state by using combined reducers
Working example:
const { combineReducers, createStore } = Redux;
const people = (state = [], action) => action.type === 'people' ? [...state, action.payload] : state;
const items = (state = [], action) => action.type === 'items' ? [...state, action.payload] : state;
const reducers = combineReducers({
people,
items
});
const mainReducer = (state = {}, action) => action.type === 'hydrate' ? action.payload : reducers(state, action);
const store = createStore(mainReducer);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'people', payload: 5 });
store.dispatch({ type: 'items', payload: 'green' });
store.dispatch({ type: 'hydrate', payload: {
people: [20, 30, 50, 100],
items: ['green', 'yellow', 'red']
}});
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
Even though the accepted answer might do the trick, I think that what you are chasing is an anti-pattern, after all.
If you are doing client rendering and handling authentication client side, the only thing you should send to your client, in my opinion, is a <Spinner/>, with zero preloaded state.
Once you are on the client, initialise your store, do your authentication, and decide if you are going to fetch data and render the authenticated version of the page, or if user is not authenticated yet, show them the login form. Everything from this point on, should be handled client side.
Here is what Redux has to say about it:
https://redux.js.org/usage/server-rendering
So, if during the initial render you got "Schrödinger" user (might be authenticated or not), the only thing you can safely assume to show is a spinner.
ANOTHER OPTION
If you really need to get new preloaded data from the server on multiple requests (that's kind of what happens in NextJS apps).
If you are going to pre-render every page (and get new state that on every page change), you can do what NextJS suggests you to do:
They basically re-initialise the store and merge the new data every time it receives new preloaded data from the server.
Here is the example:
https://github.com/vercel/next.js/tree/canary/examples/with-redux-thunk
And the main part of their code:
import { useMemo } from 'react'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
import reducers from './reducers'
let store
function initStore(initialState) {
return createStore(
reducers,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
// Reset the current store
store = undefined
}
// For SSG and SSR always create a new store
if (typeof window === 'undefined') return _store
// Create the store once in the client
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
From the discussion here it seems that the state of Redux reducers should be persisted in a database.
How does something like user authentication works in this instance?
Wouldn't a new state object be created to replace the previous state in the database for every user (and their application state) created and edited?
Would using all of this data on the front end and constantly updating the state in the database be performant?
Edit: I've created an example Redux auth project that also happens to exemplify universal Redux, and realtime updating with Redux, Socket.io and RethinkDB.
From the discussion here it seems that the state of Redux reducers should be persisted in a database.
To persist the state or not, it's likely not a concern of Redux at all. It's more up to application logic.
If something happens in an application, like data upload to server, obviously you need to save state (or a slice of the state to a server).
Since network calls are asynchronous, but Redux is synchronous - you need to introduce additional middleware, as redux-thunk or redux-promise.
As sign-up example, you likely need that actions,
export function creatingAccount() {
return { type: 'CREATING_ACCOUNT' };
}
export function accountCreated(account) {
return { type: 'ACCOUNT_CREATED', payload: account };
}
export function accountCreatingFailed(error) {
return { type: 'ACCOUNT_CREATING_FAILED', payload: error };
}
export function createAccount(data, redirectParam) {
return (dispatch) => {
dispatch(creatingAccount());
const url = config.apiUrl + '/auth/signup';
fetch(url).post({ body: data })
.then(account => {
dispatch(accountCreated(account));
})
.catch(err => {
dispatch(accountCreatingFailed(err));
});
};
}
Some portion of state, e.g. user object after authorization, might be stored to localStore and re-hydrated on next application run.
Those are valid concerns. Using localStorage to persist state on the frontend might be a better strategy. You can implement this using middleware, for example:
import {createStore, compose, applyMiddleware} from 'redux';
const localStorageMiddleware = ({getState}) => {
return (next) => (action) => {
const result = next(action);
localStorage.setItem('applicationState', JSON.stringify(
getState()
));
return result;
};
};
const store = compose(
applyMiddleware(
localStorageMiddleware
)
)(createStore)(
reducer,
JSON.parse(localStorage.getItem('applicationState'))
)
If you're concerned about the enemy accessing the user's laptop and stealing credentials from it you could persist state to the backend when the user leaves the page (Navigator.sendBeacon() might be helpful here) & store it in the session.