Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a new hook useObjectArray with many utility functions that is a… #2503

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
<br/>
<br/>
- [**State**](./docs/State.md)

- [`createMemo`](./docs/createMemo.md) &mdash; factory of memoized hooks.
- [`createReducer`](./docs/createReducer.md) &mdash; factory of reducer hooks with custom middleware.
- [`createReducerContext`](./docs/createReducerContext.md) and [`createStateContext`](./docs/createStateContext.md) &mdash; factory of hooks for a sharing state between components.
Expand Down Expand Up @@ -154,8 +155,10 @@
- [`useRendersCount`](./docs/useRendersCount.md) &mdash; count component renders. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-userenderscount--demo)
- [`createGlobalState`](./docs/createGlobalState.md) &mdash; cross component shared state.[![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-createglobalstate--demo)
- [`useMethods`](./docs/useMethods.md) &mdash; neat alternative to `useReducer`. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemethods--demo)
- [`useObjectArray`](./docs/useObjectArray.md) &mdash; React custom hook that manages an array of objects with various utility functions such as add, update, clear, set.
<br/>
<br/>

- [**Miscellaneous**]()
- [`useEnsuredForwardedRef`](./docs/useEnsuredForwardedRef.md) and [`ensuredForwardRef`](./docs/useEnsuredForwardedRef.md) &mdash; use a React.forwardedRef safely. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-useensuredforwardedref--demo)

Expand Down
95 changes: 95 additions & 0 deletions docs/useObjectArray.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# `useObjectArray`

React custom hook that manages an array of objects with various utility functions such as add, update, clear, set

## Usage

