How to properly handle simultaneous persistence actions in Redux? - redux

React application using Redux. A have a combined reducer, consisting of appStateReducer and contestReducer. Each of these two takes care of some part of the application data.
When action is performed, I want not only the respective state to be changed, but I also want to persistently save the new state, so that if the user reloads application page in the browser, the state would be preserved.
My idea is to add third reducer to take care only of save and load actions (each of the two sub-states separately).
Save and load will use IndexedDB, through localbase package. All of the db actions (add, get, update, delete) appear to be synchronous, i.e. there seems to be no real need to implement asynchronous actions. UPDATE: this is wrong, it is asynchronous, just some basic examples ignore it.
I am not sure how to handle the problem properly.
I will need a database connection object, a singleton, initialized once after page is loaded, which should be shared by all save/load actions regardless of which part of the state is to be stored or loaded. That would lead to a separate reducer working only with the db object. If I do this, the db reducer would have to have access to all the other sub-state, which is normally not the case in Redux.
Or, I could implement save and load action in each reducers separately, not a big deal, actually. But how to make the global db object accessible by the reducers?
It is as React application written in typescript and all components are implemented as classes.

You already have access to all data if you are using middleware, Example:
export const requestPost = (id) => (dispatch,getState) => {
// You can make an bank for post and check If data exist or not
const postState = getState().bank.posts.data;
const found = postState?.find((post) => post.id === id);
if (found) {
dispatch({ type: SUCCESS.POST, data: found });
} else {
dispatch({ type: REQUEST.POST });
API.get(`/post/v2?id=${id}`)
.then((res) => dispatch({ type: SUCCESS.POST, data: res.data[0] }))
.catch((err) => errorHandler(err, FAILURE.POST));
}
};
Just make and reducer for saving data on DB or somewhere and read them at the start.

Related

Proper way of using Redux and RTKQ in NextJs with code-splitting

This is a topic that's been discussed a lot through github issues and by now I've noticed two main opinions: It's not possible or it should not be done at all.
The argument for both sides is that redux is not meant for it, that the .replaceReducer function is only meant for the purposes of hot-reloading (even though redux itself mentions it as a possibility for code-splitting).
The goal
Anyway, what I would like to achieve (ideally) is a system that only sends the relevant slices and relevant redux code for a specific route in NextJs. And (even more ideally) when navigating between pages the store should just get extended and not re-created.
My initial approach
My first idea was to implement a recipe from the link above, attaching and exposing the injectReducer function onto my store during the store setup:
const store = configureStore({
reducer: {
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(rtkqApi.middleware)
});
store.dynamicReducers = {};
store.injectDynamicReducer = (name, reducer) => {
if (Object.keys(store.dynamicReducers).includes(name)) {
return;
}
store.dynamicReducers[name] = reducer;
store.replaceReducer(
combineReducers({
globals,
[rtkqApi.reducerPath]: rtkqApi.reducer,
...store.dynamicReducers
})
);
};
const makeStore = () => store;
export const wrapper = createWrapper(makeStore);
export const injectReducer = (sliceName, reducer) => store.injectDynamicReducer(sliceName, reducer);
So basically every page would have a globalsSlice, containing the user info and some other global data, and Redux Toolkit Query API slice (which would then be code-split using RTKQ injectEndpoints functionality).
With this setup, each page that wants to inject its own custom slice (reducer) would do something like this:
const SomePage = () => {
const someData = useSelector(somePageSliceSelectors.selectSomeData);
return (
<Fragment>
<Head>
<title>Some Page</title>
</Head>
</Fragment>
)
};
export default SomeRoute;
injectReducer('somePageSlice', somePageReducer);
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
// Whatever necessary logic we need
});
Initially this seemed to have worked fine, but then when I realized that next-redux-wrapper works by calling the makeStore factory on every request, and I'm manipulating and mutating a global store object, there has to be something wrong with this, ie a race condition that I haven't been able to cause by testing. Also another problem occurres when using Redux Toolkit Query. For example, if I need to get a cookie from the original request (the one that nextjs receives) and then re-send it to another API endpoint that is handled by redux toolkit query, I would need to extract the cookie from the request context, to which I don't have access unless I do something like this:
export const makeStore = (ctx) => {
return configureStore({
reducer: ...,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
thunk: {
extraArgument: ctx,
},
}).concat(...),
});
};
which further implies that I should definitely not be mutating the global store object.
So then I thought alright, instead of manipulating the global store I could try doing it in GSSP:
export const getServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
store.injectDynamicReducer('somePageSlice', somePageReducer);
});
But no luck here, the slice does not get loaded and the state does not get constructed. It is my guess that the Provider in the _app gets rendered before this, but I'm not sure.
In conclusion, I'd like to know whether anyone has tried and succeeded in implementing redux code splitting using RTK, RTKQ and NextJs. Also, I would like to ask an addition question: Is it necessary? What I mean by this is, if I were to not code-split at all, and send all slices on every request, how performance impactful would this be? Also, since I'm not sure exactly how the NextJs bundler works and how code chunking is done: If a certain page receives a slice it doesn't use at all, will it only receive its initial state or all of its logic (all the selectors, reducers and actions)? If not then maybe this isn't so bad, since initial states are just empty objects.
I hope I've presented the problem clearly enough, as it is a very complex problem, but feel free to ask follow up questions if something doesn't make sense.
Thanks in advance.

