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

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.

Related

How to make Next-Auth-session-token-dependent server queries with React Query in Next JS?

I am trying to make an API GET request, using React Query's useInfiniteQuery hook, that uses data from a Next Auth session token in the query string.
I have a callback in /api/auth/[...nextauth.ts] to send extra userData to my session token.
There are two relevant pages on the client side. Let's call them /pages/index.tsx and /hooks/useApiData.ts. This is what they look like, for all intents and purposes:
// pages/index.tsx
export default function Page() {
const {data, fetchNextPage, isLoading, isError} = useCourseData()
if (isLoading) return <main />
return <main>
<InfiniteScroller fetchMore={fetchNextPage}>
{data?.pages?.map(page => page?.results?.map(item: string => item))}
</InfiniteScroller>
</main>
}
// hooks/useApiData.ts
async function fetchPage(pageParam: string) {
const response = await fetch(pageParam)
return await response.json()
}
export default function useApiData() {
const {data: session} = useSession()
const init = `/api?userData=${session?.user?.userData}`
return useInfiniteQuery('query',
({pageParam = init}) => fetchPage(pageParam),
{getNextPageParam: prevPage => prevPage.next ?? undefined}
)
}
My initial request gets sent to the API as /api?userData=undefined. The extra data is definitely making its way into the token.
I can place the data from my session in the DOM via the render function of /pages/index.tsx, so I figure the problem is something to do with custom hooks running before the session context is ready, or something like that... I don't understand the mechanics of hooks well enough to figure that out.
I've been looking for answers for a long time, and I'm surprised not to have found a single person with the same issue. These are not unpopular packages and I guess a lot of people are using them in conjunction to achieve what I'm attempting here, so I figure I must be doing something especially dumb. But what?!
How can I get the data from my Next Auth session into my React Query request? And for bonus points, why is the session data not available when the request is sent in my custom hook?

run function on bases of internet connectivity in react native

I am working on react native application I use firebase as my backend. I fetch data from firebase real time database and render it on the page. But now I want my application to be supported offline.
I used following two functions for rendering.
For listings from database
const loadListings = () => {
let data = [];
listingRef.orderByChild("created_at").on("value", (snapshot) => {
data = [];
snapshot.forEach((listing) => {
data.push(listing.val());
});
cache.store("listings", data.slice(0, 10)); // only stores latest ten listings
setListings(data);
setLoading(false);
});
};
and then use it inside useEffect like.
useEffect(() => {
loadListings();
}, []);
and for listings from cache I used this.
const loadListingsCached = async () => {
let data = await cache.get("listings");
setListings(data);
};
Now I cant put a check inside firs function as effect hook will run only one time and initialy network status is null. its not defined.
how do I achieve this?
by the way link to package I used for detecting connectivity
Edit
I used this hook as second argument to useEffect() but didn't work for me
const netInfo = useNetInfo();
I
What you want to achieve is make the code different depending on what is the network status. In the answer linked by #Rohit there is my answer about how to check the network connectivity with Net Info Package.
What you have to do is make the effect dependant on the status change. You should pass it as a argument to the effect.
const netInfo = useNetInfo();
useEffect(() => {
loadListings();
}, [netInfo]);
This way the code will always run when a network change is detected. I hope this is what you wanted to achive. Please be more specific about you goal and what is the problem. Current questions does not specify if the hook is not working, or the rendering function does not trigger etc.

Redux Toolkit says this snippet(19 lines) is shorter code (vs. original 12) confusion

