zustand-fetching
Advanced tools
Comparing version 2.3.4 to 2.4.1
@@ -8,2 +8,3 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; | ||
cards: {}, | ||
array: [], | ||
})); | ||
@@ -10,0 +11,0 @@ export const useGroupController = leitenGroupRequest(useExampleStore, "cards", async (props) => { |
@@ -24,3 +24,6 @@ import { StoreApi } from "zustand/esm"; | ||
} | ||
export declare const leitenGroupRequest: <Store extends object, P extends DotNestedKeys<Store>, Payload, Result>(store: StoreApi<Store>, path: P extends string ? DotNestedValue<Store, P> extends Record<string, Result> | null ? P : never : never, payloadCreator: (params: ILeitenGroupRequestParams<Payload>) => Promise<Result>, options?: ILeitenGroupRequestOption<Payload, Result> | undefined) => ILeitenGroupRequest<Payload, Result>; | ||
interface ILeitenGroupRequestArrayOption<Payload, Result> extends ILeitenGroupRequestOption<Payload, Result> { | ||
getKey: (value: Result) => string; | ||
} | ||
export declare const leitenGroupRequest: <Store extends object, P extends DotNestedKeys<Store>, Payload, Result>(store: StoreApi<Store>, path: P extends string ? DotNestedValue<Store, P> extends Record<string, Result> | Result[] ? P : never : never, payloadCreator: (params: ILeitenGroupRequestParams<Payload>) => Promise<Result>, options?: (DotNestedValue<Store, P> extends Record<string, Result> ? ILeitenGroupRequestOption<Payload, Result> : ILeitenGroupRequestArrayOption<Payload, Result>) | undefined) => ILeitenGroupRequest<Payload, Result>; | ||
export {}; |
import { produce } from "immer"; | ||
import { set } from "lodash-es"; | ||
import { get, set } from "lodash-es"; | ||
import { shallow } from "zustand/shallow"; | ||
@@ -10,6 +10,36 @@ import { useLeitenRequests } from "../hooks/useLeitenRequest"; | ||
let requests = {}; | ||
const isArray = Array.isArray(get(store.getState(), path)); | ||
const getPathToArrayItem = (key) => { | ||
const source = get(store.getState(), path, []); | ||
const find = source.findIndex((s) => { | ||
var _a; | ||
return ((_a = options === null || options === void 0 ? void 0 : options.getKey) === null || _a === void 0 ? void 0 : _a.call(options, s)) === key; | ||
}); | ||
const index = find !== -1 ? find : source.length; | ||
const withKey = (path + `["${index}"]`); | ||
return { withKey, isNew: find === -1 }; | ||
}; | ||
const add = (key) => { | ||
const state = requests[key]; | ||
if (!state) { | ||
const pathWithKey = (path + `.["${key}"]`); | ||
// const state = requests[key]; | ||
// if (!state) { | ||
let pathWithKey = ""; | ||
let payload = payloadCreator; | ||
if (isArray) { | ||
const before = getPathToArrayItem(key); | ||
pathWithKey = before.withKey; | ||
// eslint-disable-next-line | ||
// @ts-ignore | ||
payload = async (params) => { | ||
const result = await payloadCreator(params); | ||
const after = getPathToArrayItem(key); | ||
if ((before.isNew && after.isNew) || !after.isNew) { | ||
const nextState = produce(store.getState(), (draft) => { | ||
set(draft, after.withKey, result); | ||
}); | ||
store.setState(nextState); | ||
} | ||
}; | ||
} | ||
else { | ||
pathWithKey = (path + `.${key}`); | ||
const nextState = produce(store.getState(), (draft) => { | ||
@@ -20,7 +50,5 @@ var _a; | ||
store.setState(nextState); | ||
requests[key] = leitenRequest(store, pathWithKey, payloadCreator, options); | ||
} | ||
else { | ||
requests[key].clear(); | ||
} | ||
requests[key] = leitenRequest(store, pathWithKey, payload, options); | ||
// } | ||
}; | ||
@@ -31,3 +59,3 @@ const call = (params, options) => { | ||
const payload = { key, params }; | ||
if (request) { | ||
if (request && !isArray) { | ||
request.action(payload, options); | ||
@@ -43,3 +71,3 @@ } | ||
if (key) { | ||
requests[key].clear(); | ||
!isArray && requests[key].clear(); | ||
delete requests[key]; | ||
@@ -46,0 +74,0 @@ } |
{ | ||
"name": "zustand-fetching", | ||
"version": "2.3.4", | ||
"version": "2.4.1", | ||
"private": false, | ||
"description": "Zustand fetching helpers", | ||
"description": "Zustand state manager controllers", | ||
"repository": { | ||
@@ -95,3 +95,3 @@ "type": "git", | ||
"publishConfig": { | ||
"homepage": "https://github.com/Hecmatyar/zustand-fetching" | ||
"homepage": "https://github.com/Hecmatyar/zustand-fecthing" | ||
}, | ||
@@ -104,4 +104,6 @@ "license": "MIT", | ||
"store", | ||
"helpers" | ||
"helpers", | ||
"constrollers", | ||
"lenses" | ||
] | ||
} |
199
README.md
@@ -1,17 +0,28 @@ | ||
# Zustand Fetching Helpers | ||
# Zustand controllers | ||
> Introducing several functions that simplify working with **zustand** and clean up your store from unnecessary actions | ||
> and states. | ||
> Helps to avoid using other state managers to execute requests and allows you to work efficiently with zustand. | ||
The functions described below are _**well-typed**_ and allow working with _**nested**_ objects. Zustand suggests writing | ||
custom [slices](https://github.com/pmndrs/zustand/blob/main/docs/guides/slices-pattern.md) and dividing the store into | ||
several parts. However, in most cases, we need to divide the store into several parts because we add a lot of | ||
unnecessary data, which can visually overload it. | ||
The Zustand Fetching Helpers library provides a set of functions and controllers that facilitate working with Zustand, a | ||
state management library. The functions described below are _**well-typed**_ and allow working with _**nested**_ | ||
objects. While Zustand suggests writing | ||
custom [slices](https://github.com/pmndrs/zustand/blob/main/docs/guides/slices-pattern.md) to divide the store into | ||
several parts, this library aims to simplify common data-related tasks without the need for additional state management | ||
solutions. | ||
I propose several helpers that will take on a significant portion of the typical data work in your store. First of all, | ||
it is easier to see | ||
[Live example on codesandbox](https://codesandbox.io/p/sandbox/bitter-lake-w57ywe?selection=%5B%7B%22endColumn%22%3A44%2C%22endLineNumber%22%3A49%2C%22startColumn%22%3A44%2C%22startLineNumber%22%3A49%7D%5D&file=%2Fsrc%2Frequest%2FRequest.tsx) | ||
to understand what it is and how it works. In most cases, leiten controllers will help lighten the | ||
store and there will be no need to split it into several parts. | ||
To get a better understanding of what this library offers and how it works, you can refer to | ||
the [live example on CodeSandbox](https://codesandbox.io/p/sandbox/bitter-lake-w57ywe?selection=%5B%7B%22endColumn%22%3A44%2C%22endLineNumber%22%3A49%2C%22startColumn%22%3A44%2C%22startLineNumber%22%3A49%7D%5D&file=%2Fsrc%2Frequest%2FRequest.tsx) | ||
. In many cases, the provided controllers will help reduce the complexity of your store, eliminating the need to split | ||
it into multiple parts. | ||
## Installation | ||
You can install the library using npm: | ||
```bash | ||
npm install leiten-zustand | ||
``` | ||
Since "Zustand" translates to "state" in German, we decided to adhere to the same naming strategy and used the word " | ||
leiten" (meaning "lead" and "manage") to denote our controllers. | ||
Common view | ||
@@ -24,31 +35,86 @@ | ||
All actions and states out of your **zustand** | ||
### Small Example | ||
Let's create some fake example: load some data and then change it. | ||
#### Pure zustand | ||
```tsx | ||
const useStore = create<IStore>((set, get) => ({ | ||
loadData: async (id: string) => { | ||
try { | ||
set({ loadingData: true }) | ||
const response = await getData(id); | ||
set({ data: response }) | ||
} catch { | ||
// Todo show error | ||
} finally { | ||
set({ loadingData: false }) | ||
} | ||
}, | ||
loadingData: false, | ||
data: { user: null, cards: [] }, | ||
updateUser: (user: Partial<IUser>) => { | ||
set({ data: { ...get().data, user: { ...get().data?.user, ...user } } }) | ||
}, | ||
removeCard: (cardId: string) => { | ||
const cards = get().data.cards.filter(card => card.id !== cardId); | ||
set({ data: { ...get().data, cards } }) | ||
} | ||
})) | ||
``` | ||
#### With leiten controllers | ||
```tsx | ||
const useStore = create<IStore>(() => ({ | ||
data: { user: null, cards: [] }, | ||
})); | ||
// loadData & loadingData | ||
const useRequest = leitenRequest(useStore, "data", (id: string) => getData(id)); | ||
// updateUser | ||
const userController = leitenRecord(useStore, "data.user"); | ||
// removeCard | ||
const cardsController = leitenList(useStore, "data.cards", { compare: (a, b) => a.id == b.id }); | ||
``` | ||
Using "leiten" controllers empowers you to simplify your state management by removing redundant actions, eliminating | ||
unnecessary states, and reducing the complexity of working with nested objects. By adopting "leiten" controllers you can | ||
create a cleaner and more streamlined store structure, making it easier to manage and manipulate your application's | ||
data. | ||
All actions and states for your **zustand** | ||
store. [Examples](https://github.com/Hecmatyar/zustand-fetching/tree/main/src/examples/controllers) | ||
- [leitenRequest](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/1_Controller_Request.tsx) | ||
help you to handle request (any async function) and catch errors, return **hook** with params of request, and have | ||
methods: _action_, _clear_, _abort_ and _set_. | ||
Helps handle requests (any async function) and catch errors. Returns a **hook** with request parameters and provides | ||
methods such as _action_, _clear_, _abort_, and _set_. | ||
- [leitenGroupRequest](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/6_Controller_GroupRequest.tsx) | ||
handle a lot of similar requests dynamically, return **hook** with 2 overloads and have methods: _call_ and _clear_. | ||
Handles multiple similar requests dynamically. Returns a **hook** with two overloads and provides methods such | ||
as _call_ and _clear_. Can work with arrays as well as with the normalized list. | ||
- [leitenRecord](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/2_Controller_Record.tsx) | ||
working with objects, have methods _set_, _patch_ and _clear_. | ||
Works with objects and provides methods such as _set_, _patch_ and _clear_. | ||
- [leitenPrimitive](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/3_Controller_Primitive.tsx) | ||
working with data like with primitive value, but it can be object, function or primitives. | ||
Have methods: _set_ and _clear_. | ||
Works with data as if it were a primitive value, but it can be an object, function, or primitives. Provides methods | ||
such as _set_ and _clear_. | ||
- [leitenList](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/4_Controller_List.tsx) | ||
working with array, have methods: _set_, _clear_, _add_, _update_, _remove_, _toggle_ and _filter_. If array item is | ||
an | ||
object then need to set **compare** function in the controller's options (third parameter). | ||
Works with arrays and provides methods such as _set_, _clear_, _add_, _update_, _remove_, _toggle_, and _filter_. If | ||
the array item | ||
is an object, a **compare** function needs to be set in the controller's options (third parameter). | ||
- [leitenNormalizedList](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/4_Controller_List.tsx) | ||
is the same as leitenList but working with normalized state. | ||
Same as leitenList but works with normalized state. | ||
- [leitenModal](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/5_Controller_Modal.tsx) | ||
help to work with modals, have built in modal manager (if you want to open modal in cascade). Return hooks | ||
with [openState, hiddenState], have methods: _open_, _close_ and _action_. | ||
Helps work with modals and provides a built-in modal manager for cascading modals. Returns hooks | ||
with [openState, hiddenState] and provides methods such as _open_, _close_ and _action_. | ||
> All leitenControllers automatically calculate required type by path and **throw typescript error** if the specified | ||
> path does not satisfy the requirements of the controller or the established types. | ||
> Examples: | ||
> All leitenControllers automatically infer the required types based on the specified path and will throw a **TypeScript | ||
> error** if the provided path does not match the controller's requirements or established types. Examples: | ||
>- Argument of type '"info.keywords.1"' is not assignable to parameter of type '"info.keywords"'. | ||
>- Argument of type 'string' is not assignable to parameter of type 'never'. | ||
⚠️ If you encounter an error when specifying the path to your field in the store, it is likely because you are | ||
attempting to attach a controller to a field with an incompatible type. Please ensure that the field you are attaching | ||
the controller to has a permitted type to resolve this issue. | ||
Library well tree shaking and have dependencies from **immer**, **lodash-es** and **nanoid** | ||
@@ -60,8 +126,45 @@ | ||
**leitenRecord**, **leitenPrimitive**, **leitenList** and **leitenNormalizedList** have options with callbacks: _ | ||
sideEffect_ and _patchEffect_. You can use them to extend basic functionality | ||
**leitenRecord**, **leitenPrimitive**, **leitenList** and **leitenNormalizedList** have options with callbacks: | ||
_sideEffect_ and _patchEffect_. You can use them to extend basic functionality | ||
**leitenRequest** and **leitenGrouprRequest** have a useful reactions: _fulfilled_, _rejected_, _abort_, _resolved_ | ||
```tsx | ||
const useExampleStore = create<IState>(() => ({ user: null })); | ||
const recordController = leitenRecord(useExampleStore, "user", { | ||
sideEffect: (value: { prev: IUser; next: IUser }) => { | ||
// you can execude here some side actions | ||
}, | ||
patchEffect: (value: VALUE) => { | ||
// you can update your entire store here in one tick with value update | ||
}, | ||
}); | ||
``` | ||
**leitenRequest** and **leitenGroupRequest** have a useful reactions: _fulfilled_, _rejected_, _abort_, _resolved_ | ||
and _action_ | ||
```tsx | ||
const useExampleStore = create<IState>(() => ({ user: null })); | ||
const recordController = leitenRequest(useExampleStore, "user", async (id: string) => getUser(id), { | ||
fulfilled: ({ previousResult, result, payload }) => { | ||
// do something after successful request | ||
}, | ||
rejected: ({ previousResult, error, payload }) => { | ||
// do something after error request | ||
}, | ||
abort: ({ previousResult, payload }) => { | ||
// do something after request was aborted | ||
}, | ||
resolved: ({ previousResult, payload }) => { | ||
// do something after request was resolved | ||
}, | ||
action: ({ previousResult, payload }) => { | ||
// do something before request was called | ||
}, | ||
optimisticUpdate: (payload) => { | ||
//if you use this callback, then leitenRequest will automatically switch to optimistic update mode | ||
}, | ||
initialStatus: ILoadingStatus // initial status if request, init by default | ||
}); | ||
``` | ||
- leitenList - if you are using object then you also should specify **compare** function like in example | ||
@@ -87,3 +190,4 @@ - leitenNormalizedList - in addition to the **compare** function, you also need to define the **getKey** function | ||
const User = () => { | ||
const status = useController(state => state[useController.key].status) | ||
// const status = useController(state => state.status) - the same | ||
const status = useLeitenRequests(state => state[useController.key].status) | ||
return <>{status}</> | ||
@@ -93,7 +197,28 @@ } | ||
leitenMap also can be | ||
helpful, [example](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/6_Controller_GroupRequest.tsx) | ||
leitenMap also can be helpful, | ||
[example](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/controllers/6_Controller_GroupRequest.tsx) | ||
### Group Request | ||
leitenGroupRequest works equally well with both a normalized list and a regular array. If you are using an array, make | ||
sure to specify the **getKey** function, as shown in the example | ||
below. [Codesandbox link](https://codesandbox.io/p/sandbox/cool-night-0mo1l7?file=%2Fsrc%2Frequest%2FRequest.tsx) with | ||
arrays | ||
```tsx | ||
interface IStore { | ||
record: Record<string, ICard>, | ||
array: ICard[], | ||
} | ||
const useStore = create<IStore>(() => ({ | ||
record: {}, | ||
array: [] | ||
})); | ||
const useRecordController = leitenGroupRequest(useStore, "record", (id: string) => getCard(id)) | ||
const useArrayController = leitenGroupRequest(useStore, "array", (id: string) => getCard(id), { | ||
getKey: (value) => value.id | ||
}) | ||
``` | ||
leitenGroupRequest return overloaded hook | ||
@@ -124,3 +249,5 @@ | ||
Wrappers for [ContextStore](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/store/ContextStore.tsx) | ||
and [ResettableStore](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/store/ResettableStore.tsx) | ||
The library provides wrappers | ||
for [ContextStore](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/store/ContextStore.tsx) | ||
and [ResettableStore](https://github.com/Hecmatyar/zustand-fetching/blob/main/src/examples/store/ResettableStore.tsx). | ||
These wrappers can be used to enhance your Zustand store with additional features. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
65207
1151
248