Recoil not persisting state, when refreshing page - javascript

I have some state with React recoil, but whenever the page is manually refreshed, the recoil state is reset.
Is this normal behaviour, because i know other state management libraries like flux and react-redux will do this.
Is it best practise to save it into localStorage to actually have it persisted in the browser (because localStorage is also a synchronous api, so that could definitely also cause some issues.)
Even though it is a fairly new library, is there some way to persist state even on manual page refreshes?

I have some state with React recoil, but whenever the page is manually refreshed, the recoil state is reset.
Yes, it's normal behavior.
Is this normal behavior because I know other state management libraries like flux and react-redux will do this.
Yes, only a subset of the state management libraries persists the data themselves. It's more common to find external libraries that do that or custom solutions.
Is it best practice to save it into localStorage to actually have it persisted in the browser (because localStorage is also asynchronous API, so that could definitely also cause some issues.)
It depends on your needs:
do you want to offer a full offline experience? Go for it
are you concerned by the performance implications in case of big amount of data? You could consider indexedDB
are you just trying to persist some auth tokens? Probably sessionStorage could be the right solution
does the server need to know the stored data? You could opt for cookies
Let's say that without knowing your use case it's hard to suggest something ๐Ÿ˜‰
Even though it is a fairly new library, is there some way to persist state even on manual page refreshes?
Yes, with useRecoilTransactionObserver_UNSTABLE you could get notified of every Recoil change and persisting the data. Then, with RecoilRoot' initializeState you could restore it. As you said, the library is new and the APIs could change rapidly in the next months ๐Ÿ˜‰

You could use recoil-persist library to persist state to localStorage.
Here how you could use it:
import { recoilPersist } from 'recoil-persist'
const { persistAtom } = recoilPersist()
const counterState = atom({
key: 'count',
default: 0,
effects_UNSTABLE: [persistAtom],
})

This works nicely, from the docs here.
Any change to the order state gets written to localStorage, and the value from localStorage is read into the order state on refresh.
// define this function somewhere
const localStorageEffect = key => ({setSelf, onSet}) => {
const savedValue = localStorage.getItem(key)
if (savedValue != null) {
setSelf(JSON.parse(savedValue))
}
onSet(newValue => {
localStorage.setItem(key, JSON.stringify(newValue))
})
}
// this is an example state atom
export const orderState = atom({
key: 'orderState',
default: {
store: {}, // { id, name, phone, email, address }
items: {}, // { [itemId]: { id, name, sizeTable, quantity, size } }
contact: { deliveryOption: 'ship' }, // { name, email, phone, address, city, state, zipcode, promotions, deliveryOption }
},
// add these lines to your state atom,
// with the localStorage key you want to use
effects_UNSTABLE: [
localStorageEffect('order'),
],
})

Related

React Redux Toolkit: Is it okay to write to localStorage in createSlices "reducers" parameter?