```jsx
import { useObjectArray } from "react-use";

type Product = {
id: string,
name: string,
description: string,
price: number,
quantity: number,
discountAvailable: boolean,
};

const initialProducts: Product[] = [
{
id: "1",
name: "Product 1",
description: "Description 1",
price: 10,
quantity: 1,
discountAvailable: true,
},
{
id: "2",
name: "Product 2",
description: "Description 2",
price: 15,
quantity: 2,
discountAvailable: false,
},
{
id: "3",
name: "Product 3",
description: "Description 3",
price: 20,
quantity: 3,
discountAvailable: true,
},
];

const Demo = () => {
const [products, setProducts, addProduct, removeProduct, updateProduct, clearProducts] =
useObjectArray < Product > initialProducts;

const totalProducts = products.length;

return (
<div>
<h1>Product Demo</h1>

<ul>
{products.map((product, index) => (
<li key={product.id}>
<strong>{product.name}</strong>
<br />
Description: {product.description}
<br />
Price: ${product.price}
<br />
Quantity: {product.quantity}
<br />
<button onClick={() => removeProduct(index)}>Remove</button>
<button onClick={() => updateProduct(index, "name", "Updated Product Name")}>
Update Name
</button>
</li>
))}
</ul>

<button
onClick={() =>
addProduct({
id: String(products.length + 1),
name: `Product ${totalProducts + 1}`,
description: `Description ${totalProducts + 1}`,
price: 20,
quantity: 3,
discountAvailable: false,
})
}
>
Add Product
</button>

<button onClick={() => clearProducts()}>Clear Products</button>
<button onClick={() => setProducts(initialProducts)}>Reset</button>
</div>
);
};
```
227 changes: 114 additions & 113 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,117 +1,118 @@
export { default as createMemo } from './factory/createMemo';
export { default as createReducerContext } from './factory/createReducerContext';
export { default as createReducer } from './factory/createReducer';
export { default as createStateContext } from './factory/createStateContext';
export { default as useAsync } from './useAsync';
export { default as useAsyncFn } from './useAsyncFn';
export { default as useAsyncRetry } from './useAsyncRetry';
export { default as useAudio } from './useAudio';
export { default as useBattery } from './useBattery';
export { default as useBeforeUnload } from './useBeforeUnload';
export { default as useBoolean } from './useBoolean';
export { default as useClickAway } from './useClickAway';
export { default as useCookie } from './useCookie';
export { default as useCopyToClipboard } from './useCopyToClipboard';
export { default as useCounter } from './useCounter';
export { default as useCss } from './useCss';
export { default as useCustomCompareEffect } from './useCustomCompareEffect';
export { default as useDebounce } from './useDebounce';
export { default as useDeepCompareEffect } from './useDeepCompareEffect';
export { default as useDefault } from './useDefault';
export { default as useDrop } from './useDrop';
export { default as useDropArea } from './useDropArea';
export { default as useEffectOnce } from './useEffectOnce';
export { default as useEnsuredForwardedRef, ensuredForwardRef } from './useEnsuredForwardedRef';
export { default as useEvent } from './useEvent';
export { default as useError } from './useError';
export { default as useFavicon } from './useFavicon';
export { default as useFullscreen } from './useFullscreen';
export { default as useGeolocation } from './useGeolocation';
export { default as useGetSet } from './useGetSet';
export { default as useGetSetState } from './useGetSetState';
export { default as useHarmonicIntervalFn } from './useHarmonicIntervalFn';
export { default as useHover } from './useHover';
export { default as useHoverDirty } from './useHoverDirty';
export { default as useIdle } from './useIdle';
export { default as useIntersection } from './useIntersection';
export { default as useInterval } from './useInterval';
export { default as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
export { default as useKey } from './useKey';
export { default as createBreakpoint } from './factory/createBreakpoint';
export { default as createMemo } from "./factory/createMemo";
export { default as createReducerContext } from "./factory/createReducerContext";
export { default as createReducer } from "./factory/createReducer";
export { default as createStateContext } from "./factory/createStateContext";
export { default as useAsync } from "./useAsync";
export { default as useAsyncFn } from "./useAsyncFn";
export { default as useAsyncRetry } from "./useAsyncRetry";
export { default as useAudio } from "./useAudio";
export { default as useBattery } from "./useBattery";
export { default as useBeforeUnload } from "./useBeforeUnload";
export { default as useBoolean } from "./useBoolean";
export { default as useClickAway } from "./useClickAway";
export { default as useCookie } from "./useCookie";
export { default as useCopyToClipboard } from "./useCopyToClipboard";
export { default as useCounter } from "./useCounter";
export { default as useCss } from "./useCss";
export { default as useCustomCompareEffect } from "./useCustomCompareEffect";
export { default as useDebounce } from "./useDebounce";
export { default as useDeepCompareEffect } from "./useDeepCompareEffect";
export { default as useDefault } from "./useDefault";
export { default as useDrop } from "./useDrop";
export { default as useDropArea } from "./useDropArea";
export { default as useEffectOnce } from "./useEffectOnce";
export { default as useEnsuredForwardedRef, ensuredForwardRef } from "./useEnsuredForwardedRef";
export { default as useEvent } from "./useEvent";
export { default as useError } from "./useError";
export { default as useFavicon } from "./useFavicon";
export { default as useFullscreen } from "./useFullscreen";
export { default as useGeolocation } from "./useGeolocation";
export { default as useGetSet } from "./useGetSet";
export { default as useGetSetState } from "./useGetSetState";
export { default as useHarmonicIntervalFn } from "./useHarmonicIntervalFn";
export { default as useHover } from "./useHover";
export { default as useHoverDirty } from "./useHoverDirty";
export { default as useIdle } from "./useIdle";
export { default as useIntersection } from "./useIntersection";
export { default as useInterval } from "./useInterval";
export { default as useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
export { default as useKey } from "./useKey";
export { default as createBreakpoint } from "./factory/createBreakpoint";
// not exported because of peer dependency
// export { default as useKeyboardJs } from './useKeyboardJs';
export { default as useKeyPress } from './useKeyPress';
export { default as useKeyPressEvent } from './useKeyPressEvent';
export { default as useLatest } from './useLatest';
export { default as useLifecycles } from './useLifecycles';
export { default as useList } from './useList';
export { default as useLocalStorage } from './useLocalStorage';
export { default as useLocation } from './useLocation';
export { default as useLockBodyScroll } from './useLockBodyScroll';
export { default as useLogger } from './useLogger';
export { default as useLongPress } from './useLongPress';
export { default as useMap } from './useMap';
export { default as useMedia } from './useMedia';
export { default as useMediaDevices } from './useMediaDevices';
export { useMediatedState } from './useMediatedState';
export { default as useMethods } from './useMethods';
export { default as useMotion } from './useMotion';
export { default as useMount } from './useMount';
export { default as useMountedState } from './useMountedState';
export { default as useMouse } from './useMouse';
export { default as useMouseHovered } from './useMouseHovered';
export { default as useMouseWheel } from './useMouseWheel';
export { default as useNetworkState } from './useNetworkState';
export { default as useNumber } from './useNumber';
export { default as useObservable } from './useObservable';
export { default as useOrientation } from './useOrientation';
export { default as usePageLeave } from './usePageLeave';
export { default as usePermission } from './usePermission';
export { default as usePrevious } from './usePrevious';
export { default as usePreviousDistinct } from './usePreviousDistinct';
export { default as usePromise } from './usePromise';
export { default as useQueue } from './useQueue';
export { default as useRaf } from './useRaf';
export { default as useRafLoop } from './useRafLoop';
export { default as useRafState } from './useRafState';
export { default as useSearchParam } from './useSearchParam';
export { default as useScratch } from './useScratch';
export { default as useScroll } from './useScroll';
export { default as useScrolling } from './useScrolling';
export { default as useSessionStorage } from './useSessionStorage';
export { default as useSetState } from './useSetState';
export { default as useShallowCompareEffect } from './useShallowCompareEffect';
export { default as useSize } from './useSize';
export { default as useSlider } from './useSlider';
export { default as useSpeech } from './useSpeech';
export { default as useKeyPress } from "./useKeyPress";
export { default as useKeyPressEvent } from "./useKeyPressEvent";
export { default as useLatest } from "./useLatest";
export { default as useLifecycles } from "./useLifecycles";
export { default as useList } from "./useList";
export { default as useLocalStorage } from "./useLocalStorage";
export { default as useLocation } from "./useLocation";
export { default as useLockBodyScroll } from "./useLockBodyScroll";
export { default as useLogger } from "./useLogger";
export { default as useLongPress } from "./useLongPress";
export { default as useMap } from "./useMap";
export { default as useMedia } from "./useMedia";
export { default as useMediaDevices } from "./useMediaDevices";
export { useMediatedState } from "./useMediatedState";
export { default as useMethods } from "./useMethods";
export { default as useMotion } from "./useMotion";
export { default as useMount } from "./useMount";
export { default as useMountedState } from "./useMountedState";
export { default as useMouse } from "./useMouse";
export { default as useMouseHovered } from "./useMouseHovered";
export { default as useMouseWheel } from "./useMouseWheel";
export { default as useNetworkState } from "./useNetworkState";
export { default as useNumber } from "./useNumber";
export { default as useObservable } from "./useObservable";
export { default as useOrientation } from "./useOrientation";
export { default as usePageLeave } from "./usePageLeave";
export { default as usePermission } from "./usePermission";
export { default as usePrevious } from "./usePrevious";
export { default as usePreviousDistinct } from "./usePreviousDistinct";
export { default as usePromise } from "./usePromise";
export { default as useQueue } from "./useQueue";
export { default as useRaf } from "./useRaf";
export { default as useRafLoop } from "./useRafLoop";
export { default as useRafState } from "./useRafState";
export { default as useSearchParam } from "./useSearchParam";
export { default as useScratch } from "./useScratch";
export { default as useScroll } from "./useScroll";
export { default as useScrolling } from "./useScrolling";
export { default as useSessionStorage } from "./useSessionStorage";
export { default as useSetState } from "./useSetState";
export { default as useShallowCompareEffect } from "./useShallowCompareEffect";
export { default as useSize } from "./useSize";
export { default as useSlider } from "./useSlider";
export { default as useSpeech } from "./useSpeech";
// not exported because of peer dependency
// export { default as useSpring } from './useSpring';
export { default as useStartTyping } from './useStartTyping';
export { useStateWithHistory } from './useStateWithHistory';
export { default as useStateList } from './useStateList';
export { default as useThrottle } from './useThrottle';
export { default as useThrottleFn } from './useThrottleFn';
export { default as useTimeout } from './useTimeout';
export { default as useTimeoutFn } from './useTimeoutFn';
export { default as useTitle } from './useTitle';
export { default as useToggle } from './useToggle';
export { default as useTween } from './useTween';
export { default as useUnmount } from './useUnmount';
export { default as useUnmountPromise } from './useUnmountPromise';
export { default as useUpdate } from './useUpdate';
export { default as useUpdateEffect } from './useUpdateEffect';
export { default as useUpsert } from './useUpsert';
export { default as useVibrate } from './useVibrate';
export { default as useVideo } from './useVideo';
export { default as useStateValidator } from './useStateValidator';
export { useScrollbarWidth } from './useScrollbarWidth';
export { useMultiStateValidator } from './useMultiStateValidator';
export { default as useWindowScroll } from './useWindowScroll';
export { default as useWindowSize } from './useWindowSize';
export { default as useMeasure } from './useMeasure';
export { default as usePinchZoom } from './usePinchZoom';
export { useRendersCount } from './useRendersCount';
export { useFirstMountState } from './useFirstMountState';
export { default as useSet } from './useSet';
export { createGlobalState } from './factory/createGlobalState';
export { useHash } from './useHash';
export { default as useStartTyping } from "./useStartTyping";
export { useStateWithHistory } from "./useStateWithHistory";
export { default as useStateList } from "./useStateList";
export { default as useThrottle } from "./useThrottle";
export { default as useThrottleFn } from "./useThrottleFn";
export { default as useTimeout } from "./useTimeout";
export { default as useTimeoutFn } from "./useTimeoutFn";
export { default as useTitle } from "./useTitle";
export { default as useToggle } from "./useToggle";
export { default as useTween } from "./useTween";
export { default as useUnmount } from "./useUnmount";
export { default as useUnmountPromise } from "./useUnmountPromise";
export { default as useUpdate } from "./useUpdate";
export { default as useUpdateEffect } from "./useUpdateEffect";
export { default as useUpsert } from "./useUpsert";
export { default as useVibrate } from "./useVibrate";
export { default as useVideo } from "./useVideo";
export { default as useStateValidator } from "./useStateValidator";
export { useScrollbarWidth } from "./useScrollbarWidth";
export { useMultiStateValidator } from "./useMultiStateValidator";
export { default as useWindowScroll } from "./useWindowScroll";
export { default as useWindowSize } from "./useWindowSize";
export { default as useMeasure } from "./useMeasure";
export { default as usePinchZoom } from "./usePinchZoom";
export { useRendersCount } from "./useRendersCount";
export { useFirstMountState } from "./useFirstMountState";
export { default as useSet } from "./useSet";
export { createGlobalState } from "./factory/createGlobalState";
export { useHash } from "./useHash";
export { default as useObjectArray } from "./useObjectArray";
30 changes: 30 additions & 0 deletions src/useObjectArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useState } from "react";

export type ObjectType = Record<string, unknown>;

export type useObjectArrayReturn<T extends ObjectType> = [
array: T[],
set: React.Dispatch<React.SetStateAction<T[]>>,
addItem: (item: T) => void,
removeItem: (index: number) => void,
updatePropertyValue: <K extends keyof T>(index: number, propertyName: K, newValue: T[K]) => void,
clear: () => void
];

const useObjectArray = <T extends ObjectType>(initialArray: T[]): useObjectArrayReturn<T> => {
const [array, setArray] = useState<T[]>(initialArray);

const removeItem = (index: number) =>
setArray((prevValue) => prevValue.filter((_, i) => i !== index));
const addItem = (item: T) => setArray((prevValue) => prevValue.concat(item));
const updatePropertyValue = (index: number, propertyName: keyof T, newValue: T[keyof T]) =>
setArray((prevValue) =>
prevValue.map((item, i) => (i === index ? { ...item, [propertyName]: newValue } : item))
);

const clear = () => setArray([]);

return [array, setArray, addItem, removeItem, updatePropertyValue, clear];
};

export default useObjectArray;