replaceReducer on #reduxjs/toolkit - redux-toolkit

Although I'm newbie of redux as well as of RTK, I'm just starting redux project with RTK as if you start with Spring Boot not Spring these days.
I want to dynamically inject reducers on demand on redux toolkit(RTK). I realized that I need to keep track of current reducers to make it. I expected my store from RTK would have a reference to them, but unfortunately it seems it doesn't have such a property.
While I found this module that seems to do the job, but it seems I have to go back to the days before RTK was created to make it work.
import {createStore, createReducer, Slice, Reducer, AnyAction, combineReducers} from "#reduxjs/toolkit";
const Store = createStore<any, any, any, any>(createReducer({}, () => {}));
const reducers: {
[key: string]: Reducer<any, AnyAction>;
} = {};
export const injectReducer = (slice: Slice) => {
reducers[slice.name] = slice.reducer;
Store.replaceReducer(combineReducers(reducers));
};
Even more (maybe I just don't know the way) type definitions will go insane.
Is there any way to make this?

I had the same issue, and finally managed to fix it with a few lines;
First, I created the store:
const staticReducers = {
counter: counterReducerSlice,
};
export const store = configureStore({
reducer: staticReducers,
middleware: getDefaultMiddleware => [logger, ...getDefaultMiddleware()],
enhancers: compose([monitorReducerEnhancer]),
});
then making the async reducers directory:
store.asyncReducers = {};
store.injectReducer = (key, asyncReducer) => {
store.asyncReducers[key] = asyncReducer;
store.replaceReducer(createReducer(store.asyncReducers));
};
function createReducer(asyncReducers) {
return combineReducers({
...staticReducers,
...asyncReducers
});
}
finally, in my code, every time I want to add a new reducer to my store, I call store.injectReducer:
store.injectReducer('reducerKey', theReducerSlice);

Related

Redux toolkit serializableMiddleware doesn't work

I encountered with an issue of serializableMiddleware in redux toolkit. I need to store firestore timestamps inside my redux store and don't want that serializableMiddleware logged this text:
A non-serializable value was detected in the state, in the path: path to timestamp. Value: nt {seconds: > 1675422816, nanoseconds: 106000000}
Take a look at the reducer(s) handling this action.
So i take a look at the serializableMiddleware in redux toolkit docs and find out that there is an option isSerializable?: (value: any) => boolean. When I return true from this method, value should consider serializable, but still logs error out even if I always return true.
I wrote function but it didn't work:
const isSerializable = (value: any) =>
value instanceof Timestamp || isPlain(value);
const serializableMiddleware = createSerializableStateInvariantMiddleware({
isSerializable
})
Here is how I setup my store:
export const setupStore = () => {
return configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(serializableMiddleware)
})
}
The problem here is that getDefaultMiddleware() already adds the serializable middleware as part of the default setup. You're creating a second instance of the middleware, but that doesn't change the first instance.
Instead, you need to customize the first instance by passing the right options to getDefaultMiddleware():
export const setupStore = () => {
return configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
isSerializable: myCustomIsSerializableFunction
}
})
})
}

How to import type definition from react-query to set type of options for useQuery hook?