I am reading this page getting into react-redux https://redux.js.org/introduction/getting-started
I am very confused looking at the Basic Example which has 12 lines of code(excluding usage, imports, and comments)
Then I read this line on the "Redux Toolkit Example" which below the code states "Redux Toolkit allows us to write shorter logic that's easier to read, while still following the same Redux behavior and data flow." However, this example is 19 lines of code(excluding usage, imports, and comments)
The usage in both examples is 3 lines of code. Could someone explain this to me?
Perhaps when it scales, the redux toolkit example does save more code? Honestly, I find the Basic Example MUCH easier to read and maintain. NOTE: I am a complete newb which leads me to believe the basic example may be better as we ramp up and hire developers. This allows them to ramp up more quickly(but I am only a single data point amongst newbs)
thanks,
Dean
You're right about the lines of code in the example. Perhaps that simple counter example doesn't do justice to how much code Redux Toolkit can save because they aren't adding all the "bells and whistles" in their non-toolkit version.
This section is called "getting started with Redux" rather than "migrating to Redux Toolkit" so I suspect they don't want to overwhelm the user by introducing best practices like action creator functions which aren't strictly necessary. But you're not seeing the "write less code" benefit because most of the code that you can remove with the Toolkit is coming from things that weren't in the example in first place.
Action Creators
One of the main benefits of the createSlice function is that it automatically creates the action name constants and action creator functions to go along with each case in the reducer.
This example is just dispatching raw actions directly with string names store.dispatch({ type: 'counter/incremented' }). Most devs would not do this because of how fragile and inflexible it is.
An alternate version of the non-toolkit example, what you would see in most code, looks more like this:
// action name constants
const INCREMENTED = 'counter/incremented';
const DECREMENTED = 'counter/decremented';
// action creator functions
// usually most take some arguments
const incremented = () => ({
type: INCREMENTED,
})
const decremented = () => ({
type: DECREMENTED,
})
// reducer function
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case INCREMENTED:
return { value: state.value + 1 }
case DECREMENTED:
return { value: state.value - 1 }
default:
return state
}
}
If you want to include typescript types it gets even worse.
Immutability
The reducer itself could get really lengthy if you are trying to do immutable updates on deeply nested data.
Here's an example copied from those docs on how to safely update the property state.first.second[someId].fourth
Without Toolkit
function updateVeryNestedField(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
}
With Toolkit:
const reducer = createReducer(initialState, {
UPDATE_ITEM: (state, action) => {
state.first.second[action.someId].fourth = action.someValue
}
})
configureStore
The Toolkit configureStore actually does save a step vs the Redux createStore function when you are combining more than one reducer. But again this example fails to show it. Instead the Toolkit version is longer because we set a reducer property rather than just passing the reducer.
A typical Redux app uses the combineReducers utility to combine multiple reducers as properties on an object:
import {createStore, combineReducers} from "redux";
const rootReducer = combineReducers({
counter: counterReducer,
other: otherReducer
});
const vanillaStore = createStore(rootReducer);
With the Toolkit you can just pass your reducers map directly without calling combineReducers.
import {configureStore} from "#reduxjs/toolkit";
const toolkitStore = configureStore({
reducer: {
counter: counterReducer,
other: otherReducer
}
});
Which is roughly the same amount of code. But it also includes some default middleware which would be extra lines in the non-toolkit example.

is it possible to keep redux dispatch function in class performing async actions?

I am beginner in Redux and I want to use it for asynchronous logic. Redux style quide recommends to use redux-thunk for it, but it seems I don't need it if I use redux in following way:
class Actions {
constructor(dispatch) {
this.dispatch = dispatch;
}
someSyncAction1(data) {
this.dispatch({
type: SOME_SYNC_ACTION1,
payload: data,
})
}
someSyncAction2(data) {
this.dispatch({
type: SOME_SYNC_ACTION2,
payload: data,
})
}
async someAsyncAction(data1, data2) {
this.someSyncAction1(data1);
await somethingAsync();
this.someSyncAction2(data2);
}
}
// then in my react component:
function MyComponent() {
const dispatch = useDispatch();
const actions = new Actions(dispatch);
//...
return <div onClick={() => actions.someAsyncAction(1, 2)}></div>;
}
It seems to be a simple way but I worry whether it can lead to errors. Please help me to understand what is wrong with it.
This is not very different from the useActions hook referred to in the Hooks documentation - at least for the synchronous stuff.
In the async stuff, you are losing functionality though: Thunks can at any given time access the current state by calling getState.
Also, and this is probably more important: thunks are not only recommended, they are a pattern almost every redux developer knows. So they look at your code and can immediately go to work. Your pattern on the other hand is not established, so it will lead to conflicts if someone other will ever take over your code - without any real benefit.

How to apply middleware selectively to reducers?

With reduxjs-toolkit I define my store and middleware something like this:
const store = configureStore({
reducer: rootReducer,
middleware: [reduxWebsocketMiddleware, adamBrownMiddleware]
});
However I only want to apply this middleware to certain reducers, not all. Specificially I want to apply websocket middleware to certain reducers, whereas in others I'm calling an API and this websocket middleware is not necessary.
It seems like adding another store to do this is an anti-pattern.
So how can I apply middleware only for certain reducers?
You can always, in your middleware, check the action.type property (for example if it begins with "mySliceName/") and otherwise skip the rest of the middleware.
As you don't really give an example what your middleware is doing and why you want to limit it, it's not really possible to give you any more input than that.
This could look like this:
const middleware = api => next => action => {
if (!action.type.startsWith("myWebsocketSlice/") { return next(action); }
// normal middleware code here
}

Resources