RTKex
An extension for Redux Toolkit
with NPM
npm i rtkex --save
with YARN
yarn add rtkex
RTKex provides a new configreStore(), it accepts buildCallback that retrieves a builder object as its argument. The builder object provides methods for registering slice, reducer, middleware, etc.
import { configureStore } from "rtkex";
const store = configureStore((builder) =>
builder
.withSlice(slice1)
.withSlice(slice2)
.withMiddleware(middleware1, middleware2)
.withPreMiddleware(middleware1, middleware2)
.withListener(actionCreator, effect)
.withListener({ type: "action_type" }, effect)
.withReducer(reducer1)
.withReducer(reducer2)
.withDevTools(enabled) // or .withDevTools(devToolsOptions)
.withEnhancers(enhancer1, enhancer2)
);
RTKex has new implementation of createSlice, it retrieves following parameters createSlice(name, initialState, reducers, options). RTKex also provides some extra properties for slice object
import { createSlice, configureStore } from "rtkex";
const counterSlice = createSlice(
// slide name, where to put data in the app state tree
"count",
// initial state
1,
// main reducers
{
increment: (state) => state + 1,
decrement: (state) => state - 1,
},
// options
{
extraReducers: (builder) => {},
}
);
// register the slice to the store with ease
const store = configureStore((builder) => builder.withSlice(counterSlice));
// with RTK you should do
// configureStore({ reducer: { count: counterSlice.reducer } })
// now the store has following state tree { count: 1 }
The slice has built-in selector slice.select() function, that uses to select the state of slice from app state tree. You also pass custom selector to select() function
import { useSelector } from "rtkex";
const count1 = counterSlice.select(store.getState());
const count2 = useSelector(counterSlice);
const count3 = useSelector(counterSlice.select);
// using custom selector with default selector
const doubleCount = useSelector(counterSlice.select((count) => count * 2));
A slice can depend on one or many other slices. When configuring the store, you just need to add dependent slices, all their dependencies will be added as well
import { createSlice, configureStore } from "rtkex";
const slice1 = createSlice("slice1", 0, {});
const slice2 = createSlice("slice2", 0, {})
// add dependency slice when the store is building
.onBuild((builder) => builder.withSlide(slide1));
// no need to add slice1
configureStore((builder) => builder.withSlice(slice2));
// create a store wihout any slice
const store = configureStore();
// counter.js
import { useBuilder, useSelector } from "rtkex";
import counterSlice from "./slices/counterSlice";
const withCounterSlice = (builder) =>
function Counter() {
// easy ?
useBuilder((builder) => builder.withSlice(counterSlice));
// use the slice afterward
const count = useSelector(counterSlice);
return <div>{count}</div>;
};
Slice Ready Event uses to handle something whenever the slice added to the store
const mySlice = createSlice().onReady((storeApi, slice) => {
// dispatch an action
storeApi.dispatch(action);
// get the current store state
storeApi.getState();
});
Redux toolkit supports createAsyncThunk but it is complicated to use. RTKex wraps slice and thunk logics into one place, it is loadable slice
import { createLoadableSlice, configureStore, useSelector } from "rtkex";
import { userAPI } from "./userAPI";
const userListSlice = createLoadableSlice(
"users",
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId);
return response.data;
}
);
const store = configureStore((builder) => builder.withSlide(userListSlice));
// load users outside component
store.dispatch(userListSlice.actions.load(123));
// load users inside component
const dispatch = useDispatch();
useEffect(() => {
dispatch(userListSlice.actions.load(123));
}, [dispatch]);
// load users once when the slice is added to the store
const userListSlice = createLoadableSlice(/* ... */).onReady(
(storeApi, slice) => {
storeApi.dispatch(slice.actions.load(123));
}
);
const userList = useSelector(userListSlice);
console.log(userList);
/*
loadable object has following properties
{
data: [...],
loading: false,
idle: false,
loaded: true,
error: undefined
}
*/
RTKex also supports Suspense and error boundary for loadable slice
const UserList = () => {
// when using selectData selector RTKex will throw a promise if slice is still loading and throw an error if slice has been failed
const userList = useSelector(userListSlice.selectData);
// the userList value is loadable.data not loadable object
console.log(userList); // [...]
};
<Suspense fallback="Loading...">
<UserList />
</Suspense>;
If you need to add more actions for loadable slice, just use following code:
const userListSlice = createLoadableSlice(
"users",
async (userId: number, thunkAPI) => {
const response = await userAPI.fetchById(userId);
return response.data;
},
// options
{
reducers: {
// clear user list action
// the state is loadable.data
clear: (state) => [],
},
// you also define extraReducers to handler external actions
extraReducers: (builder) =>
// clear user list when logout action is dispatched
builder.addCase(logoutAction, (state) => []),
}
);
RTKex slice can work with HOR ease
import undoable from "redux-undo";
import { createSlice } from "rtkex";
const counterSlice = createSlice().wrap(undoable);
Generated using TypeDoc