I'm adding react-query to my automations pipe and need generating wrapper around the useQuery for various API calls. I want to expose all options of useQuery to developer using this wrapper. How to correctly define options type to get all the benefits of ts for those?
:any works but not giving autocomplete/checks then:
const usePosts = ({ page, perPage, workspaceId }: payload, ___options?: any) => {
If I add :UseQueryOptions, just to see the error of mismatch, error message is quite big and has a lot inside. Would love to find way to import that type definition from react-query and reuse it.
Just found it, sometimes all needed is to write a question 😅 Maybe someone else will benefit from this:
import { useQuery, QueryOptions } from "#tanstack/react-query";
import sdk from "../sdks/demo";
type payload = {
page?: number;
perPage?: number;
workspaceId: string;
};
const usePosts = (
{ page, perPage, workspaceId }: payload,
___options?: QueryOptions
) => {
let key = `posts-list-${workspaceId}-${page}-${perPage}`;
return useQuery(
[key],
() =>
sdk.postsList({
workspaceId,
page,
perPage,
}),
___options
);
};
export default usePosts;

Suspense returns data too fast in #tanstack/react-query v4

I am upgrading a React app from react-query v3 to #tanstack/react-query v4.
Almost everything works, but I'm having a problem with Suspense.
I have a react component:
const WrapperPageEdit: React.FC<MyProps> = ({
pageUuid,
redirect,
}: MyProps) => {
const FormPage = React.lazy(() => import('./FormPage'));
const { data } = usePageView(pageUuid);
if (data?.[0]) {
const pageObjectToEdit= data[0];
const content = pageObjectToEdit.myStuff.content;
return (
<Suspense
fallback={<Trans id="loading.editor">Loading the editor...</Trans>}
>
<FormPage
id={uuid}
content={content}
redirect={redirect}
/>
</Suspense>
);
}
return <p>No data.</p>;
};
And here's my query:
export function usePageView(
uuid: string,
): UseQueryResult<DrupalPage[], Error> {
return useQuery<DrupalPage[], Error>(
queryKeyUsePageView(uuid),
async () => {
return fetchAnon(getPageByPageUuid(uuid));
},
{
cacheTime: YEAR_MILLISECONDS,
staleTime: YEAR_MILLISECONDS,
onSuccess: (data) => {
if (data?.[0]) {
data.map((element) => processResult(element));
}
},
},
);
}
This works in v3 but fails in v4 with the following error:
TypeError: Cannot read properties of undefined (reading 'content')
The reason the property is undefined is because that property is set by the processing in onSuccess (data.map).
The issue appears to be that in v4, the component WrapperPageEdit is refreshed before onSuccess in the usePageView query has finished processing, whereas in v3, the component WrapperPageEdit is not refreshed until the onSuccess data.map is complete.
How can I correctly fix this? I can write some additional code to try to check whether the onSuccess data.map is complete, but since react-query handled this automatically in v3, I'd like to rewrite my code in v4 so that it is the same.
The problem is likely that you are mutating the data in onSuccess. Directly modifying data in callbacks is not a good idea. Instead, do your transformation for example directly in the queryFn:
async () => {
const data = fetchAnon(getPageByPageUuid(uuid));
if (data?.[0]) {
data.map((element) => processResult(element));
}
return data
},
other good places to do data transformation is e.g. the select option, but it should always happen in an immutable way, because otherwise, you are overwriting the cached data inadvertently. React prefers updates to be immutable.

Using variables or constants in nextjs api

I'm trying to use a constant in my API. This constant is an array where I can push or pop objects. I populate it through API calls. This is the code:
const washes = [];
export default async (req, res) => {
if(req.method === 'GET')
{
res.json({washes});
}
if(req.method === 'POST')
{
washes.push(req.body);
console.log(washes);
res.json({washes});
}
}
I make calls from the front-end to populate the array and to retrieve it. The problem is that, sometimes I get an empty array an sometimes I get the expected array. Or after a while I always get the empty array, like if it was restarted.
I deployed the application on Vercel.
Is there something I'm missing from the back-end functionality, or is it related to Vercel?. Last but not least is it a good practice to use variables or constants in back-end.?
You cant use an array as storage. You need to connect it to a database of sorts.
When you use this endpoint it will only retrieve the correct array sometimes, because it's stored in a temporary memory on the server.
Look into firebase if you easily want to setup up a project and store information in a database.
https://firebase.google.com/docs/web/setup
Another way is to use a .json file in the back-end
Read more here: https://jasonwatmore.com/post/2021/08/28/next-js-read-write-data-to-json-files-as-the-database
USER EXAMPLE
const fs = require('fs');
// users in JSON file for simplicity, store in a db for
production applications
let users = require('data/users.json');
export const usersRepo = {
getAll: () => users,
getById: id => users.find(x => x.id.toString() === id.toString()),
find: x => users.find(x),
create,
update,
delete: _delete
};
function create(user) {
// generate new user id
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
// set date created and updated
user.dateCreated = new Date().toISOString();
user.dateUpdated = new Date().toISOString();
// add and save user
users.push(user);
saveData();
}
function update(id, params) {
const user = users.find(x => x.id.toString() === id.toString());
// set date updated
user.dateUpdated = new Date().toISOString();
// update and save
Object.assign(user, params);
saveData();
}
// prefixed with underscore '_' because 'delete' is a reserved word in javascript
function _delete(id) {
// filter out deleted user and save
users = users.filter(x => x.id.toString() !== id.toString());
saveData();
}
// private helper functions
function saveData() {
fs.writeFileSync('data/users.json', JSON.stringify(users, null, 4));
}

React-Query: Is there an alternative way to update useQuery staleTime besides setQueryDefaults?

I'm trying to do this:
function useReactiveStaleTimeQuery() {
const [staleTime, setStaleTime] = useState(1000);
const { data } = useQuery(queryKey, queryFn, { staleTime, notifyOnChangeProps: ["tracked"] })
}
But setQueryDefaults feels weird:
function useReactiveStaleTimeQuery() {
const queryClient = useQueryClient();
const [staleTime, setStaleTime] = useState(1000);
useEffect(() => queryClient.setQueryDefaults(queryKey, { staleTime }), [staleTime]);
const { data } = useQuery(queryKey, queryFn, { notifyOnChangeProps: ["data"]});
return data;
}
It's possible that only the queryKey and/or queryFn is watched by useQuery but I'm not entirely sure (don't have time to go through the source code) but what I don't understand is how is it possible that the enable option is watched but staleTime is ignored. Docs don't say anything about using staleTime as a thunk like initialDataUpdatedAt.
but what I don't understand is how is it possible that the enable option is watched but staleTime is ignored.
so this usually works:
function useReactiveStaleTimeQuery() {
const queryClient = useQueryClient();
const [staleTime, setStaleTime] = useState(1000);
const { data } = useQuery(
queryKey,
queryFn,
{ staleTime }
)
return [data, setStaleTime];
}
Once you update the staleTime, the query will notice it. However, unlike enabled (which works on a per-observer level), there is only one query in the cache, so there can only be one staleTime.
This is important if you have two observers (use the custom hook twice). You can have one enabled and one disabled query, but you cannot have staleTime: 1000 and staleTime: 500 on the same query. As far as I know, the shorter one "wins".
So yeah, controlling the staleTime with state works, but I think it would be better to store it globally and not with local state on a per hook level.