I am using redux in react.js When i refresh the application, redux state also get refreshed. How can I persist the redux state, without using the local storage.
I don't want to know about redux-persist/redux-storage package
In Main Class:
import PersistedStore from "./PersistedStore";
ReactDOM.render(
<Provider store={PersistedStore.getDefaultStore().store}><MainClass />
</Provider>,
document.getElementById('root')
);
add the following class to your project
import {
createStore
} from "redux";
import rootreducer from './RootReducer'
const LOCAL_STORAGE_NAME = "localData";
class PersistedStore {
// Singleton property
static DefaultStore = null;
// Accessor to the default instance of this class
static getDefaultStore() {
if (PersistedStore.DefaultStore === null) {
PersistedStore.DefaultStore = new PersistedStore();
}
return PersistedStore.DefaultStore;
}
// Redux store
_store = null;
// When class instance is used, initialize the store
constructor() {
this.initStore()
}
// Initialization of Redux Store
initStore() {
this._store = createStore(rootReducer, PersistedStore.loadState());
this._store.subscribe(() => {
PersistedStore.saveState(this._store.getState());
});
}
// Getter to access the Redux store
get store() {
return this._store;
}
// Loading persisted state from localStorage, no need to access
// this method from the outside
static loadState() {
try {
let serializedState = localStorage.getItem(LOCAL_STORAGE_NAME);
if (serializedState === null) {
return PersistedStore.initialState();
}
return JSON.parse(serializedState);
} catch (err) {
return PersistedStore.initialState();
}
}
// Saving persisted state to localStorage every time something
// changes in the Redux Store (This happens because of the subscribe()
// in the instore-method). No need to access this method from the outside
static saveState(state) {
try {
let serializedState = JSON.stringify(state);
localStorage.setItem(LOCAL_STORAGE_NAME, serializedState);
} catch (err) {}
}
// Return whatever you want your initial state to be
static initialState() {
return {};
}
}
export default PersistedStore;
Related
I am using iron-session, next-connect with nextjs in our webapp and one of the requirements is to publish analytics events from our frontend code, like page views, button clicks and other custom events. These events are stored in our database and used by our data analyst with PowerBI.
Our webapp takes a user on an onboarding journey, then once it's done, we create an account for the user and redirects to dashboard. For the onboarding part, we don't have a user id yet while in the dashboard, we already do. However, we want to be able to track the user journey in the webapp so we need an identifier that is persisted throughout the whole journey. Thus, we think of a session id with the iron-session.
Now iron-session doesn't have a concept of session id, so I am trying to implement it myself. The session id will be our identifier of the user in our events table.
Here is the withSession middleware used with next-connect
import { getIronSession } from "iron-session";
import type { IncomingMessage } from "http";
import type { NextApiRequest } from "next";
import { nanoid } from "nanoid";
import appConfig from "#/backend/app.config";
export const sessionOptions = {
password: appConfig.secret,
cookieName: appConfig.cookies.sessionToken.name,
cookieOptions: appConfig.cookies.sessionToken.options,
};
export async function withSession(
req: IncomingMessage | NextApiRequest,
res: any,
next: any
) {
const session = await getIronSession(req, res, sessionOptions);
if (!session.id) session.id = nanoid(32);
req.session = session;
await req.session.save();
return next();
}
declare module "iron-session" {
interface IronSessionData {
user?: { id: string };
id: string;
}
}
And a route that will use the middleware
const router = createRouter<NextApiRequest, NextApiResponse>()
.use(...([withSession, withLogger, withTenant] as const))
.get(async (req, res) => {
// Authenticate user
req.session.user = { id: userId };
await req.session.save();
return res.redirect("/");
});
export default router.handler();
Is this a correct implementation of the said requirement?
Some libraries implement a kind of session.regenerate() when a user perform signIn and signOut. Do I need to implement it too? If I do, I will lose the identifier that persists throughout the whole user journey.
since you are using typescript first define the type of session object
declare module "iron-session" {
interface IronSessionData {
nameOfSessionObject?: {
// in your implementation you were creating req.user and req.id
// you could overwrite the req properties
user?: { id: string };
// you can manually create on the server
id: string;
};
}
}
create a wrapper session function
export function withSession(handler: any) {
return withIronSessionApiRoute(handler, {
password: appConfig.secret,
cookieName: appConfig.cookies.sessionToken.name,
// Said in another way, the browser will not send a cookie with the secure attribute set over an unencrypted HTTP request
cookieOptions: appConfig.cookies.sessionToken.options,
})}
create the session object. you do not use getIronSession when creating a session.
you need that when you need to access to the session object in middleware
export default withSessio(
async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "GET") {
try {
const sessionObj={....}
req.session.nameOfSessionObject={...sessionObj}
await req.session.save();
// whatever you want to return
return res.json(sessionObj);
} catch (error) {
console.error("error in verify post req", error);
// 422 Unprocessable Entity
res.status(422).send({ message: "Cannot create SESSION" });
}
} else if (req.method === "POST") {
try {
..HANDLE POST HERE
} catch (error) {
res.status(422).send({ message: "Cannot generate a SESSION" });
}
} else {
return res.status(200).json({ message: "Invalid api Route" });
}
}
);
now you can import above handler and connect with next-connect
Actions File
import * as AuthApi from "../api/AuthApi.js"
export const logIn=(formData)=>async(dispatch)=>{
dispatch({type:"AUTH_START"})
try {
const data=await AuthApi.logIn(formData)
dispatch({type:"AUTH_SUCCESS",data:data})
} catch (error) {
console.log(error)
dispatch({type:"AUTH_FAILED"})
}
}
export const signUp=(formData)=>async(dispatch)=>{
dispatch({type:"AUTH_START"})
try {
const {data}=await AuthApi.signUp(formData)
dispatch({type:"AUTH_SUCCESS",data:data})
} catch (error) {
console.log(error)
dispatch({type:"AUTH_FAILED"})
}
}
Reducers File
const authReducer=(state={authData:null,loading:false,error:false},action)=>{
switch (action.type) {
case "AUTH_START":
return {...state,loading:true,error:false}
case "AUTH_SUCCESS":
//also storing the data in local storage
localStorage.setItem("profile",JSON.stringify({...action?.data}))
return {...state,loading:false,authData:action.data}
case "AUTH_FAILED":
return {...state,error:true,loading:false}
default:
return state
}
}
export default authReducer
Combine Reducer
import { combineReducers } from "redux";
import authReducer from "./authReducer.js";
export const reducers=combineReducers({authReducer})
Store
import { legacy_createStore as createStore,applyMiddleware,compose } from "redux"
import thunk from "redux-thunk"
import {reducers} from "../reducers/index.js"
function saveToLocalStorage(store){
try {
const serializedStore=JSON.stringify(store)
window.localStorage.setItem('store',serializedStore)
} catch (error) {
console.log(error)
}
}
function loadFromLocalStorage(){
try {
const serializedStore=window.localStorage.getItem("store")
if(serializedStore==null) return undefined
return JSON.parse(serializedStore)
} catch (error) {
console.log(error)
return undefined
}
}
const composedEnhancers=window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose()
const persistedState=loadFromLocalStorage()
const store=createStore(reducers,persistedState,composedEnhancers(applyMiddleware(thunk)))
//so for everytime we ll change the store , that change will be reflected in the local storage as well
store.subscribe(()=>saveToLocalStorage(store.getState()))
export default store
I have search many solutions for it on the internet as well as on the stack overflow but didnt figure it out. my actions are not triggering and the states in the reducer file is also not changing
How to handle firebase auth state observer in redux saga?
firebase.auth().onAuthStateChanged((user) => {
});
I want to run APP_START saga when my app starts which will run firebase.auth().onAuthStateChanged observer and will run other sagas depending on the callback.
As I understand eventChannel is right way to do it. But I don't understand how to make it work with firebase.auth().onAuthStateChanged.
Can someone show how to put firebase.auth().onAuthStateChanged in to eventChannel?
You can use eventChannel. Here is an example code:
function getAuthChannel() {
if (!this.authChannel) {
this.authChannel = eventChannel(emit => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => emit({ user }));
return unsubscribe;
});
}
return this.authChannel;
}
function* watchForFirebaseAuth() {
...
// This is where you wait for a callback from firebase
const channel = yield call(getAuthChannel);
const result = yield take(channel);
// result is what you pass to the emit function. In this case, it's an object like { user: { name: 'xyz' } }
...
}
When you are done, you can close the channel using this.authChannel.close().
Create your own function onAuthStateChanged() that will return a Promise
function onAuthStateChanged() {
return new Promise((resolve, reject) => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
resolve(user);
} else {
reject(new Error('Ops!'));
}
});
});
}
Then use call method to get the user synchronously
const user = yield call(onAuthStateChanged);
This could be handled in the Saga such as the following for Redux Saga Firebase:
// Redux Saga: Firebase Auth Channel
export function* firebaseAuthChannelSaga() {
try {
// Auth Channel (Events Emit On Login And Logout)
const authChannel = yield call(reduxSagaFirebase.auth.channel);
while (true) {
const { user } = yield take(authChannel);
// Check If User Exists
if (user) {
// Redux: Login Success
yield put(loginSuccess(user));
}
else {
// Redux: Logout Success
yield put(logoutSuccess());
}
}
}
catch (error) {
console.log(error);
}
};
here is how you would run the onAuthStateChanged observable using redux-saga features (mainly eventChannel)
import { eventChannel } from "redux-saga";
import { take, call } from "redux-saga/effects";
const authStateChannel = function () {
return eventChannel((emit) => {
const unsubscribe = firebase.auth().onAuthStateChanged(
(doc) => emit({ doc }),
(error) => emit({ error })
);
return unsubscribe;
});
};
export const onAuthStateChanged = function* () {
const channel = yield call(authStateChannel);
while (true) {
const { doc, error } = yield take(channel);
if (error) {
// handle error
} else {
if (doc) {
// user has signed in, use `doc.toJSON()` to check
} else {
// user has signed out
}
}
}
};
please note that other solutions that don't utilize channel sagas are not optimal for redux-saga, because turning an observable into a promise is not a valid solution in this case since you would need to call the promise each time you anticipate a change in authentication state (like for example: taking every USER_SIGNED_IN action and calling the "promisified" observable), which will negate the whole purpose of an observable
Maybe I am over thinking it, but I can't figure out a way to put the results of a Firestore query into a Polymer 3 template. For example:
class MyPage extends PolymerElement {
constructor() {
super();
/* somehow set this.users to the firestore users query results */
}
static get properties() {
return {
users: {
type: String
}
}
}
static get template() {
return html`<div>[[users]]</div>`;
}
}
Using the following code, which does work correctly and print to the console:
var all_users;
const setsRef = firestore.collection("users");
setsRef.get().then(function(querySnapshot) {
var users = [];
querySnapshot.forEach(function(doc) {
users.push(doc.data().verb);
});
all_users = users.join(", ");
console.log("All users: ", all_users);
/* run a function here that sets this.users = all_users */
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
The problem is, I have to run a function from the Firestore results, while the other is a constructor of an object. I would preferably like to have all of my Firebase queries in an external js file, but this async thought process is confusing me.
Use one of the lifecycle methods to load the users:
https://www.polymer-project.org/3.0/docs/devguide/custom-elements#element-lifecycle
class MyPage extends PolymerElement {
constructor() {
super();
this.users = []
}
static get properties() {
return {
users: {
type: String
}
}
}
static get template() {
return html`<div>[[users]]</div>`;
}
async ready() {
super.ready()
try {
const querySnapshot = await firestore.collection("users").get()
this.users = querySnapshot.map(doc => doc.data().verb).join(", ");
} catch (error) {
console.log(error)
}
}
}
If you don't want to use one of the lifescycle methods, then you can fire your own custom event which your MyPage element can listen for: https://www.polymer-project.org/3.0/docs/devguide/events#custom-events
I'm not familiar with Polymer, so the above is untested and what I figure from reading the docs.
I'm trying to build a simple app to view photos posted from nasa's picture of the day service (https://api.nasa.gov/api.html#apod). Currently watching for keypresses, and then changing the date (and asynchronously the picture) based on the keypress being an arrow left, up, right, or down. These would correspondingly change the date represented by a week or a day (imagine moving across a calendar one square at a time).
What I'm having trouble with is this: I've created an async action creator to fetch the next potential date - however I need to know the current state of the application and the keypress to retrieve the new date. Is there a way to encapsulate this into the action creator? Or should I put the application state where the exported action creator is called in the application so I can keep my action creator unaware of the state of the application? I've tried to do this by binding the keydown function in componentDidMount for the top level Component, but the binding to the application store doesn't seem to reflect the changes that happen in the reducer.
The async logic relying on redux-thunk middleware and q:
// This function needs to know the current state of the application
// I don't seem to be able to pass in a valid representation of the current state
function goGetAPIUrl(date) {
...
}
function getAsync(date) {
return function (dispatch) {
return goGetAPIUrl(date).then(
val => dispatch(gotURL(val)),
error => dispatch(apologize(error))
);
};
}
export default function eventuallyGetAsync(event, date) {
if(event.which == 37...) {
return getAsync(date);
} else {
return {
type: "NOACTION"
}
}
}
Here's the top level binding to the gridAppState, and other stuff that happens at top level that may be relevant that I don't quite understand.
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
actions.eventuallyGetAsync(event, gridAppState.date);
});
}
render() {
const { gridAppState, actions } = this.props;
return (
<GridApp gridAppState={gridAppState} actions={actions} />
);
}
}
App.propTypes = {
actions: PropTypes.object.isRequired,
gridAppState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
gridAppState: state.gridAppState
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(GridActions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I've validated that the correctly modified date object is getting to the reducer - however the gridAppState seems stuck at my initial date that is loaded.
What is the right way to approach async logic in redux that relies on attaching event handlers and current application state? Is there a right way to do all three?
You should handle the event in your component and call the correct action depending on the key pressed.
So when you dispatch an async action you can do something like
export default function getNextPhoto(currentDate) {
return (dispatch) => {
const newDate = calculateNewDate(currentDate);
dispatch(requestNewPhoto(newDate));
return photosService.getPhotoOfDate(newDate)
.then((response) => {
dispatch(newPhotoReceived(response.photoURL);
});
};
}
You should handle the keypress event on the component and just dispatch your action when you know you need to fetch a new photo.
Your App would look like
class App extends React.Component {
componentDidMount() {
const { gridAppState, actions } = this.props;
document.addEventListener("keydown", function() {
if (event.which == 37) {
actions.getNextPhoto(gridAppState.date);
} else if (...) {
actions.getPrevPhoto(gridAppState.date);
}
// etc
});
}
}
By the way you re still missing your reducers that update your state in the Redux Store.