Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add useDocumentForm hook #8557

Open
wants to merge 11 commits into
base: next
Choose a base branch
from
Open

Conversation

pedrobonamin
Copy link
Contributor

@pedrobonamin pedrobonamin commented Feb 10, 2025

Description

This PR implements a new hook useDocumentForm which handles the necessary logic to build a form and pass the values to the <FormProvider>

It also simplifies the logic inside the <DocumentPaneProvider> to make it easier to digest:

Further steps:

  • update sanity assist to use this and deprecate the forcedVersion in DocumentPaneProvider
  • refactor the inspect legacy dialog into the new inspectors logic suggestion

Why we need this?

Currently if you want to render a form using the patch mechanism, have presence, validation and compare values you would need to reuse the <DocumentPaneProvider> which bring with it some unnecessary things like history , actions, perspective etc, or you needed to write it in your own like in useTasksFormBuilder. This is not ideal and it takes a lot of effort from the developer.

By implementing this hook you can now do use this minimal implementation to get all the necessary form values and handlers. (see example in code)

const form = useDocumentForm({documentId, documentType, initialValue})

This should also be useful for:

What to review

Please help me review in detail this PR as it touches a critical surface which handles a lot of logic, my recommendation is going commit per commit to see how the changes build on top of each other.

Is the naming of the new useDocumentForm hook good?

Testing

Manual testing has been done to the PR, focusing on history, versions and tasks which are not covered by e2e tests.
Existing tests should cover the rest of the functionality

Notes for release

adds useDocumentForm hook

@pedrobonamin pedrobonamin requested review from a team as code owners February 10, 2025 10:31
@pedrobonamin pedrobonamin requested review from cngonzalez and removed request for a team February 10, 2025 10:31
Copy link

vercel bot commented Feb 10, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
page-building-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 21, 2025 10:16am
performance-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 21, 2025 10:16am
test-studio ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 21, 2025 10:16am
2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
studio-workshop ⬜️ Ignored (Inspect) Visit Preview Feb 21, 2025 10:16am
test-next-studio ⬜️ Ignored (Inspect) Feb 21, 2025 10:16am

Copy link
Contributor

No changes to documentation

Copy link
Contributor

github-actions bot commented Feb 10, 2025

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 42.98% 55016 / 127975
🔵 Statements 42.98% 55016 / 127975
🔵 Functions 48.09% 2796 / 5813
🔵 Branches 79.51% 10655 / 13400
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/sanity/src/core/form/index.ts 100% 100% 100% 100%
packages/sanity/src/core/form/useDocumentForm.ts 6.7% 100% 0% 6.7% 110-466, 469-486
packages/sanity/src/core/i18n/bundles/studio.ts 100% 100% 100% 100%
packages/sanity/src/core/tasks/components/form/tasksFormBuilder/TasksFormBuilder.tsx 20.56% 100% 0% 20.56% 27-35, 49-115, 123-173
packages/sanity/src/structure/i18n/resources.ts 100% 100% 100% 100%
packages/sanity/src/structure/panes/document/DocumentPaneContext.ts 0% 0% 0% 0%
packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx 3.78% 100% 100% 3.78% 52-513
packages/sanity/src/structure/panes/document/useDocumentPaneInitialValue.ts 25% 100% 0% 25% 19-44
packages/sanity/src/structure/panes/document/useDocumentPaneInspector.ts 4.05% 100% 0% 4.05% 11-193
packages/sanity/src/structure/panes/document/document-layout/DocumentLayout.tsx 13.48% 100% 0% 13.48% 67-294
packages/sanity/src/structure/panes/document/statusBar/DocumentStatusBarActions.tsx 12.72% 100% 0% 12.72% 30-93, 96-142, 145-175, 178-181
Generated in workflow #30809 for commit f391abb by the Vitest Coverage Report Action

Copy link
Contributor

github-actions bot commented Feb 10, 2025

Component Testing Report Updated Feb 21, 2025 10:27 AM (UTC)