I'm implementing a simple favorites list. Currently there's no backend part so it will be persisted in localStorage. In the future it may become an API call.
Is it okay to call localStorage.setItem
and localStorage.getItem from within a rtk slice?
For example:
createSlice({
name: "slice",
initialState,
extraReducers: extraReducers,
reducers: {
setLocalStorageThing: (
state,
action: PayloadAction<{ payload: string }>
) => {
let localStoragething= JSON.parse(
localStorage.getItem("key")
);
if (localStorageThing !== payload) {
localStorage.setItem(
"key",
JSON.stringify(payload)
);
}
},
}
Alternatively, is this possible as a thunk? Ultimately I'd like to be able to use the useSelector hook to access this information, so when the swap happens I only have to change the action/reducer in the slice. This seems like a side effect, but I'm not sure the harm in it. It has already been done else where in the code without issue (so far).
It's not recommended to deal with localStore inside reducers because they must be pure https://redux.js.org/understanding/thinking-in-redux/three-principles. You can use a thunk to deal with it
Writing logic for local storage inside reducers will not break the application. But it breaks the rules of writing reducers, i.e reducers should not cause "side effects". See the rules here. In short, it means try to make reducers be all concerned about redux. One way to solve your problem in sync with redux recommendations, is to use a middleware before a particular action type has been run.

How to persist UI component state data in Sapper?

In a Sapper app, I want to be able to persist the state of some UI components so I can navigate the app without losing state when the user returns to the pages using those components.
In a Svelte-only app, this is usually done with a custom store that uses the sessionStorage or localStorage API. A good example of that can be found in R. Mark Volkmann's book Svelte and Sapper in Action, ยง6.24:
store-util.js
import {writable} from 'svelte/store';
function persist(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
}
export function writableSession(key, initialValue) {
const sessionValue = JSON.parse(sessionStorage.getItem(key));
if (!sessionValue) persist(key, initialValue);
const store = writable(sessionValue || initialValue);
store.subscribe(value => persist(key, value));
return store;
}
Unfortunately, using stores that way breaks immediately in Sapper because the scripts run on the server first, where sessionStorage is not defined. There are ways to prevent some parts of code from running on the server (using the onMount lifecycle function in a component, or checking process.browser === true), but that doesn't seem possible here.
Persisting some state locally looks like a very common use case so I'm wondering what's the right way to do it in a Sapper app (considering that I haven't even found the wrong way).
Provide a dummy store for SSR.
It is always possible to do feature detection with something like typeof localStorage !== 'undefined'.
Your component code will re-run in the browser, even if the page was SSR'd. This means that if it is fed a different store, the browser-only values will take over and update existing state (inherited from the server).
See this answer for an example.

Where should things like the current state of an async action be stored in a react-redux application?

I have a login popup which maps a 'isLoggingIn' boolean to the redux store. When a login request action is dispatched a saga intercepts the action and sends another action that the login is processing, the reducer will take that in and set the 'isLoggingIn' boolean to true.
My store:
export interface AppState {
playerToken:string,
loginOpen: boolean,
loginProcessing: boolean
}
The login saga:
function* loginUser(action: any) {
yield put({ type: (LOGIN + PROCESSING) });
try {
const response = yield call(apiCall, 'api/token', 'POST', { username: action.payload.username, password: action.payload.password });
if (response)
{
yield put({ type: (LOGIN + SUCCESS), payload: response.data });
}
catch ({ statusCode }) {
if (statusCode === 401) {
yield put({ type: (LOGIN + FAIL), payload: { error: "Invalid username or password" } })
}
console.log(statusCode);
}
}
Once the saga is done with the login if there's an error it dispatches an action which the reducer sets to a 'loginError' string in the store and sets the isLoggingIn to false, otherwise isLoggingIn is set to false and the user login id is set which prompts the popup to hide itself (i.e. isVisible={this.props.playerToken == undefined).
This seems insanely complicated but I'm not sure how to break this down using Redux principles. I feel strongly the isProcessingLogin should be part of the components state, but the component has no real idea what's going on after it sends the login attempt event and there's no way for it to ever know unless it's listening on for something in the props.
It gets much worse with the various crud operations which need to happen and the various 'isCreatingXModel' booleans which have to be set to true/false in the store and mapped correctly in components.
Is this how redux is supposed to work or am I over using it in places it doesn't belong?
If this is how redux is supposed to be used what are its benefits exactly? I've read online a lot about things which make sense like having a single point of truth, but they can all be done without the crazy redux bloat, I've read people say not to use redux until you need it but that means I'm going to be doing api calls in two conceptually separate areas of code when redux is integrated whenever I 'need it', finally one of the biggest advantages I see purported by advocates is its ability to rewind and move forward in time, which is great but it won't work in any live application which connects to a database in the backend it manipulates unless as part of rewinding there's an undo last api call action.
Keep in mind that these are all entirely my opinions.
1. You might not need sagas (or thunk or other 'async' redux plugin)
Remember that redux is state management only. The API calls can be written in vanilla javascript with or without redux. For example: here's a basic replication of your flow without sagas:
e.g.
import { setLoadingStatus } from './actions'
import { store } from './reducers' // this is what is returned by a createStore call
export function myApiCall(myUrl, fetchOptions) {
store.dispatch(setLoadingStatus('loading'))
return fetch(myUrl, fetchOptions)
.then((response) => {
store.dispatch(setLoadingStatus('succeeded', data))
// do stuff with response data (maybe dispatch a different action to use it?)
})
.catch((error) => {
store.dispatch(setLoadingStatus('failed', error))
// do stuff
})
}
Note the use of store.dispatch. There's an interesting notion in React-Redux that you can only dispatch actions with mapDispatchToProps, but fortunately, that's not true.
I replaced your multiple actions with one that takes a state and optional data. This'll reduce the number of actions and reducers you need to write, but your action history will be harder to read. (Instead of three distinct actions, you'll only have one.)
2. You might not need redux.
The example function above could look basically identical if you weren't using redux -- imagine replacing the store.dispatch calls with this.setState calls. In the future, when you added it, you'd still have to write all the reducer, action, action creator boilerplate, but it would be only slightly more painful than doing it from the start.
As I said above, I usually go with Redux when working with React the built-in state management is has a bad mental map with any sort of large app.
There are two opposing rules of thumb:
use redux for state that needs to be shared between components
use redux for all state and get a single source of truth
I tend to lean to the second one. I hate hunting down errant pieces of state in the leaves of a large React component tree. This is definitely a question with no "correct" answer.

Is the ngrx store persistent?

Is the ngrx store persistent? In other words can we close the browser reopen it, and retrieve state that was added to the ngrx store?
Currently ngrx/store doesn't support such functionality. But you can maintain state by using a library like ngrx-store-persist to save data to the browsers indexedDB, localStorage or WebStorage. This library automatically saves and restores ngrx/store's data. You just need to add redux keys that you want to store in the config (see "Usage" section).
The nrxg/store is in memory only. To manage state via the browser with something like pouchdb ngrx/effects can be used. More here.
You can maintain state by using a library like idb.js to save data to the browsers indexDB, then by using the ngrx effect you can have an init effect to reload the state back when webapp loads. here is a example code let say I want to reload selected language. the effect would be:
#Effect()
init$: Observable<Action> = defer(() => {
return from(storageDb.readData('app', Lang.ActionType.LANG_KEY)).pipe(
map((data: any) => {
const tmpData = data ? data.state : {dir: 'rtl', selected: 'ar'};
this.translate.setDefaultLang(tmpData.selected);
return new Lang.LanguageInit(tmpData);
})
);
});
the storageDb.readData is a wrapper for idb to load data by key, the effect will kick in when effects are getting loaded it will get the data from the indexdb and if it does not find any sets default value then dispatchs an Action. ngrx best practices is to use promises in effects not in reducers since loading the data is async operation, then in your state reducer you can catch the Action like so:
case lang.ActionType.LANGUAGE_INIT: {
return {...state, ...action.payload};
}
Using it in this way you can slice your states save them and loaded them even when lazyloading.

How much of this business logic belongs in Vuex?

I have a simple app which pulls products from an API and displays them on-page, like this:
I've added Vuex to the app so that the search results as well as the product search array doesn't disappear when the router moves the user to a specific product page.
The search itself consists of the following steps:
show loading spinner (update the store object)
dispatch an action to access the API
update the store object with products, spinner
decide if the product list is exhausted
hide loading spinner
You get the idea.
With all of the variables stored in Vuex, it stands to reason all of the business logic should belong there as well, but should it really?
I'm talking specifically about accessing store params such as productsExhausted (when there are no more products to display) or productPage (which increments every time the infinite scroller module is triggered) etc.
How much logic - and what kind - belongs in Vuex? How much does not?
I was under the impression that Vuex is used for storage only but since all of the data is located there, fetching it all back to the Vue app only to send it all back seems like an overly verbose way to address the problem.
Vuex allows you to share data !
For everything that concerns the state of the app its pretty straightforward.
All the data that can be used by multiple components should be added
to the store.
Now concerning the business logic, even though I find its not really clear in the official documentation, it should follow the same principle.
What I mean is that logic that can be used by multiple components should be stored in actions.
Moreover actions allows you to deal with async operations. Knowing this, your code that pulls the data should definitely be stored in vuex's actions.
What I think you should do is to put the request inside an action, then mutate the state of your variables and automatically your UI will reflect the changes.
Moreover, a good pattern to apply is to convert most of the logic to a state logic. For instance consider this demo of a jumping snowman. In here the click action results on updating a value from the store. Although the interesting part is that one component uses the watch functionnality to be notified when the store changes. This way we keep the logic inside the component but use the store as an event emitter.
var store = new Vuex.Store({
state: {
isJumping: 0
},
mutations: {
jump: function(state){
state.isJumping++;
}
}
})
Vue.component('snowman', {
template: '<div id="snowman" :class="color">โ›„</div>',
computed: {
isJumping: function(){
return this.$store.state.isJumping;
}
},
watch: {
isJumping: function(){
var tl = new TimelineMax();
tl.set(this.$el,{'top':'100px'})
tl.to(this.$el, 0.2, {'top':'50px'});
tl.to(this.$el, 0.5, {'top':'100px', ease: Bounce.easeOut});
}
}
})

Categories