Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
- [`useMap`](./docs/useMap.md) — tracks state of an object. [![][img-demo]](https://codesandbox.io/s/quirky-dewdney-gi161)
- [`useSet`](./docs/useSet.md) — tracks state of a Set. [![][img-demo]](https://codesandbox.io/s/bold-shtern-6jlgw)
- [`useQueue`](./docs/useQueue.md) — implements simple queue.
- [`useStack`](./docs/useStack.md) — implements simple Stack (LIFO).
- [`useStateValidator`](./docs/useStateValidator.md) — tracks state of an object. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usestatevalidator--demo)
- [`useStateWithHistory`](./docs/useStateWithHistory.md) — stores previous state values and provides handles to travel through them. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usestatewithhistory--demo)
- [`useMultiStateValidator`](./docs/useMultiStateValidator.md) — alike the `useStateValidator`, but tracks multiple states at a time. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usemultistatevalidator--demo)
Expand Down
26 changes: 26 additions & 0 deletions docs/useStack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### useStack

React hook implementing stack (LIFO) behavior.

```tsx
import { useStack } from 'react-use';

const Demo = () => {
const [stack, { push, pop, peek, clear, reset, size }] = useStack([1, 2]);

return (
<div>
<ul>
<li>Stack: {JSON.stringify(stack)}</li>
<li>Top: {peek()}</li>
<li>Size: {size()}</li>
</ul>

<button onClick={() => push((peek() || 0) + 1)}>Push</button>
<button onClick={() => pop()}>Pop</button>
<button onClick={() => clear()}>Clear</button>
<button onClick={() => reset()}>Reset</button>
</div>
);
};

1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,4 @@ export { useFirstMountState } from './useFirstMountState';
export { default as useSet } from './useSet';
export { createGlobalState } from './factory/createGlobalState';
export { useHash } from './useHash';
export { default as useStack } from './useStack';
93 changes: 93 additions & 0 deletions src/useStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useMemo, useRef } from 'react';
import useUpdate from './useUpdate';
import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from './misc/hookState';

export interface StackActions<T> {
/**
* @description Push an element onto the stack (top).
*/
push: (item: T) => void;

/**
* @description Pop and return the top element. Returns undefined if empty.
*/
pop: () => T | undefined;

/**
* @description Returns the top element without removing it.
*/
peek: () => T | undefined;

/**
* @description Clear all elements from the stack.
*/
clear: () => void;

/**
* @description Reset stack to its initial value.
*/
reset: () => void;

/**
* @description Replace the entire stack manually.
*/
set: (newStack: IHookStateSetAction<T[]>) => void;

/**
* @description Returns the current stack size.
*/
size: () => number;
}

/**
* @name useStack
* @description React hook that provides stack (LIFO) operations.
* @example
* const [stack, { push, pop, peek, clear, reset, size }] = useStack<number>([1, 2]);
*/
function useStack<T>(initialStack: IHookStateInitAction<T[]> = []): [T[], StackActions<T>] {
const stack = useRef(resolveHookState(initialStack));
const update = useUpdate();

const actions = useMemo<StackActions<T>>(() => {
const a = {
set: (newStack: IHookStateSetAction<T[]>) => {
stack.current = resolveHookState(newStack, stack.current);
update();
},

push: (item: T) => {
a.set((curr: T[]) => curr.concat(item));
},

pop: () => {
if (stack.current.length === 0) return undefined;
const item = stack.current[stack.current.length - 1];
a.set((curr: T[]) => curr.slice(0, -1));
return item;
},

peek: () => {
return stack.current[stack.current.length - 1];
},

clear: () => {
a.set([]);
},

reset: () => {
a.set(resolveHookState(initialStack).slice());
},

size: () => {
return stack.current.length;
},
};

return a as StackActions<T>;
}, []);

return [stack.current, actions];
}

export default useStack;
27 changes: 27 additions & 0 deletions stories/useStack.story.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import useStack from '../src/useStack';
import ShowDocs from './util/ShowDocs';

const Demo = () => {
const [stack, { push, pop, peek, clear, reset, size }] = useStack<number>([1, 2]);

return (
<div>
<ul>
<li>Stack: {JSON.stringify(stack)}</li>
<li>Top: {peek()}</li>
<li>Size: {size()}</li>
</ul>

<button onClick={() => push((peek() || 0) + 1)}>Push</button>
<button onClick={() => pop()}>Pop</button>
<button onClick={clear}>Clear</button>
<button onClick={reset}>Reset</button>
</div>
);
};

storiesOf('State/useStack', module)
.add('Docs', () => <ShowDocs md={require('../docs/useStack.md')} />)
.add('Demo', () => <Demo />);
49 changes: 49 additions & 0 deletions tests/useStack.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { renderHook, act } from '@testing-library/react-hooks';
import useStack from '../src/useStack';

describe('useStack', () => {
it('should initialize with initial values', () => {
const { result } = renderHook(() => useStack([1, 2]));
expect(result.current[0]).toEqual([1, 2]);
});

it('should push and pop elements correctly', () => {
const { result } = renderHook(() => useStack<number>());
const [, actions] = result.current;

act(() => {
actions.push(10);
actions.push(20);
});

expect(result.current[0]).toEqual([10, 20]);
expect(actions.peek()).toBe(20);

act(() => {
actions.pop();
});

expect(result.current[0]).toEqual([10]);
});

it('should clear and reset stack', () => {
const { result } = renderHook(() => useStack([1, 2, 3]));
const [, actions] = result.current;

act(() => {
actions.clear();
});
expect(result.current[0]).toEqual([]);

act(() => {
actions.reset();
});
expect(result.current[0]).toEqual([1, 2, 3]);
});

it('should return correct size', () => {
const { result } = renderHook(() => useStack([1, 2]));
const [, actions] = result.current;
expect(actions.size()).toBe(2);
});
});