From harness-claude
Types Redux Toolkit state, actions, thunks, hooks, and middleware with full TypeScript inference and minimal annotations. Use for setup, fixing type errors in slices, extraReducers, or components.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Type Redux state, actions, thunks, and hooks with full inference and minimal manual annotation
Configures Redux store using configureStore with typed hooks, middleware, and Provider wiring in React apps. For initializing new apps, migrating from createStore, or adding custom middleware.
Guides Redux and Redux Toolkit for global state management: slices, stores, actions, reducers, hooks, selectors, middleware, async thunks.
Guides type-safe Zustand stores in TypeScript: interfaces, typed selectors, getters, async actions, type inference, and best practices.
Share bugs, ideas, or general feedback.
Type Redux state, actions, thunks, and hooks with full inference and minimal manual annotation
useDispatch and useSelectorextraReducers, createAsyncThunk, or middlewareRootState and AppDispatch from the store — never define them manually.useAppDispatch, useAppSelector) and use them everywhere.PayloadAction<T> for reducer parameter types. RTK infers the rest from createSlice.createAsyncThunk with three generic arguments: <ReturnType, ArgType, ThunkApiConfig>.extraReducers, use the builder callback — it provides full type inference for each case.isRejectedWithValue type guard for narrowing rejection payloads.as type assertions in reducers — if you need them, the type definition is wrong.// store/index.ts — inferred types
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// Typed hooks — use throughout the app
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// Typed createAsyncThunk
interface FetchError {
message: string;
code: number;
}
export const fetchUser = createAsyncThunk<
User, // Fulfilled return type
string, // Argument type (userId)
{ state: RootState; rejectValue: FetchError } // ThunkAPI config
>(
'users/fetch',
async (userId, { rejectWithValue }) => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) {
return rejectWithValue({ message: 'Not found', code: res.status });
}
return res.json();
}
);
// extraReducers with full inference
extraReducers: (builder) => {
builder
.addCase(fetchUser.fulfilled, (state, action) => {
// action.payload is User (inferred)
state.currentUser = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
if (action.payload) {
// action.payload is FetchError (from rejectWithValue)
state.error = action.payload.message;
} else {
// action.error is SerializedError (thrown exception)
state.error = action.error.message ?? 'Unknown error';
}
});
},
ThunkAPI config object: Pass { state: RootState; rejectValue: T; dispatch: AppDispatch } as the third generic to createAsyncThunk. This types getState(), rejectWithValue(), and dispatch() inside the payload creator.
Middleware typing: Custom middleware receives MiddlewareAPI<AppDispatch, RootState>:
const logger: Middleware<{}, RootState> = (storeApi) => (next) => (action) => {
console.log('dispatching', action);
return next(action);
};
Entity adapter typing: Pass the entity type to createEntityAdapter<User>(). The adapter methods and selectors are fully typed from this.
Common type errors and fixes:
Property does not exist on type in getState() — add state: RootState to the thunk configArgument not assignable to PayloadAction — check that the action creator's prepare callback return type matchesType instantiation is excessively deep — usually from circular slice imports; break the cycle with a shared types fileCannot use namespace as type — import types with import type to avoid circular dependenciesRTK 2.0 changes: Tuple replaces arrays for middleware. combineSlices provides automatic type inference for lazy-loaded slices. The reducer field in configureStore accepts a combineSlices result directly.
https://redux-toolkit.js.org/usage/usage-with-typescript