Prevent refetching data when resetting redux state on a reroute with next-router

I currently have this piece of code:
const handleClick = async () => {
dispatch(resetFilters());
if (router.pathname !== '/') {
await router.push('/');
}
};
Where resetFilters() is a function to reset all the state in a slice.
My problem is that wherever I place this function (before or after the reroute), it will cause data to be fetched twice (since what data is fetched depends on the state).
If I place it before, I fetch data based on the reset state on the page I'm rerouting away from (which I won't use)
If I place it after, I fetch data based on the old state on the page I'm rerouting to, which then has to be fetched again with the reset state.
I saw that react-router-redux has a LOCATION_CHANGE action which seems to solve my problem.
Is there an equivalent version for next-router?
I.e. I need something which allows me to update redux state and redirect with next-router in an atomic step.

Ngrx Large Amounts of Data causes app to slow down

I have an app that loads some images with metadata. A single folder can be quite large (~100-142Mb) once loaded into memory. Previously, we were using a plain old javascript object to manage the state of the app and everything worked fine, but i'd like to gain the benefits of ngrx's state management.
I've discovered ngrx and it seemed to be a smarter option when it comes to state management. However, when i add these items to the state, the app hangs when adding images to the store and then performance slows down when accessing individual (and unrelated) flags from the store i.e. UI flag - draw is open.
1) Here "directories" is a Map < string, Directory > () object that is saved the the Store (~100-120Mb). Directory is a complex object with many nested values. Once images are loaded, and then added to the store, it a) hangs and then b) everything else (i.e. changing a ui flag) slows down.
return {
...state,
loadedDirectories: directories,
filesLoading: false,
};
2) The directories are then later accessed from the store.
this.store
.pipe(select(fromReducer.getLoadedDirectories))
.subscribe(loadedDirectories => {
this._directoryData = loadedDirectories;
});
Selector looks like this....
export interface ImageLoaderState {
loadedDirectories: Map<string, Directory>;
filesLoading: boolean;
errorMessage: string;
}
export class AppState {
imageLoader: fromImageLoader.ImageLoaderState;
}
export const combinedReducers = {
imageLoader: fromImageLoader.imageLoaderReducer
.... More reducers here ....
}
// Select Image loader state.
export const selectImageLoaderState = (state: AppState) => state.imageLoader;
export const getLoadedDirectories = createSelector(
selectImageLoaderState,
(state: fromImageLoader.ImageLoaderState) => state.loadedDirectories
);
Using angular 8 and the following versions of ngrx.
"#ngrx/effects": "^8.4.0",
"#ngrx/store": "^8.4.0",
"#ngrx/store-devtools": "^8.4.0",
Are there any better practices? i.e. Add each image, one at a time to the store?
The ngrx store is for application state and not so good as a document store.
Please see..
https://github.com/btroncone/ngrx-store-localstorage/issues/39
One issue I see is how you create your new state. You mention that when you create your new state, you do the following
return {
...state,
loadedDirectories: directories,
filesLoading: false,
};
I think you are creating an object with tons of key-value pairs, then recreating that work when you set the loadedDirectories property again. I'm uncertain about the performance costs of using the spread operator in the context of very large objects. I would suggest you focus on creating this property once. This might help you
Does spread operator affect performance?

