Redux Toolkit in TypeScript: createAction abstraction of adding timestamp - reactjs

Using Redux Toolkit with TypeScript, I want to be able to add a timestamp property to all payloads using a prepare callback on createAction() and maintain good type definitions. Currently I do this with a lot of repeated code like:
const timestampAdder = (unpreparedPayload) => {
const prepared = { payload: unpreparedPayload };
if (!unpreparedPayload.timestamp) {
prepared.payload.timestamp = Date.now();
}
return prepared;
};
export const someActionCreator = createAction(
'slice_name/action_name',
(
prePayload: Omit<MyPayloadInterface, 'timestamp'>
): { payload: MyPayloadInterface; } => timestampAdder(prePayload)
);
export const someActionCreator2 = createAction(
'slice_name/action2_name',
(
prePayload: Omit<EntirelyDifferentInterface, 'timestamp'>
): { payload: EntirelyDifferentInterface; } => timestampAdder(prePayload)
);
// ...repeat the same structure for each action I want to add "timestamp" to...
If this was plain JS (or less strict TS) I could do something like:
export const jsActionCreator1 = createAction('js/action_name1', timestampAdder);
export const jsActionCreator2 = createAction('js/action_name2', timestampAdder);
But because the interfaces for the various action creators in TS are different, I end up writing a lot of boilerplate for each to maintain good types. I could do the same in TS:
export const myActionCreator = createAction('slice_name/action3_name', timestampAdder);
But then I lose the benefit of types, and I can invoke the action creator with any argument:
myActionCreator(123);
myActionCreator('abc');
myActionCreator({ a: 1, b: 'c' });
Is there a good way to reduce boilerplate but maintain good types? Thanks!

Just do something like
const timestampAdder = <T>() => (unpreparedPayload: T) => {
const payload = Object.assign( { timestamp: Date.now() }, unpreparedPayload )
return { payload };
};
and use it like
export const actionCreator1 = createAction('js/action_name1', timestampAdder<{foo: string}>());
export const actionCreator2 = createAction('js/action_name2', timestampAdder<{bar: number}>());
Essentially, you have one method call wrapped around, just to "bind" the type there.

Related

Writing a TypeScript Interface for React Context, how to describe the interface/ different types to be passed as values?

I am trying to write a React Context for my application
This is a follow up from my previous question:
How to pass value to Context Provider in TypeScript?
I would please like some help describing different value types to be passed through the provider.
Overview:
I am currently trying to construct a context provider that can be used across the scope of my application in TypeScript.
It contains some useState hooks and and an asynchronous function to be passed through as values the provider to all the child components.
ResultConextProvider.tsx
export const ResultContextProvider = () => {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [greenStatus, setGreenStatus] =
useState(new Set<MyEnum>());
const [redStatus, setRedStatus] =
useState(new Set<MyEnum>());
const [searchTerm, setSearchTerm] = useState<string>('')
// objects to be passed values
const greenValue = {greenStatus, setGreenStatus};
const redValue = {redStatus, setRedStatus};
const searchValue = {searchTerm, setSearchTerm};
// api function coming from tested API spec (external)
const getResults = async () => {
setIsLoading(true)
myAPI.myGet(greenStatus, redStatus).then((result) => {
setResults(result.data);
})
setIsLoading(false)
}
return (
<ResultContext.Provider value={{getResults, greenValue, redValue, searchValue}}>
{children}
</ResultContext.Provider>
}
export const useResultContext = () => useContext(ResultContext);
As you can see above, I would like to pass the getResults function, my greenValues, redValus and searchValues to all my child components, the Provider implentation will look something like this:
index.tsx
import { ResultContextProvider } from "./contexts/ResultContextProvider";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ResultContextProvider
<Router>
<App />
</Router>
</ResultContextProvider>
);
When it comes to writing the interface, I am struggling to understand what needs to be represented prior to my component.
So far my interface looks something like this:
ResultConextProvider.tsx
interface ContextParametersType {
greenValue: { greenStatus: Set<MyEnum>, setGreenStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
redValue: { redStatus: Set<MyEnum>, setRedStatus:
Dispatch<SetStateAction<Set<MyEnum>>> };
///INCORRECT //
// searchValue: {searchTerm: <string>(''), setSearchTerm:
Dispatch<SetStateAction<string> };
///UNSURE
// getResults : {
// What to write as interface for getResults
// }
}
I have worked out what needs to be declared for my greenValue and redValue, based on the answer to my previous question however struggling to fill out the rest of the interface specification
The Search Term:
///INCORRECT //
// searchValue: {searchTerm: (''), setSearchTerm:
Dispatch<SetStateAction };
I have tried to follow the pattern of the Enum state, particularly in declaring the state as type string and rerferecning the Dispatch<SetStateAction as the same type to change the state however this produces error
getResults
This is an asynchronous function with an Axios function inside, to be truthful i do not know where to begin within this one.
My confusion on these two steps persists when creating the ResultContext
const ResultContext = createContext<ContextParametersType>({
greenValue: {
greenStatus: new Set<FirmStatusEnum>(), setGreenStatus: () => {
}
},
redValue: {
redStatus: new Set<FirmStatusEnum>(), setRedStatus: () => {
}
},
// searchValue: {
// searchTerm: <string>(''), setSearchTerm: () => {
// }
// },
getResults()
// Not Sure how to initalsie this?
});
Does this make sense? Am I understanding the issues here correctly?
I would really like some help in understanding how to configure the context interfaces for this.
It is essentially a typescript adaptation of this tutorial
Here:
Thanks, please let me know if I need to provide clarity

