DEV Community

Cover image for πŸš€ New React Challenge: Collaborative Editor
ReactChallenges
ReactChallenges

Posted on

πŸš€ New React Challenge: Collaborative Editor

Most editors keep the truth in local state and hope that the save, a second tab, and a remote collaborator all agree. This challenge inverts that: every change is an immutable event appended to a log, and the document you see is just a projection of it.


🧩 Overview

Build a collaborative notes editor as a tiny event-sourced + CQRS system. Edits are optimistic, the document is rebuilt from the event log, autosave debounces into a single request, and a simulated peer merges its events through the same rules.

https://www.reactchallenges.com/challenges/collaborative-editor


βœ… Requirements

  • Implement the editor in App.tsx; the provided types.ts, data.ts, eventLog.ts, projection.ts, api.ts, and peer.ts are read-only, and the first render shows the document built from initialEvents.
  • Render one editable block per textarea in insertion order, with an aria-label on each; the seed blocks appear in that order.
  • Each block has a delete control that removes the block immediately.
  • An add control appends a new empty block to the end of the document.
  • Typing updates the visible text immediately and sets the save status to Dirty.
  • Several edits in quick succession result in a single save, not one save per keystroke.
  • While a save is pending the status reads Saving…; after a successful save it reads Saved.
  • When a save fails the status reads Error, the edited text stays visible, and a retry control appears.
  • Clicking retry saves the latest document immediately and moves from Saving… to Saved on success.
  • A pending autosave is cancelled on unmount, so leaving the editor does not start a save.
  • A remote update applied to a block replaces that block's text with the remote text.
  • A remote event that was already applied is ignored: it changes nothing and triggers no extra save.
  • A remote event that is older than the block's current version is ignored.
  • Remote edits never mark the document Dirty and never trigger an autosave.
  • The presence indicator reads Ana is editing while the simulated peer subscription is active; on unmount the subscription is removed and the peer reports no listeners.

πŸ’‘ Notes

  • Treat the log as append-only and always return a new array from an append β€” mutate events in place and React never re-renders.
  • Idempotency is keyed on the event id, not its payload: a re-delivered event can carry a higher version and still has to be dropped before it reaches the projection.
  • Debounce the save with the timeout handle in a ref, reset it on every edit, and clear it in the effect cleanup so an unmount never fires a pending save.

πŸ§ͺ Tests

  1. Renders the three initial blocks in order
  2. Updates the text immediately and marks the status dirty
  3. Debounces a burst of edits into a single save
  4. Moves from saving to saved and persists the latest text
  5. Keeps the edited text on failure and lets retry save the latest version
  6. Removes a block immediately when it is deleted
  7. Adds a new empty block
  8. Applies a remote update with a higher version
  9. Ignores a remote event whose id was already applied
  10. Ignores a stale remote event with a lower version
  11. Shows the peer presence and unsubscribes on unmount

You finish with a mental model you can reuse anywhere state has to be synced: derive the read model, write events instead of mutating objects, and make every incoming change idempotent. That's the shape of most real-time collaboration and offline-first code.

πŸ”₯ Start the Challenge Now

Top comments (0)