useServerDraft.ts 1.1 KB

1234567891011121314151617181920212223242526272829303132333435
  1. import { useCallback, useEffect, useRef, useState } from 'react';
  2. export function useServerDraft<T>(
  3. server: T | undefined,
  4. clone: (value: T) => T,
  5. equals: (left: T, right: T) => boolean,
  6. ) {
  7. const cloneRef = useRef(clone);
  8. useEffect(() => {
  9. cloneRef.current = clone;
  10. });
  11. const [draft, setDraft] = useState<T | undefined>();
  12. const [baseline, setBaseline] = useState<T | undefined>();
  13. const [syncedServer, setSyncedServer] = useState<T | undefined>();
  14. const isDirty = draft !== undefined && (baseline === undefined || !equals(draft, baseline));
  15. // Adopting the server value during render (not in an effect) keeps the
  16. // returned draft and isDirty consistent within the very first render.
  17. if (server !== syncedServer) {
  18. setSyncedServer(server);
  19. if (server !== undefined) {
  20. setBaseline(server);
  21. const keepLocalEdits = isDirty && !equals(draft as T, server);
  22. if (!keepLocalEdits) setDraft(clone(server));
  23. }
  24. }
  25. const markSaved = useCallback((value: T) => {
  26. setBaseline(cloneRef.current(value));
  27. }, []);
  28. return { draft, setDraft, isDirty, markSaved };
  29. }