State management in the Editor Starter
State in the Editor Starter is managed with the built-in React state management utilities: useState()
and useContext()
.
Why?
The Editor Starter was built in a way that as many different teams can adopt it as easily as possible.
While the default React state management utilities are controversial for some, any React application can make use of them and developers are most familiar with them.
Shape
The whole state of the Editor Starter is stored in a single object with the shape defined by the EditorState
.
src/editor/state/types.tsts
export type UndoableState = {tracks: TrackType[];assets: Record<string, EditorStarterAsset>;items: Record<string, EditorStarterItem>;fps: number;compositionWidth: number;compositionHeight: number;};export type EditorState = {undoableState: UndoableState;selectedItems: string[];textItemEditing: string | null;textItemHoverPreview: TextItemHoverPreview | null;rendering: RenderState;captions: CaptionState;initialized: boolean;itemsBeingTrimmed: ItemBeingTrimmed[];loop: boolean;};
undoableState
- State that is affected by the undo stacktracks
: An array of timeline tracks, the last ones are the ones rendered in the back.assets
: A map of all assets that have been uploaded to the editor.items
: A map of all items that have been added to the editor.fps
: The frame rate (kept in state, but no UI is exposed to change it)compositionWidth
: The width of the canvascompositionHeight
: The height of the canvas
selectedItems
: An array of item IDs that are currently selectedtextItemEditing
: The ID of the text item that is currently being edited, if there is onetextItemHoverPreview
: Preview updates of a text item (for example if a font is hovered in the font picker, the text will render temporarily with the font hovered)rendering
: The state of the rendering processcaptions
: The state of captioning processinitialized
: Whether the editor has been initialized, if not initialized, canvas will not be visibleitemsBeingTrimmed
: An array of items that are currently being trimmed, to show an indication of the maximum trim that is possibleloop
: Whether the playback should loop
Undoable state
State is separated into undoable and non-undoable parts.
Undoable state is located within the undoableState
object of the root state.
Undoable state may be:
- Position, size and other properties of an item
- Assets and tracks
- Video properties like dimensions and frame rate
Non-undoable state may be:
- Asset upload progress
- Captioning progress
- Zoom level
- Rendering state
- Selection state
See also: Undo and Redo
Contexts
In src/editor-context-provider.tsx
, you will see a very deeply nested tree of various context providers.
This is intentional and achieves that when updating a portion of the state, only the components that are dependent on that portion of the state will re-render, while the rest of the components will not.
For the best performance, we recommend that you continue to use this pattern in your own application.
Preventing state updates when nothing has changed
You should prevent an unnecessary state update when nothing has changed.
- This is better for performance
- This will prevent adding a snapshot to the undo stack, which would lead to clicking the undo button once with no effect
Throughout the codebase, you will see checks that prevent an unnecessary state update.
Preventing an identity changets
export const markAsDragging = (state: EditorState, itemId: string): EditorState => {return changeItem(state, itemId, (item) => {if (item.isDragging) {// The item would not change, so we return the original objectreturn item;}return {...item,isDragging: true,};});};
Reading the state imperatively
If you only need to access the state upon an interaction, you can use the useCurrentStateAsRef()
hook.
It allows you to imperatively access the state when you need it.
You cannot build a reactive UI with it, but is more performant than using a hook that re-renders when the state changes.
For example: A save button that only needs to access the state when the user clicks it.