useCycleLoading saved progress…

useCycle

Build a custom React hook that steps through a fixed list of values, one at a time. useCycle('red', 'green', 'blue') hands back the current value (starting with the first) and a cycle function. Each call to cycle() moves to the next value, looping back to the start after the last — like clicking a toggle that rotates through a set of options. You can also pass an index to jump straight to a specific value. It's modeled on framer-motion's hook of the same name.

Signature

function useCycle<T>(...items: T[]): [current: T, cycle: (index?: number) => void];

current starts as items[0]. cycle() advances to the next item; cycle(index) jumps directly to items[index]. The cycle function keeps the same identity across every render.

Examples

function Theme() {
  const [color, cycle] = useCycle('red', 'green', 'blue');
  return <button onClick={() => cycle()} style={{ color }}>{color}</button>;
}
// renders 'red'; click → 'green'; click → 'blue'; click → wraps to 'red'
// Advancing wraps around; passing an index jumps directly.
const [value, cycle] = useCycle('a', 'b', 'c'); // value === 'a'
cycle();   // value === 'b'
cycle();   // value === 'c'
cycle();   // value === 'a'  (wrapped past the end)
cycle(2);  // value === 'c'  (jumped to items[2])

Notes

  • The current value starts at the first item. Before any cycle() call, current is items[0].
  • Advancing wraps around. Calling cycle() on the last item returns to the first; the list is a loop, not a dead end.
  • cycle accepts an optional index. cycle() steps forward by one; cycle(2) jumps straight to items[2].
  • cycle must have a stable identity. Returning a brand-new function each render is not acceptable here — the same reference must come back across renders, so it's safe to pass to memoized children or effect dependency arrays.
  • You can assume the index passed is in range. Items are short, fixed lists; you don't need to validate against bad input beyond keeping the wrap correct.
Loading editor…