How do you access query arguments in getSelectors() when using createEntityAdapter with RTK Query

I've been following along the REDUX essentials guide and I'm at part 8, combining RTK Query with the createEntityAdapter. I'm using the guide to implement it in a personal project where my getUni endpoint has an argument named country, as you can see from the code snippet below.
I'm wondering is there anyway to access the country argument value from the state in universityAdaptor.getSelector(state => ) at the bottom of the snippet, as the query key name keeps changing.
import {
createEntityAdapter,
createSelector,
nanoid
} from "#reduxjs/toolkit";
import {
apiSlice
} from "../api/apiSlice";
const universityAdapter = createEntityAdapter({})
const initialState = universityAdapter.getInitialState();
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUni: builder.query({
query: country => ({
url: `http://universities.hipolabs.com/search?country=${country}`,
}),
transformResponse: responseData => {
let resConvert = responseData.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(each => {
return { ...each,
id: nanoid()
}
});
return universityAdapter.setAll(initialState, resConvert)
}
})
})
});
export const {
useGetUniQuery
} = extendedApiSlice;
export const {
selectAll: getAllUniversity
} = universityAdapter.getSelectors(state => {
return Object.keys({ ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }).length === 0
? initialState : { ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }
})
UPDATE: I got it working with a turnery operator due to the multiple redux Actions created when RTK Query handles fetching. Wondering if this is best practice as I still haven't figured out how to access the country argument.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
return !Object.values(state.api.queries)[0]
? initialState : Object.values(state.api.queries)[0].status !== 'fulfilled'
? initialState : Object.values(state.api.queries)[0].data
})
I wrote that "Essentials" tutorial :)
I'm actually a bit confused what your question is - can you clarify what specifically you're trying to do?
That said, I'll try to offer some hopefully relevant info.
First, you don't need to manually call someEndpoint.select() most of the time - instead, call const { data } = useGetThingQuery("someArg"), and RTKQ will fetch and return it. You only need to call someEndpoint.select() if you're manually constructing a selector for use elsewhere.
Second, if you are manually trying to construct a selector, keep in mind that the point of someEndpoint.select() is to construct "a selector that gives you back the entire cache entry for that cache key". What you usually want from that cache entry is just the received value, which is stored as cacheEntry.data, and in this case that will contain the normalized { ids : [], entities: {} } lookup table you returned from transformResponse().
Notionally, you might be able to do something like this:
const selectNormalizedPokemonData = someApi.endpoints.getAllPokemon.select();
// These selectors expect the entity state as an arg,
// not the entire Redux root state:
// https://redux-toolkit.js.org/api/createEntityAdapter#selector-functions
const localizedPokemonSelectors = pokemonAdapter.getSelectors();
const selectPokemonEntryById = createSelector(
selectNormalizedPokemonData ,
(state, pokemonId) => pokemonId,
(pokemonData, pokemonId) => {
return localizedPokemonSelectors.selectById(pokemonData, pokemonId);
}
)
Some more info that may help see what's happening with the code in the Essentials tutorial, background - getLists endpoint takes 1 parameter, select in the service:
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId) : [];
};
And my selector in the slice:
export const selectAllLists = createSelector(getListsResult, (listsResult) => {
console.log('inside of selectAllLists selector = ', listsResult);
return listsResult.data;
// return useSelector(listsResult) ?? [];
});
Now this console logs listsResult as ƒ memoized() { function! Not something that can have .data property as tutorial suggests. Additionally return useSelector(listsResult) - makes it work, by executing the memoized function.
This is how far I got, but from what I understand, the code in the Essentials tutorial does not work as it is...
However going here https://codesandbox.io/s/distracted-chandrasekhar-r4mcn1?file=/src/features/users/usersSlice.js and adding same console log:
const selectUsersData = createSelector(selectUsersResult, (usersResult) => {
console.log("usersResult", usersResult);
return usersResult.data;
});
Shows it is not returning a memorised function, but an object with data on it instead.
Wonder if the difference happening because I have a parameter on my endpoint...
select returns a memoized curry function. Thus, call it with first with corresponding arg aka tribeId in your case and then with state. This will give you the result object back for corresponding chained selectors.
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId)(state) : [];
};
The intention of the getUni endpoint was to produce an array of university data. To implement the .getSelector function to retrieve that array, I looped over all query values, searching for a getUni query and ensuring they were fulfilled. The bottom turnery operator confirms the getUni endpoint was fired at least once otherwise, it returns the initialState value.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
let newObj = {};
for (const value of Object.values(state.api.queries)) {
if (value?.endpointName === 'getUni' && value?.status === 'fulfilled') {
newObj = value.data;
}
}
return !Object.values(newObj)[0] ? initialState : newObj;
})

