Skip to content
react-cards-opening

Keep opened cards

The library never decides where opened cards are stored. It asks for a list of opened ids and calls onOpen when one is added. That leaves three good places to keep the list, and the right one depends on whether a card is a toy or a thing someone owns.

On the device

tsx
const { opened, open } = useOpenedCards(`deal:${userId}:${dealtAt}`);
const hunt = useCardOpening({ cards, opened, onOpen: open });

Right for a reveal that is a presentation: the cards are already decided and "opened" only means "already watched". It costs nothing and works signed out. It does not follow the person to their phone.

In your own state

tsx
const [opened, setOpened] = useState<string[]>([]);
const hunt = useCardOpening({
  cards,
  opened,
  onOpen: (id) => setOpened((now) => [...now, id]),
});

Right for a demo, a game round, or anything that should start over on reload.

On a server

tsx
const { data } = useQuery(["deal"], fetchDeal); // { cards, opened }
const [local, setLocal] = useState<string[]>([]);
const opened = [...(data?.opened ?? []), ...local];

const hunt = useCardOpening({
  cards: data?.cards ?? [],
  opened,
  onOpen: (id) => {
    setLocal((now) => [...now, id]); // the reveal goes on at once
    void api.openCard(id); // the server catches up
  },
});
  • Update local state first. onOpen fires while the card is face up, and the reveal should not wait on a request.
  • Make the endpoint safe to call twice. A retry or a second tab will do it.
  • Send the cards down already dealt. A deal made in the browser can be redone by the visitor; see Rolls and odds.

Skipping counts

openAll() calls onOpen once for each unopened card, one after another. If your handler makes a request per card, that is a burst of requests. Batch them, or give the skip button its own endpoint.

Last updated . Read this page as Markdown.