redux-as-repo
v1.6.0
Published
a ready to use reducer and a middleware to manage state with redux with one slice
Downloads
5
Readme
Redux As Repo
This library provides utility functions to deal with redux without the boilerplate.
Usage Examples on CodeSandbox
Installation
npm i redux-as-repo
# using yarn
yarn add redux-as-repo
Usage
import { repoReducer } from 'redux-as-repo';
// in your rootReducer, add the repoReducer
combineReducers({
repository: repoReducer,
});
// or if you are using a reducer registry
reducerRegistry.register('repository', repoReducer);
import { all, fork } from 'react-sagas/effects';
import { createRepoSaga } from 'redux-as-repo';
import { axiosInstance } from 'your/axios/instance';
// in your rootSaga, create repoSaga
const handleResponse = (axiosData) => {
if (...){
return ...
}
return ...
}
const repoSaga = createRepoSaga(axiosInstance, handleResponse);
export default function* rootSaga() {
yield all([fork(repoSaga)]);
}
createRepoSaga
takes 2 arguments:
axiosInstance
which is required- response handler:
optional
(if your backend has fixed format to return data, you can create a handler that returns a portion of response and store it inside the keydata
)
{
"result": [],
"status": "success",
"errorMessage": "...",
"errorCode": "..."
}
if you need just result
, define a response handler and pass it to createRepoSaga
const handleResponse = data => {
if (status === 'success') {
return data.result;
} else throw new Error(data.errorMessage);
};
Throwing an error will cause a FETCH_ERROR
action to be dispatched
Common Action Creators
| actionCreator | args | saga effect | Description |
| ------------------ | --------------- | ------------- | ------------------------------------------------------------------------------------------------- |
| fetchInit
| FetchOptions
| takeEvery
| Every action is handled by the repo reducer |
| fetchLatest
| FetchOptions
| takeLatest
| Only last resolved value will be taken into consideration by the repo reducer |
| fetchFirst
| FetchOptions
| takeLeading
| it blocks all upcoming actions FETCH_FIRST
until the previous action is handled by repo reducer |
| fetchNewInit
| FetchOptions
| takeEvery
| same as fetchInit
but creates a new namespace template for each request |
| fetchClear
| string
| None | No Saga effect, will clear the namespace in question |
| updateRepository
| UpdateOptions
| takeEvery
| update/create new namespace with the resulting of compute
method |
FetchOptions
repository
slice in redux store handled by common action creators to store data.
| Property | Type | required | Description |
| --------------------- | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| url
| string
| Yes | url |
| namespace
| string
| Yes | where to store the dara inside repository |
| config
| AxiosRequestConfig
| | Axios config object (method, data, params) |
| successCb
| Action Creator | | Function that takes response data as an argument and returns an action (data) => ({type:'SOME_ACTION', data})
|
| errorCb
| Action Creator | | same as successCb but will take error as callback argument |
| autoClear
| boolean
| | will clear the namespace after success |
| skipResponseHandler
| boolean
| | if you are using response handler that you want to disable for this specific request , pass this option as true
|
| selector
| Function
| | A selector that returns an object with the desired to be formatted keys: state => ( { projectId: state.projectId })
const url = '/randomLink/{projectId}'
projectId will be replaced therefore by the value coming from the selector |
export function fetchProjects() {
return fetchInit(({
url,
namespace: 'projects'
}));
}
//in your YourComponent
import { useDispatch } from 'react-redux';
import { fetchProjects } from 'path/of/your_redux_actions';
const MyComponent = () => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchProjects())
},[])
return (...)
}
- dispatch action of type : @repo-as-reducer/FETCH_INIT
- an xhr call to url with options provided
- response data will be stored inside repository.projects
data is stored in this format
{
repository: {
projects: {
data: [...],
error: false,
loading: false,
success: true,
trace: null,
}
}
}
updateRepository
Use this common action creator for creating a new namespace or updating an existing namespace.
UpdateOptions
UpdateOptions {
namespace: string;
compute: (namespaceState: undefined | NamespaceState) => NewNamespaceState;
}
Usage
function updateProject(newValue) {
return updateRepository({
namespace: 'projects',
compute: oldNamespaceState => ({
...oldNamespaceState,
key: newValue,
}),
});
}
Selectors
to get stored data by namespace use getData(namespace)
this will create a memoized selector.
// store/YourComponent/index.ts
import { getData, getLoadingState } from 'redux-as-repo';
const dataSelector = getData(namespace);
const loadingStateSelector = getLoadingState(namespace);
// YourComponent/index.tsx
const data = useSelector(dataSelector);
const isLoading = useSelector(loadingStateSelector);
useNamespace as a custom hook
To get namespace data without using selectors, a custom hook is there for you
import { useNamespace } from 'redux-as-repo';
const { data, error, loading } = useNamespace({
namespace: 'PROJECTS',
onSuccess: callback,
autoClear: true,
});
| Property | Type | required | Description |
| ----------- | ---------- | ----------------- | ----------------------------------------------------------------------------- |
| autoClear
| boolean
| default : false
| clear namespace on component cleanup |
| namespace
| string
| Yes | where namespace is saved |
| onSuccess
| callback
| No | will be executed if namespace.success is true, data is passed to the callback |
Query Hooks Generation
To generate hooks ready to be used in the component
createNamespaceApi
| Property | Type | required | Description |
| ----------- | -------- | -------- | ------------------------------------------------------------ |
| namespace
| string
| Yes | base namespace where to store data {namespace}_{queryName}
|
| queries
| Hook
| Yes | object that holds all queries methods |
interface Hook {
[key: string]: {
query: (...args: any[]) => string | HookFetchOptions;
effect?: `${FetchEffect}`;
fetchOnMount?: boolean;
};
}
interface HookFetchOptions {
url: string;
config: AxiosRequestConfig;
}
export enum FetchEffect {
New = 'new',
First = 'first',
Latest = 'latest',
Init = 'init',
}
Generated Hook ReturnType
interface HookResult extends NamespaceState {
namespace: string;
refetch: (...args: any[]) => void;
}
export type NamespaceState = {
data: any;
error: boolean;
loading: boolean;
success: boolean;
trace: null | string;
fullError: null | any;
};
Usage
import { createNamespaceApi, FetchEffect } from 'redux-as-repo';
const hooks = createNamespaceApi({
namespace: 'todos',
queries: {
getTodo: {
query: () => 'todos',
effect: FetchEffect.First,
fetchOnMount: true,
},
getTodoById: {
query: id => `todos/${id}`,
},
addTodo: {
query: data => ({
url: '/todos',
config: {
method: 'post',
data,
},
}),
},
},
});
export const { useGetTodo, useGetTodoById, useAddTodo } = hooks;
// Component.tsx
...
const [state, setState] = useState(false);
const [id, setId] = useState(1);
const { data, loading, error, refetch } = useGetTodo({
autoClear: true,
onSuccess: console.log,
deps: [state],
});
const { refetch: getTodoById } = useGetTodoById(id, {
deps: [id, state],
});
const { refetch: addTodo } = useAddTodo({ key: 'value' });
interface HookOptions {
autoClear?: boolean;
deps: any[];
onSuccess: (data: NamespaceState) => any;
}