Best approach to make non component functions update redux state without passing store.dispatch() as a parameter

So I'm creating my first ReactJS/redux application and I need a little guidance.
I've created a generic apiFetch<T>(method, params) : Promise<T> function which lives in api/apiClient.ts. (Not a React component, but called indirectly from React components)
Basically every fetchEmployee/fetchSettings/fetchWhatever method in rpc/rpcMethods.ts calls this apiFetch<T>() function.
What I'd like to achieve is a statusbar in my app which shows how many concurrent api calls are active. I therefore created a redux rpcStatusSlice based on this redux example.
Can I make apiFetch<T>() update the slice without passing the UseAppDispatch() result as a parameter from my React components?
If I directly import the store in apiClient.ts and call the state modifying functions from rpcStatusSlice on it I get this error:
Uncaught ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
at Module.default (bundle.js:1444:42)
at Module../src/store/store.ts (bundle.js:1957:67)
at Module.options.factory (bundle.js:90091:31)
at __webpack_require__ (bundle.js:89541:33)
at fn (bundle.js:89762:21)
at Module../src/api/apiClient.ts (bundle.js:206:70)
at Module.options.factory (bundle.js:90091:31)
at __webpack_require__ (bundle.js:89541:33)
at fn (bundle.js:89762:21)
at Module../src/api/rpcMethods.ts (bundle.js:288:68)
apiFetch.ts:
import { store } from "../store/store";
import { incrementByAmount } from "../store/features/rpcStatusSlice";
export function apiFetch<T>(method: string, params: any): Promise<T> {
store.dispatch(incrementByAmount(1));
return fetch(apiUrl, {
method: "POST",
cache: "no-cache",
mode: "cors",
redirect: "follow",
body: JSON.stringify(getApiRequest(method, params)),
})
.then(etc)
./store/features/rpcStatusSlice.ts
import { createSlice, PayloadAction } from '#reduxjs/toolkit';
import { RootState } from '../store';
export interface ActiveRequest{
requestType: string;
}
export interface RpcStatus {
activeRequestsCount: 0;
activeRequests: ActiveRequest[];
}
export interface RpcStatusState {
value: RpcStatus;
status: 'idle' | 'loading' | 'failed';
}
const initialState: RpcStatusState = {
value: {
activeRequestsCount: 0,
activeRequests: []
},
status: 'idle',
};
export const rpcStatusSlice = createSlice({
name: 'rpcstatus',
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value.activeRequestsCount += 1;
},
decrement: (state) => {
state.value.activeRequestsCount -= 1;
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value.activeRequestsCount += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = rpcStatusSlice.actions;
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.rpcstatus.value)`
export const selectCount = (state: RootState) => state.rpcStatus.value.activeRequestsCount;
export default rpcStatusSlice.reducer;
./store/store.ts
import { configureStore, ThunkAction, Action } from '#reduxjs/toolkit';
import rpcStatusReducer from './features/rpcStatusSlice';
export const store = configureStore({
reducer: {
rpcStatus: rpcStatusReducer
},
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
Can I make apiFetch() update the slice without passing the UseAppDispatch() result as a parameter from my React components?
This sounds like you want apiFetch() to do two things:
Make an HTTP request.
Update redux state.
This violates the separation of concerns principle. Instead, call apiFetch() from a thunk that then updates redux state.
If your StatusBar component is the only consumer of the number of concurrent api calls, I'd consider not even using redux for this. This counter is not necessarily even global state, I'd see it as volatile, local component state for the StatusBar. Therefore, the solution I'm proposing relies only on React. It takes advantage of the pattern of keeping state in the module scope of the apiClient. At runtime, your apiClient module exists as a singleton with its own scope (many modules importing the apiClient will always use the same instance).
This abstracts the task of counting api calls away and lets the other parts of the codebase use apiFetch() without worrying about the counter. No need to adjust your existing thunks etc..
// apiClient.ts
let incrementCounter = (): void => null;
let decrementCounter = (): void => null;
export const onRequestStart = (callback: () => void) => {
incrementCounter = callback;
};
export const onRequestEnd = (callback: () => void) => {
decrementCounter = callback;
};
export const apiFetch = (method, params) => {
incrementCounter();
// do actual api call
return someKindOfPromise.finally(decrementCounter);
};
// StatusBar.tsx (component showing the number of concurrent api calls)
import { onRequestStart, onRequestEnd } from 'apiClient.ts';
const StatusBar = () => {
const [numConcurrentCalls, setNumConcurrentCalls] = useState(0);
useEffect(() => {
// Tell api client to always call the following function when a request starts.
onRequestStart(() => setNumConcurrentCalls(num => num + 1));
// Tell api client to always call the following function when a request ends.
onRequestEnd(() => setNumConcurrentCalls(num => num - 1));
}, [setNumConcurrentApiCalls]);
return (
<div>
<p>Concurrent api calls: {numConcurrentCalls}</p>
</div>
);
};

Is separating data retrieval code into interfaces and impl. of those interfaces a good idea in React?

My question is: is the below pattern a good idea in React or no? I come from Java world where this type of code is standard. However, I've ran into several things that, while being a good idea in Java, are NOT a good idea in ReactJS. So I want to make sure that this type of code structure does not have weird memory leaks or hidden side-effects in the react world.
Some notes on below code: I'm only putting everything in the same file for brevity purposes. In real life, the react component the interface and the class would all be in their own source files.
What I'm trying to do: 1) Separate the display logic from data access logic so that my display classes are not married to a specific implementation of talking to a database. 2) Separating DAO stuff into interface + class so that I can later use a different type of database by replacing the class implementaton of the same DAO and won't need to touch much of the rest of the code.
so, A) Is this a good idea in React? B) What sort of things should I watch out for with this type of design? and C) Are there better patterns in React for this that I'm not aware of?
Thanks!
import { useState, useEffect } from 'react';
interface Dao {
getThing: (id: string) => Promise<string>
}
class DaoSpecificImpl implements Dao {
tableName: string;
constructor(tableName: string) {
this.tableName = tableName;
}
getThing = async (id: string) => {
// use a specific database like firebase to
// get data from tabled called tablename
return "herp";
}
}
const dao: Dao = new DaoSpecificImpl("thingies");
const Display: React.FC = () => {
const [thing, setThing] = useState("derp");
useEffect(() => {
dao.getThing("123").then((newThing) =>
setThing(newThing));
});
return (
<div>{thing}</div>
)
}
export default Display;
https://codesandbox.io/s/competent-taussig-g948n?file=/src/App.tsx
The DaoSpecificImpl approach works however I would change your component to use a React hook:
export const useDAO = (initialId = "123") => {
const [thing, setThing] = useState("derp");
const [id, setId] = useState(initialId);
useEffect(() => {
const fetchThing = async () => {
try{
const data = await dao.getThing(id);
setThing(data);
}catch(e){
// Handle errors...
}
}
fetchThing();
}, [id]);
return {thing, setId};
}
using the hook in your component:
const Display = () => {
const {thing, setId} = useDao("123"); // If you don't specify initialId it'll be "123"
return <button onClick={() => setId("234")}>{thing}</button> // Pressing the button will update "thing"
}
Side note: You could also use a HOC:
const withDAO = (WrappedComponent, initialId = "123") => {
.... data logic...
return (props) => <WrappedComponent {...props} thing={thing} setId={setId}/>
};
export default withDAO;
E.g. using the HOC to wrap a component:
export default withDao(Display); // If you don't specify initialId it'll be "123"

How to create a modified useSelector custom hook with TypeScript

I want to create custom hook to remove a lot of boilerplate from reusable code.
My redux setup involves a bunch of combined reducers so getting redux values using useSelector from react-redux entails quite a lot of boilerplate code.
Let's say I have an admin reducer inside my rootReducer. I could get one of it's values as follows:
const MyComponent: FC = () => {
const value = useSelector<IRootReducer, boolean>(
({ admin: { val1 } }) => val1
)
// rest of code
}
I want to create a custom hook useAdminSelector based off the implementation above so that it can be used as follows:
const value = useAdminSelector<boolean>(({ val1 }) => val1)
In the definition of the implementation I'd like to have my IAdminReducer interface implemented too.
Here's my attempt:
export function useApplicantSelector<T>((applicantState: IApplicant): T => (retVal)): T {
return useSelector<IReducer, T>((state) => (state.applicant))
}
But this solution is obviously syntactically incorrect.
How about
export const useAdminSelector = <T>(adminSelector: (adminState: IAdminReducer) => T) => {
return useSelector((state: IRootReducer) => adminSelector(state.admin))
}
and then
const value = useAdminSelector<boolean>(({ booleanVal }) => val1)
If I understand you correctly, I think this should solve your problems:)
You don't need a custom hood. Read more about reselect or similar.
If you don't want any libraries - just write your custom select function
const selectVal1 = ({ admin }: IRootReducer) => admin.val1;
and use it
const val1 = useSelector(selectVal1);
UPDATE
What about this?.
[JS]
const useAdminState = (key) => useSelect((state) => state.admin[key]);
[TS]
const useAdminState = (key: keyof IRootReducer['admin']) => useSelect(({ admin }: IRootReducer) => admin[key]);

Resources