useStackState
Manages stack (LIFO) state with push, pop, and peek operations.
About
A React hook that manages state in the form of a stack
Examples
import { useStackState } from "rooks";
export default function NavigationStack() {
const [, { peek, pop, push }, newestFirst] = useStackState(["Home"]);
return (
<section>
<p>Top: {peek() ?? "Empty"}</p>
<ol>
{newestFirst.map((item) => (
<li key={item}>{item}</li>
))}
</ol>
<button
type="button"
onClick={() => push(`Page ${newestFirst.length + 1}`)}
>
Push
</button>
<button type="button" onClick={() => void pop()}>
Pop
</button>
</section>
);
}Basic usage
import React, { useRef } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { useStackState } from "rooks";
import styled from "styled-components";
const variants = {
appear: {
opacity: 0,
y: "-150%",
x: "-15%",
rotate: -15,
transition: {
duration: 0.5,
},
},
disappear: {
opacity: 0,
y: "-200%",
x: "100%",
rotate: 45,
transition: {
duration: 0.5,
},
},
visible: {
opacity: 1,
y: "0%",
x: "0%",
rotate: 0,
transition: {
duration: 0.5,
},
},
};
const StackInner = styled(motion.div)`
border-bottom: 5px solid black;
padding: 0 12px 3px;
width: 300px;
> * {
margin: 0;
padding: 12px 16px;
}
`;
export function StackDiv({ children, ...props }) {
return (
<StackInner {...props}>
<AnimatePresence initial={false}>{children}</AnimatePresence>
</StackInner>
);
}
export const StackItem = styled(motion.div).attrs((props) => ({
variants,
initial: "appear",
animate: "visible",
exit: "disappear",
}))`
width: 100%;
background-color: rgba(254, 243, 199, 1);
margin: 0;
padding: 12px 16px;
font-weight: 500;
font-size: 1rem;
line-height: 1.5rem;
border-color: rgba(0, 0, 0, 1);
border-width: 1px;
border-style: solid;
border-radius: 0.375rem;
place-content: center;
display: flex;
`;
export const ActionButton = styled(motion.button)`
width: 100%;
font-weight: 500;
color: rgba(67,56,202,1);
font-size: 1rem;
line-height: 1.5rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 1.5rem;
padding-right: 1.5rem;
border-color: transparent;
align-items: center;
display: inline-flex;
border-radius: 0.375rem;
border-color: transparent;
background-color: rgba(224,231,255,1);
}
`;
export const ActionButton2 = styled(motion.button)`
width: 100%;
font-weight: 500;
font-size: 1rem;
line-height: 1.5rem;
padding-top: 0.75rem;
padding-bottom: 0.75rem;
padding-left: 1.5rem;
padding-right: 1.5rem;
border-color: transparent;
align-items: center;
display: inline-flex;
border-radius: 0.375rem;
border-color: transparent;
background-color: rgba(254, 226, 226, 1);
`;
export const ActionButtons = styled.div`
margin: 12px 0;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 16px;
`;
export const Root = styled.div`
height: 100vh;
padding-bottom: 100px;
width: 300px;
display: flex;
align-items: flex-end;
margin: auto;
box-sizing: border-box;
`;
export default function App() {
const numberToPushRef = useRef(3);
const [list, { push, pop }, listInReverse] = useStackState([1, 2, 3]);
function addToStack() {
numberToPushRef.current = numberToPushRef.current + 1;
push(numberToPushRef.current);
}
return (
<Root>
<div>
<StackDiv>
{listInReverse.map((item) => {
return <StackItem key={item}>{item}</StackItem>;
})}
</StackDiv>
<ActionButtons>
<ActionButton onClick={addToStack}>Push</ActionButton>{" "}
<ActionButton2 onClick={pop} warning>
Pop
</ActionButton2>{" "}
</ActionButtons>
</div>
</Root>
);
}Inspect the top item and render newest-first
import { useStackState } from "rooks";
export default function UploadQueue() {
const [queue, controls, newestFirst] = useStackState([
"draft.pdf",
"brief.png",
]);
return (
<div>
<p>Next upload: {controls.peek() ?? "Nothing queued"}</p>
<p>Items queued: {controls.length}</p>
<button onClick={() => controls.push(`file-${controls.length + 1}.txt`)}>
Add file
</button>
<button onClick={controls.pop} disabled={controls.isEmpty()}>
Remove latest
</button>
<button onClick={controls.clear} disabled={controls.isEmpty()}>
Clear queue
</button>
<ul>
{newestFirst.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
initialList | T[] | Required | Items copied into the initial stack. |
Returned array items
| Returned items | Type | Description |
|---|---|---|
| push | function | Put an item to the top of the stack |
| pop | function | Remove the item on the top of the stack |
| peek | function | Return the item on the top of the stack |
| length | number | Number of items in the stack |
Return value
Returns [list, controls, listInReverse]. list is ordered from bottom to top; listInReverse is a memoized copy ordered top to bottom. Controls include push, pop, peek, clear, isEmpty, and the current numeric length. Empty pop and peek return undefined; push returns the new length.
Behavior and lifecycle
The initial array and reversed view are shallow copies. push and pop callbacks close over the current rendered list rather than using functional updates, so several operations from one stale render do not reliably compose. clear is recreated on every render; other query/mutation callbacks are recreated when the list changes. No effects or external resources are created.
Compatibility and accessibility
The hook is SSR-safe and has no browser requirement. Render stack contents with semantic list markup, and manage focus or announcements if push/pop actions change content users are actively reviewing.