❌ Failed Tests (6) -- expand for details
File Status Duration Passed Skipped Failed
comments/CommentInput.spec.tsx ✅ Passed (Inspect) 1m 17s 15 0 0
formBuilder/ArrayInput.spec.tsx ✅ Passed (Inspect) 15s 3 0 0
formBuilder/inputs/PortableText/Annotations.spec.tsx ❌ Failed (Inspect) 2m 6s 4 0 2
formBuilder/inputs/PortableText/copyPaste/CopyPaste.spec.tsx ✅ Passed (Inspect) 59s 11 7 0
formBuilder/inputs/PortableText/copyPaste/CopyPasteFields.spec.tsx ✅ Passed (Inspect) 0s 0 12 0
formBuilder/inputs/PortableText/Decorators.spec.tsx ✅ Passed (Inspect) 30s 6 0 0
formBuilder/inputs/PortableText/DisableFocusAndUnset.spec.tsx ✅ Passed (Inspect) 17s 3 0 0
formBuilder/inputs/PortableText/DragAndDrop.spec.tsx ✅ Passed (Inspect) 31s 6 0 0
formBuilder/inputs/PortableText/FocusTracking.spec.tsx ❌ Failed (Inspect) 2m 37s 13 0 2
formBuilder/inputs/PortableText/Input.spec.tsx ✅ Passed (Inspect) 1m 53s 21 0 0
formBuilder/inputs/PortableText/ObjectBlock.spec.tsx ❌ Failed (Inspect) 3m 38s 19 0 2
formBuilder/inputs/PortableText/PresenceCursors.spec.tsx ✅ Passed (Inspect) 15s 3 9 0
formBuilder/inputs/PortableText/Styles.spec.tsx ✅ Passed (Inspect) 30s 6 0 0
formBuilder/inputs/PortableText/Toolbar.spec.tsx ✅ Passed (Inspect) 2m 0s 21 0 0
formBuilder/tree-editing/TreeEditing.spec.tsx ✅ Passed (Inspect) 0s 0 3 0
formBuilder/tree-editing/TreeEditingNestedObjects.spec.tsx ✅ Passed (Inspect) 0s 0 3 0

Copy link
Contributor

github-actions bot commented Feb 10, 2025

⚡️ Editor Performance Report

Updated Fri, 21 Feb 2025 10:22:14 GMT

Benchmark reference
latency of sanity@latest
experiment
latency of this branch
Δ (%)
latency difference
article (title) 23.8 efps (42ms) 26.3 efps (38ms) -4ms (-9.5%)
article (body) 72.5 efps (14ms) 67.6 efps (15ms) +1ms (-/-%)
article (string inside object) 28.6 efps (35ms) 27.0 efps (37ms) +2ms (+5.7%)
article (string inside array) 25.0 efps (40ms) 24.4 efps (41ms) +1ms (+2.5%)
recipe (name) 46.5 efps (22ms) 50.0 efps (20ms) -2ms (-7.0%)
recipe (description) 55.6 efps (18ms) 55.6 efps (18ms) +0ms (-/-%)
recipe (instructions) 99.9+ efps (6ms) 99.9+ efps (5ms) -1ms (-/-%)
synthetic (title) 20.6 efps (49ms) 21.1 efps (48ms) -1ms (-2.1%)
synthetic (string inside object) 20.8 efps (48ms) 20.4 efps (49ms) +1ms (+2.1%)

efps — editor "frames per second". The number of updates assumed to be possible within a second.

Derived from input latency. efps = 1000 / input_latency

Detailed information

🏠 Reference result

The performance result of sanity@latest

Benchmark latency p75 p90 p99 blocking time test duration
article (title) 42ms 48ms 63ms 508ms 936ms 12.1s
article (body) 14ms 15ms 21ms 172ms 221ms 5.6s
article (string inside object) 35ms 38ms 44ms 187ms 219ms 6.7s
article (string inside array) 40ms 42ms 46ms 152ms 135ms 6.7s
recipe (name) 22ms 23ms 27ms 45ms 0ms 7.1s
recipe (description) 18ms 20ms 22ms 38ms 0ms 4.7s
recipe (instructions) 6ms 8ms 9ms 26ms 0ms 3.2s
synthetic (title) 49ms 52ms 59ms 220ms 580ms 12.7s
synthetic (string inside object) 48ms 53ms 60ms 475ms 941ms 7.9s

