diff --git a/README.md b/README.md
index cc1728fafd..27f4e3ee3c 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/docs/useStack.md b/docs/useStack.md
new file mode 100644
index 0000000000..ef69bae74d
--- /dev/null
+++ b/docs/useStack.md
@@ -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 (
+
+
+ - Stack: {JSON.stringify(stack)}
+ - Top: {peek()}
+ - Size: {size()}
+
+
+
+
+
+
+
+ );
+};
+
diff --git a/src/index.ts b/src/index.ts
index 62b69356b7..bbcdb4b474 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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';
diff --git a/src/useStack.ts b/src/useStack.ts
new file mode 100644
index 0000000000..319e32b8e6
--- /dev/null
+++ b/src/useStack.ts
@@ -0,0 +1,93 @@
+import { useMemo, useRef } from 'react';
+import useUpdate from './useUpdate';
+import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from './misc/hookState';
+
+export interface StackActions {
+ /**
+ * @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) => 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([1, 2]);
+ */
+function useStack(initialStack: IHookStateInitAction = []): [T[], StackActions] {
+ const stack = useRef(resolveHookState(initialStack));
+ const update = useUpdate();
+
+ const actions = useMemo>(() => {
+ const a = {
+ set: (newStack: IHookStateSetAction) => {
+ 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;
+ }, []);
+
+ return [stack.current, actions];
+}
+
+export default useStack;
diff --git a/stories/useStack.story.tsx b/stories/useStack.story.tsx
new file mode 100644
index 0000000000..948a515e85
--- /dev/null
+++ b/stories/useStack.story.tsx
@@ -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([1, 2]);
+
+ return (
+
+
+ - Stack: {JSON.stringify(stack)}
+ - Top: {peek()}
+ - Size: {size()}
+
+
+
+
+
+
+
+ );
+};
+
+storiesOf('State/useStack', module)
+ .add('Docs', () => )
+ .add('Demo', () => );
diff --git a/tests/useStack.test.ts b/tests/useStack.test.ts
new file mode 100644
index 0000000000..802922411e
--- /dev/null
+++ b/tests/useStack.test.ts
@@ -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());
+ 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);
+ });
+});