Firebase React Native fetch data

I am trying to do an app on react-native with a feed. On my main screen, I go fetch the data :
fetchData() {
firebase.database().ref(`/posts/${group}`).on('value', async snapshot => {...}
}
when I want for example to like a comment of the post, I first push the data into firebase with different queries, for example :
export const likeComment = (...) => {
firebase.database().ref(`/posts/${group}/${post}`).update
({
updatedAt: firebase.database.ServerValue.TIMESTAMP
});
firebase.database().ref(`/posts/${group}/${post}/lastComments`).set(...);
But I realized that my first function fetchData was called 3 times.
then I grouped my queries like :
let updates = {}
updates[`/posts/${group}/${post}/lastComments`] = {...};
updates[`/posts/${group}/${post}`] = { ... };
firebase.database().ref().update(updates);
Then, fetchData was called still 2 times.
I was wondering if that was the best way to do it, and why does my function fetchData was still called twice.
Thanks for your help
It's hard to tell from the limited code, but my guess is that fetchData is not actually being called more than once, but instead the snapshot is firing when you make updates to your Firebase database.
The part of your code where you say .on('value', async snapshot => you're setting up a listener to send you updates any time that value changes.
So my guess is that your three invocations are:
The first time you actually call fetchData and set up the
listener
Your .on( snapshot listener firing when you call
update
Your .on( snapshot listener firing again when you
call set
This push-based database workflow is one of the main benefits of Firebase. If you only want to get the data once and not get live updates, you can call .once( instead of .on(
https://firebase.google.com/docs/database/web/read-and-write

async reducer with react-router seems to have multiple stores?

I have an async reducer structure. On any given page, I inject the page's reducer with:
export const injectReducer = (store, { key, reducer }) => {
// store.asyncReducers already initialized to {}
// makeRootReducer just returns combineReducers({...store.asyncReducers})
store.asyncReducers[key] = reducer
store.replaceReducer(makeRootReducer(store.asyncReducers))
}
I use react-router 3 and the plain routes definitions on any given page. I use require.ensure for async handling of routes within getComponent of the plain route definition:
export default (store) => ({
path : 'counter',
getComponent (nextState, cb) {
require.ensure([], (require) => {
const Counter = require('./containers/Counter').default
const reducer = require('./modules/counter').default
injectReducer(store, { key: 'counter', reducer })
cb(null, Counter)
}, 'Counter')
}
})
My problem is that if I use dispatch followed by browserHistory.push, I would expect that the state gets updated before going to the new page. What happens however, is that there appears to be 2 separate stores. For example, navigating between pages, the value of the counter from the previous page seems to be preserved despite it being on the same key. What is going on here???
Sample repo of my problem. You can git clone, npm install, npm start and go to localhost:3000/counter. If you click Double (Async) it doubles the counter and then goes to the /otherPage. If you then click Half (Async) it will bring you back to /counter. However the value of the counter is the value from doubling, not from halving. Also, importantly, pulling up Redux DevTools and navigating between the pages seems to show the counter change for ALL data before. Almost as if the entire store was replaced, yet the prior values are preserved.
What is going on here???
After much investigation I have discovered that in development ReduxDevTools will recompute the entire state history on a new page. Inserting a new reducer where initialState is different on the new page results in 2 different results. There are not 2 stores or any caches, it is just redux dev tools recomputing the entire state history with different initialStates.
Hence in my scenario, dispatch was updating the state on my first page. Then browserHistory makes a push to go to the second page. The second page recomputes the entire state history. However the second page has a reducer that is missing the action handler for the first page. Hence when recomputing the entire state history, the state doesn't change from that last action.

Resources