-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
This is not an issue but more of a question regarding understanding redux design philosophy (and so I did not ask on SF).
Say, I have a store slice for different animals:
interface AnimalState {
dogs: Dog[];
cats: Cat[];
...
}
const initialState: AnimalState = {
dogs: [],
cats: []
}
const animalsSlice = createSlice({
name: 'animals',
initialState,
reducers: {/* ... */},
extraReducers: builder => { /* ... */ }
})
A typical async thunk for creating new dog could be:
export const createDog = createAsyncThunk<
Dog,
Omit<Dog, 'id'>,
{ /* ... */ }
>('animals/createDog', async (dogData) => { /* thunk logic */ })
This works great. The thing is, the one for creating new cat (and all other animals) are exactly the same, so I want to make it a little bit more flexible to avoid repeat codes.
The first approach I tried, was to play with the type generic for createAsyncThunk(), where I tried to fit in an union to infer the animal to be created, something like this:
type Kind = 'dog' | 'cat';
type Animal<T extends Kind> = T extends 'dog' ? Dog : Cat;
export const createAnimal = createAsyncThunk<
Animal<K>, // <-- is it possible to define `K` somewhere so the generic can pick it up?
{ kind: K; data: Omit<Animal<K>, 'id'> }, // <-- so animal kind could be inferred from thunkArg...
{ /* ... */ }
>('animals/create', async (animalData) => { /* ... */ })
This obviously won't work, because I can't define K anywhere for the generic... but you get the idea.
The second approach I tried, is to create an async thunk factory, something like:
function getAsyncThunk<K extends Kind>(kind: K) {
return createAsyncThunk<
Animal<K>,
Omit<Animal<K>, 'id'>,
{ /* ... */ }
>(`animals/create${kind}`, (animalData) => { /* ... */ })
}
export const createDog = getAsyncThunk('dog')
export const createCat = getAsyncThunk('cat')
...
The second approach works fine, but still, feels kinda repetitive, and will add more work on the reducer part. I would definitely prefer more on createAnimal('dog') rather than createDog(), createCat()......
Question:
- What is the suggested approach/pattern for this kind of usage?
- Is the idea behind the first approach (abstracting common action creators) an anti-pattern? I.e. one action creator touching multiple slice fields or even multiple state slices should be avoided?