🧪 Experiment result

The performance result of this branch

Benchmark latency p75 p90 p99 blocking time test duration
article (title) 38ms 40ms 44ms 394ms 138ms 10.8s
article (body) 15ms 18ms 36ms 78ms 375ms 6.0s
article (string inside object) 37ms 40ms 46ms 155ms 134ms 6.6s
article (string inside array) 41ms 45ms 51ms 142ms 237ms 7.0s
recipe (name) 20ms 22ms 24ms 50ms 0ms 7.1s
recipe (description) 18ms 19ms 21ms 38ms 0ms 4.6s
recipe (instructions) 5ms 6ms 8ms 11ms 0ms 3.2s
synthetic (title) 48ms 52ms 59ms 226ms 517ms 12.5s
synthetic (string inside object) 49ms 51ms 59ms 295ms 586ms 7.5s

📚 Glossary

column definitions

  • benchmark — the name of the test, e.g. "article", followed by the label of the field being measured, e.g. "(title)".
  • latency — the time between when a key was pressed and when it was rendered. derived from a set of samples. the median (p50) is shown to show the most common latency.
  • p75 — the 75th percentile of the input latency in the test run. 75% of the sampled inputs in this benchmark were processed faster than this value. this provides insight into the upper range of typical performance.
  • p90 — the 90th percentile of the input latency in the test run. 90% of the sampled inputs were faster than this. this metric helps identify slower interactions that occurred less frequently during the benchmark.
  • p99 — the 99th percentile of the input latency in the test run. only 1% of sampled inputs were slower than this. this represents the worst-case scenarios encountered during the benchmark, useful for identifying potential performance outliers.
  • blocking time — the total time during which the main thread was blocked, preventing user input and UI updates. this metric helps identify performance bottlenecks that may cause the interface to feel unresponsive.
  • test duration — how long the test run took to complete.

Copy link
Member

@bjoerge bjoerge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very happy to some decoupling of the document pane provider. Wondering if there are some improvements to be made on naming, but otherwise this looks good to me.

*
* Use this as a base point to create your own form.
*/
export function useCreateForm(options: CreateFormOptions): CreateFormValue {
Copy link
Member

@bjoerge bjoerge Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure about the naming here. Why "create" form? Could it be named "useDocumentForm" instead?

Copy link
Contributor Author

@pedrobonamin pedrobonamin Feb 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not married with the name.
I was thinking on create because it creates the handlers and states necessary for the <FormBuilder>
create ~ build.

We are exposing another hook that is useFormBuilder which gives you the values back from the formBuilder. Which when I read that name it gives me the idea that I can create a new form using the useFormBuilder, but not, it gives me the values from the provider.

Btw, the comment is incorrect, it should say:

Hook for creating a form state and combine it with the FormProvider FormBuilder.

documentId: string
documentType: string
}) {
const {setParams: setPaneParams} = usePaneRouter()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the passed params also coming from usePaneRouter() ? Wondering if setParams could then be passed in as well? Or if we could get params from the hook

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I'll pass it.
In the first implementation it was using them from the usePaneRouter but then it requires to do here the to use the useUnique which ends up creating a new state for the params, which we reuse in multiple places, so it was better to pass it.

collapsedPaths,
schemaType,
value,
} = useCreateForm({documentId, documentType: 'tasks.task', initialValue})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice that you can now do this 🎉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could have save me a lot of work last year , hope it saves some time for us in the future

@pedrobonamin pedrobonamin requested a review from bjoerge February 17, 2025 10:46
const handleChange = useCallback((event: PatchEvent) => patchRef.current(event), [])

useInsertionEffect(() => {
if (readOnly) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now needs the change from #8691

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebasesd the branch and added here, thank you for the reminder.

*/
getFormDocumentValue?: (value: SanityDocumentLike) => SanityDocumentLike
}
interface CreateFormValue {
Copy link
Member

@bjoerge bjoerge Feb 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to DocumentFormProps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching.
Why Props and not Value ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants