Product design

Framer CMS List: make each item a whole you can add, remove and reorder

Framer is a visual website design and publishing tool. CMS List lets content teams maintain course outcomes, FAQs or tutorial steps as rows, then display changing content through a consistent card design.

Break down the problem and try your own design and implementation first. Then explore the reasoning to check states, edge cases, trade-offs, and ways to test your approach. The guide offers a reasoning path, not a complete implementation.

01
FRONTEND EXERCISEAdvanced

How can editable rows keep their drafts through reordering, filtering and undo?

Scenario

You are building a course-outcomes list editor with a title and description in each row. Unsubmitted text must stay with its original row after reordering or temporary filtering. A draft is unsubmitted input; the complete list includes filtered-out rows; the undo record retains the most recently deleted row. Design state ownership rather than a drag-and-drop component.

Given conditions and requirements

Design a React/JS state model for your own list editor. Users must keep editing the same item, not whichever input happens to occupy a position. Explain the design and its validation without writing a complete implementation.

  1. Data. Each row has a globally unique, never-reused rowId, a title and a description. Titles may be identical. Create the ID when adding a row; copying a row creates a new ID. Capacity is 1,000 entities, counting rows in the complete current list plus the deleted row retained for undo. Filtering does not free capacity. Server saving and concurrent editing are outside scope.
  2. Drafts. Retain uncommitted edits by row. Reordering or filtering must not transfer a draft to another row. Filtered-out rows unmount; their drafts must still exist when they return.
  3. Delete and undo. Only the latest deletion can be undone. Another deletion replaces that opportunity. While undo is available, other rows may be edited, reordered or added. Restore the deleted row before its original next neighbor if that neighbor still exists; otherwise after its original previous neighbor if it still exists; otherwise at the end. Record neighbors from the complete list before deletion, not from filtered results.
  4. Editing state. Undo restores the deleted row’s content and draft. Copying takes the currently displayed text but does not share later editing state. Reject an addition or copy that would exceed capacity, show a capacity message and preserve existing content, order and the undo record. A valid deletion can replace and release the old undo record. Undo moves its retained entity back into the list without increasing the entity total. Explain how each action affects undo.
  5. Explanation and counterexamples. Describe data structures, React keys, state ownership, invalid operations and complexity. Cover reordering, unmounting through filtering, duplicate titles and adding after a deletion. Dragging, focus management and a complete editor are not required.

Work through this case

The complete order is A, B, C, and A and B share a title. Type the unsubmitted draft “Practice feedback” in B, reorder to B, A, C, hide B with a filter and show it again. Then delete B, add D and undo the deletion. Track B’s identity, text and position at every step rather than its visible row number.

What to deliver

Deliver a state-structure diagram, a table of state changes per action, and traces for the sequence and capacity boundary. Explain what React keys do and what must still be retained across filtered unmounting. Server storage, persistence and complete page code are out of scope.

Explore the reasoning
  1. Separate identity, order and the visible subset. Index content and drafts by rowId, with a separate ordered ID list for the complete order. Filtering computes visible IDs without deleting entities. Use stable rowId keys in React; titles, array indices and random values generated during rendering do not express identity. See React’s list guidance.
  2. Own persistent drafts above individual rows. A key helps match components that remain in the tree, but does not preserve an unmounted component’s local state. React’s state lifecycle explains that removal discards that state. Keep drafts in the list-level state indexed by ID and pass each row its value. A copy creates an independent entity.
  3. Keep a bounded undo record. Save the deleted entity, its draft and original neighbor IDs, then remove it from the active entities and order. A later deletion replaces the record; edits, reorders and additions to other rows retain it. Undo inserts by the specified neighbor priority and consumes the record; an old numeric index cannot reliably locate the row. A missing target or undo without a record is invalid and leaves state unchanged. Before adding or copying, count active rows plus the undo-retained entity. Reject overflow without discarding the undo record to make room.
  4. Trace small state sequences. A and B share a title. Edit B, then reorder to B, A: B keeps its draft. Filter B out and back in: its draft survives. Delete B, then add D: undo restores B’s original ID and draft without overwriting D. Delete B, then A: only A can be restored and B’s old record can be released. Copy B to C: later edits to C do not change B. At capacity, deleting B leaves 999 active rows plus one retained entity, so adding D is rejected; undo returns to 1,000 active rows. Alternatively, without undoing B, deleting A releases B’s old record, leaving 999 total entities and room for one addition.
  5. State the costs and guarantees. With amortized constant-time hash access and an array for order, insertion, deletion, neighbor lookup and a full filtering pass are O(n) in the worst case. Entities, drafts, order and one undo record take O(n) space, with total text size counted separately. React rendering and character processing are not constant-time guarantees. This model governs state within one page; it does not persist it across a reload.