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

Add (Client) Functions as Form Actions #26674

Merged
merged 6 commits into from
Apr 19, 2023

Conversation

sebmarkbage
Copy link
Collaborator

This lets you pass a function to <form action={...}> or <button formAction={...}> or <input type="submit formAction={...}>. This will behave basically like a javascript: URL except not quite implemented that way. This is a convenience for the onSubmit={e => { e.preventDefault(); const fromData = new FormData(e.target); ... } pattern.

You can still implement a custom onSubmit handler and if it calls preventDefault, it won't invoke the action, just like it would if you used a full page form navigation or javascript urls. It behaves just like a navigation and we might implement it with the Navigation API in the future.

Currently this is just a synchronous function but in a follow up this will accept async functions, handle pending states and handle errors.

This is implemented by setting javascript: URLs, but these only exist to trigger an error message if something goes wrong instead of navigating away. Like if you called stopPropagation to prevent React from handling it or if you called form.submit() instead of form.requestSubmit() which by-passes the submit event. If CSP is used to ban javascript: urls, those will trigger errors when these URLs are invoked which would be a different error message but it's still there to notify the user that something went wrong in the plumbing.

Next up is improving the SSR state with action replaying and progressive enhancement.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Apr 19, 2023
Copy link
Collaborator

@acdlite acdlite left a comment

Choose a reason for hiding this comment

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

Stamping the parts I’m familiar with, will let @sophiebits handle the rest

@react-sizebot
Copy link

react-sizebot commented Apr 19, 2023

Comparing: 1f248bd...5a7629d

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js +0.08% 164.41 kB 164.54 kB +0.03% 51.68 kB 51.69 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.55% 166.77 kB 167.68 kB +0.61% 52.32 kB 52.64 kB
facebook-www/ReactDOM-prod.classic.js +0.41% 563.69 kB 566.00 kB +0.48% 99.34 kB 99.81 kB
facebook-www/ReactDOM-prod.modern.js +0.42% 547.43 kB 549.73 kB +0.51% 96.62 kB 97.11 kB
facebook-www/ReactDOMServer-prod.modern.js +3.45% 128.34 kB 132.77 kB +2.51% 24.26 kB 24.87 kB
facebook-www/ReactDOMServer-prod.classic.js +3.35% 131.91 kB 136.33 kB +2.45% 24.96 kB 25.57 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +3.32% 133.21 kB 137.64 kB +2.46% 25.51 kB 26.14 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +2.64% 356.33 kB 365.72 kB +1.80% 77.88 kB 79.28 kB
facebook-www/ReactDOMServer-dev.modern.js +2.60% 361.42 kB 370.81 kB +1.74% 79.17 kB 80.54 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +2.56% 349.86 kB 358.82 kB +1.73% 78.08 kB 79.44 kB
oss-experimental/react-dom/umd/react-dom-server.browser.development.js +2.56% 368.68 kB 378.11 kB +1.72% 79.60 kB 80.97 kB
oss-experimental/react-dom/umd/react-dom-server-legacy.browser.development.js +2.55% 369.53 kB 378.96 kB +1.73% 79.49 kB 80.86 kB
oss-experimental/react-dom/cjs/react-dom-static.browser.development.js +2.55% 351.35 kB 360.30 kB +1.72% 78.58 kB 79.93 kB
oss-experimental/react-dom/cjs/react-dom-static.edge.development.js +2.55% 351.48 kB 360.44 kB +1.72% 78.63 kB 79.98 kB
facebook-www/ReactDOMServer-dev.classic.js +2.55% 368.84 kB 378.23 kB +1.70% 80.79 kB 82.16 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +2.54% 352.04 kB 360.99 kB +1.72% 78.76 kB 80.12 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +2.54% 352.18 kB 361.13 kB +1.72% 78.81 kB 80.16 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.54% 352.87 kB 361.82 kB +1.72% 78.68 kB 80.03 kB
oss-experimental/react-dom/cjs/react-dom-static.node.development.js +2.53% 353.37 kB 362.32 kB +1.73% 78.84 kB 80.21 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +2.53% 353.41 kB 362.36 kB +1.73% 78.76 kB 80.12 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +2.52% 354.62 kB 363.58 kB +1.70% 79.14 kB 80.49 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +2.52% 56.85 kB 58.28 kB +2.55% 16.76 kB 17.19 kB
oss-experimental/react-dom/umd/react-dom-server-legacy.browser.production.min.js +2.50% 57.02 kB 58.44 kB +2.41% 16.99 kB 17.39 kB
oss-experimental/react-dom/cjs/react-dom-static.browser.production.min.js +2.48% 57.44 kB 58.87 kB +2.38% 17.59 kB 18.00 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.min.js +2.48% 57.56 kB 58.99 kB +2.34% 17.64 kB 18.05 kB
oss-experimental/react-dom/cjs/react-dom-static.edge.production.min.js +2.48% 57.63 kB 59.05 kB +2.36% 17.63 kB 18.05 kB
oss-experimental/react-dom/umd/react-dom-server.browser.production.min.js +2.47% 57.73 kB 59.16 kB +2.49% 17.82 kB 18.27 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.min.js +2.36% 60.46 kB 61.89 kB +2.23% 18.22 kB 18.63 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.min.js +2.32% 61.49 kB 62.92 kB +2.27% 18.26 kB 18.67 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.min.js +2.32% 61.58 kB 63.01 kB +2.48% 18.97 kB 19.44 kB
oss-stable-semver/react-dom/umd/react-dom-server-legacy.browser.production.min.js +2.31% 56.68 kB 57.99 kB +2.40% 16.86 kB 17.26 kB
oss-stable/react-dom/umd/react-dom-server-legacy.browser.production.min.js +2.31% 56.71 kB 58.02 kB +2.41% 16.88 kB 17.29 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +2.31% 56.52 kB 57.82 kB +2.42% 16.63 kB 17.03 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +2.31% 56.54 kB 57.84 kB +2.42% 16.65 kB 17.05 kB
oss-experimental/react-dom/cjs/react-dom-static.node.production.min.js +2.29% 61.76 kB 63.18 kB +2.48% 19.03 kB 19.50 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.min.js +2.29% 61.80 kB 63.22 kB +2.22% 19.03 kB 19.45 kB
oss-stable-semver/react-dom/umd/react-dom-server.browser.production.min.js +2.29% 57.40 kB 58.71 kB +2.14% 17.73 kB 18.11 kB
oss-stable/react-dom/umd/react-dom-server.browser.production.min.js +2.29% 57.42 kB 58.74 kB +2.14% 17.75 kB 18.13 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.min.js +2.29% 57.23 kB 58.54 kB +2.22% 17.51 kB 17.90 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.min.js +2.29% 57.26 kB 58.57 kB +2.21% 17.54 kB 17.92 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.min.js +2.17% 60.06 kB 61.37 kB +2.08% 18.07 kB 18.45 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.min.js +2.17% 60.09 kB 61.39 kB +2.08% 18.09 kB 18.47 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.min.js +2.14% 61.19 kB 62.50 kB +2.28% 18.82 kB 19.24 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.min.js +2.14% 61.21 kB 62.52 kB +2.27% 18.84 kB 19.27 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.min.js +2.13% 61.09 kB 62.39 kB +2.13% 18.10 kB 18.49 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.min.js +2.13% 61.11 kB 62.42 kB +2.13% 18.13 kB 18.51 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.min.js +2.12% 61.40 kB 62.71 kB +2.04% 18.88 kB 19.27 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.min.js +2.12% 61.43 kB 62.73 kB +2.04% 18.91 kB 19.29 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
facebook-www/ReactDOMServer-prod.modern.js +3.45% 128.34 kB 132.77 kB +2.51% 24.26 kB 24.87 kB
facebook-www/ReactDOMServer-prod.classic.js +3.35% 131.91 kB 136.33 kB +2.45% 24.96 kB 25.57 kB
facebook-www/ReactDOMServerStreaming-prod.modern.js +3.32% 133.21 kB 137.64 kB +2.46% 25.51 kB 26.14 kB
facebook-www/ReactDOMServerStreaming-dev.modern.js +2.64% 356.33 kB 365.72 kB +1.80% 77.88 kB 79.28 kB
facebook-www/ReactDOMServer-dev.modern.js +2.60% 361.42 kB 370.81 kB +1.74% 79.17 kB 80.54 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.development.js +2.56% 349.86 kB 358.82 kB +1.73% 78.08 kB 79.44 kB
oss-experimental/react-dom/umd/react-dom-server.browser.development.js +2.56% 368.68 kB 378.11 kB +1.72% 79.60 kB 80.97 kB
oss-experimental/react-dom/umd/react-dom-server-legacy.browser.development.js +2.55% 369.53 kB 378.96 kB +1.73% 79.49 kB 80.86 kB
oss-experimental/react-dom/cjs/react-dom-static.browser.development.js +2.55% 351.35 kB 360.30 kB +1.72% 78.58 kB 79.93 kB
oss-experimental/react-dom/cjs/react-dom-static.edge.development.js +2.55% 351.48 kB 360.44 kB +1.72% 78.63 kB 79.98 kB
facebook-www/ReactDOMServer-dev.classic.js +2.55% 368.84 kB 378.23 kB +1.70% 80.79 kB 82.16 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.development.js +2.54% 352.04 kB 360.99 kB +1.72% 78.76 kB 80.12 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.development.js +2.54% 352.18 kB 361.13 kB +1.72% 78.81 kB 80.16 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.development.js +2.54% 352.87 kB 361.82 kB +1.72% 78.68 kB 80.03 kB
oss-experimental/react-dom/cjs/react-dom-static.node.development.js +2.53% 353.37 kB 362.32 kB +1.73% 78.84 kB 80.21 kB
oss-experimental/react-dom/cjs/react-dom-server.node.development.js +2.53% 353.41 kB 362.36 kB +1.73% 78.76 kB 80.12 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.development.js +2.52% 354.62 kB 363.58 kB +1.70% 79.14 kB 80.49 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +2.52% 56.85 kB 58.28 kB +2.55% 16.76 kB 17.19 kB
oss-experimental/react-dom/umd/react-dom-server-legacy.browser.production.min.js +2.50% 57.02 kB 58.44 kB +2.41% 16.99 kB 17.39 kB
oss-experimental/react-dom/cjs/react-dom-static.browser.production.min.js +2.48% 57.44 kB 58.87 kB +2.38% 17.59 kB 18.00 kB
oss-experimental/react-dom/cjs/react-dom-server.browser.production.min.js +2.48% 57.56 kB 58.99 kB +2.34% 17.64 kB 18.05 kB
oss-experimental/react-dom/cjs/react-dom-static.edge.production.min.js +2.48% 57.63 kB 59.05 kB +2.36% 17.63 kB 18.05 kB
oss-experimental/react-dom/umd/react-dom-server.browser.production.min.js +2.47% 57.73 kB 59.16 kB +2.49% 17.82 kB 18.27 kB
oss-experimental/react-dom/cjs/react-dom-server.bun.production.min.js +2.36% 60.46 kB 61.89 kB +2.23% 18.22 kB 18.63 kB
oss-experimental/react-dom/cjs/react-dom-server-legacy.node.production.min.js +2.32% 61.49 kB 62.92 kB +2.27% 18.26 kB 18.67 kB
oss-experimental/react-dom/cjs/react-dom-server.edge.production.min.js +2.32% 61.58 kB 63.01 kB +2.48% 18.97 kB 19.44 kB
oss-stable-semver/react-dom/umd/react-dom-server-legacy.browser.production.min.js +2.31% 56.68 kB 57.99 kB +2.40% 16.86 kB 17.26 kB
oss-stable/react-dom/umd/react-dom-server-legacy.browser.production.min.js +2.31% 56.71 kB 58.02 kB +2.41% 16.88 kB 17.29 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +2.31% 56.52 kB 57.82 kB +2.42% 16.63 kB 17.03 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.production.min.js +2.31% 56.54 kB 57.84 kB +2.42% 16.65 kB 17.05 kB
oss-experimental/react-dom/cjs/react-dom-static.node.production.min.js +2.29% 61.76 kB 63.18 kB +2.48% 19.03 kB 19.50 kB
oss-experimental/react-dom/cjs/react-dom-server.node.production.min.js +2.29% 61.80 kB 63.22 kB +2.22% 19.03 kB 19.45 kB
oss-stable-semver/react-dom/umd/react-dom-server.browser.production.min.js +2.29% 57.40 kB 58.71 kB +2.14% 17.73 kB 18.11 kB
oss-stable/react-dom/umd/react-dom-server.browser.production.min.js +2.29% 57.42 kB 58.74 kB +2.14% 17.75 kB 18.13 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.production.min.js +2.29% 57.23 kB 58.54 kB +2.22% 17.51 kB 17.90 kB
oss-stable/react-dom/cjs/react-dom-server.browser.production.min.js +2.29% 57.26 kB 58.57 kB +2.21% 17.54 kB 17.92 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.production.min.js +2.17% 60.06 kB 61.37 kB +2.08% 18.07 kB 18.45 kB
oss-stable/react-dom/cjs/react-dom-server.bun.production.min.js +2.17% 60.09 kB 61.39 kB +2.08% 18.09 kB 18.47 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.production.min.js +2.14% 61.19 kB 62.50 kB +2.28% 18.82 kB 19.24 kB
oss-stable/react-dom/cjs/react-dom-server.edge.production.min.js +2.14% 61.21 kB 62.52 kB +2.27% 18.84 kB 19.27 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.production.min.js +2.13% 61.09 kB 62.39 kB +2.13% 18.10 kB 18.49 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.production.min.js +2.13% 61.11 kB 62.42 kB +2.13% 18.13 kB 18.51 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.production.min.js +2.12% 61.40 kB 62.71 kB +2.04% 18.88 kB 19.27 kB
oss-stable/react-dom/cjs/react-dom-server.node.production.min.js +2.12% 61.43 kB 62.73 kB +2.04% 18.91 kB 19.29 kB
oss-stable-semver/react-dom/umd/react-dom-server.browser.development.js +1.61% 366.52 kB 372.42 kB +0.94% 79.05 kB 79.80 kB
oss-stable/react-dom/umd/react-dom-server.browser.development.js +1.61% 366.55 kB 372.44 kB +0.94% 79.08 kB 79.82 kB
oss-stable-semver/react-dom/umd/react-dom-server-legacy.browser.development.js +1.60% 367.37 kB 373.27 kB +0.94% 78.94 kB 79.69 kB
oss-stable/react-dom/umd/react-dom-server-legacy.browser.development.js +1.60% 367.40 kB 373.29 kB +0.94% 78.97 kB 79.71 kB
oss-stable-semver/react-dom/cjs/react-dom-server.bun.development.js +1.59% 347.81 kB 353.34 kB +0.91% 77.51 kB 78.21 kB
oss-stable/react-dom/cjs/react-dom-server.bun.development.js +1.59% 347.84 kB 353.37 kB +0.91% 77.53 kB 78.23 kB
oss-stable-semver/react-dom/cjs/react-dom-server.browser.development.js +1.58% 349.99 kB 355.52 kB +0.88% 78.19 kB 78.88 kB
oss-stable/react-dom/cjs/react-dom-server.browser.development.js +1.58% 350.01 kB 355.54 kB +0.88% 78.21 kB 78.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server.edge.development.js +1.58% 350.13 kB 355.66 kB +0.88% 78.24 kB 78.93 kB
oss-stable/react-dom/cjs/react-dom-server.edge.development.js +1.58% 350.15 kB 355.68 kB +0.89% 78.26 kB 78.95 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.browser.development.js +1.58% 350.82 kB 356.35 kB +0.89% 78.10 kB 78.80 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.browser.development.js +1.58% 350.84 kB 356.37 kB +0.90% 78.12 kB 78.82 kB
oss-stable-semver/react-dom/cjs/react-dom-server.node.development.js +1.57% 351.36 kB 356.89 kB +0.89% 78.19 kB 78.88 kB
oss-stable/react-dom/cjs/react-dom-server.node.development.js +1.57% 351.38 kB 356.91 kB +0.89% 78.21 kB 78.90 kB
oss-stable-semver/react-dom/cjs/react-dom-server-legacy.node.development.js +1.57% 352.57 kB 358.11 kB +0.89% 78.56 kB 79.26 kB
oss-stable/react-dom/cjs/react-dom-server-legacy.node.development.js +1.57% 352.60 kB 358.13 kB +0.89% 78.59 kB 79.28 kB
oss-experimental/react-dom/cjs/react-dom.development.js +0.70% 1,265.82 kB 1,274.62 kB +0.74% 278.36 kB 280.41 kB
oss-experimental/react-dom/umd/react-dom.development.js +0.69% 1,327.29 kB 1,336.46 kB +0.73% 281.08 kB 283.13 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.69% 1,283.93 kB 1,292.73 kB +0.72% 282.74 kB 284.78 kB
facebook-www/ReactDOM-dev.modern.js +0.68% 1,391.51 kB 1,401.00 kB +0.71% 300.17 kB 302.29 kB
facebook-www/ReactDOMTesting-dev.modern.js +0.67% 1,409.91 kB 1,419.40 kB +0.69% 304.64 kB 306.75 kB
facebook-www/ReactDOM-dev.classic.js +0.67% 1,419.40 kB 1,428.89 kB +0.69% 305.74 kB 307.86 kB
facebook-www/ReactDOMTesting-dev.classic.js +0.66% 1,437.80 kB 1,447.29 kB +0.65% 309.97 kB 312.00 kB
oss-experimental/react-dom/umd/react-dom.production.min.js +0.55% 166.67 kB 167.59 kB +0.62% 52.70 kB 53.03 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js +0.55% 166.77 kB 167.68 kB +0.61% 52.32 kB 52.64 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.min.js +0.53% 172.98 kB 173.89 kB +0.59% 54.65 kB 54.97 kB
oss-experimental/react-dom/umd/react-dom.profiling.min.js +0.52% 175.66 kB 176.57 kB +0.58% 55.03 kB 55.35 kB
oss-experimental/react-dom/cjs/react-dom.profiling.min.js +0.52% 176.40 kB 177.31 kB +0.57% 54.73 kB 55.04 kB
facebook-www/ReactDOM-prod.modern.js +0.42% 547.43 kB 549.73 kB +0.51% 96.62 kB 97.11 kB
facebook-www/ReactDOM-prod.classic.js +0.41% 563.69 kB 566.00 kB +0.48% 99.34 kB 99.81 kB
facebook-www/ReactDOMTesting-prod.modern.js +0.41% 563.97 kB 566.27 kB +0.49% 100.81 kB 101.30 kB
facebook-www/ReactDOM-profiling.modern.js +0.40% 577.86 kB 580.17 kB +0.49% 101.12 kB 101.62 kB
facebook-www/ReactDOMTesting-prod.classic.js +0.40% 578.17 kB 580.48 kB +0.46% 103.02 kB 103.50 kB
facebook-www/ReactDOM-profiling.classic.js +0.39% 594.20 kB 596.51 kB +0.46% 103.87 kB 104.35 kB
oss-stable-semver/react-dom/cjs/react-dom.development.js +0.29% 1,252.40 kB 1,256.03 kB +0.24% 276.20 kB 276.87 kB
oss-stable/react-dom/cjs/react-dom.development.js +0.29% 1,252.43 kB 1,256.05 kB +0.24% 276.23 kB 276.90 kB
oss-stable-semver/react-dom/umd/react-dom.development.js +0.29% 1,313.17 kB 1,316.93 kB +0.22% 279.12 kB 279.74 kB
oss-stable/react-dom/umd/react-dom.development.js +0.29% 1,313.19 kB 1,316.95 kB +0.22% 279.15 kB 279.77 kB

Generated by 🚫 dangerJS against 5a7629d

@@ -377,6 +475,50 @@ function setProp(
domElement.setAttribute(key, sanitizedValue);
break;
}
case 'action':
case 'formAction': {
// TODO: Consider moving these special cases to the form, input and button tags.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I decided to leave these here for now and not special case per tag. To keep the code smaller. Ideally these should follow the pattern of input, select and textarea instead.

That way we could more easily clear out props like target, method and encType when they're not relevant or set them to what they're supposed to be. That's what Fizz does. That probably also lead to hydration warnings but they also warn just in general.

"consider using form.requestSubmit() instead. If you're trying to use " +
'event.stopPropagation() in a submit event handler, consider also calling ' +
'event.preventDefault().' +
"')",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This doesn't get minified but it doesn't have access to the shared helper anyway so we can't really do that the same way anyway. We could consider a shorter message which is what I did for SSR.

@sebmarkbage sebmarkbage force-pushed the formactions branch 4 times, most recently from a14141b to ffee4ce Compare April 19, 2023 18:57
Buttons with actions have to be wrapped in form elements. They can't be
standalone unfortunately because it's the act of submitting the form
that triggers the action.

This also ensures that the progressive enhancement can work properly too.
Copy link
Collaborator

@sophiebits sophiebits left a comment

Choose a reason for hiding this comment

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

when do enctype method target get used? how is that layered?

edit: oh right for server references

fixtures/flight/src/Button.js Show resolved Hide resolved
didWarnFormActionTarget = true;
console.error(
'Cannot specify a target for a form that specifies a function as the action. ' +
'The function will always be executed in the same scope.',
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe "same window" would be clearer?

@@ -635,6 +636,83 @@ function pushStringAttribute(
}
}

// Since this will likely be repeated a lot in the HTML, we use a more concise message
Copy link
Collaborator

@sophiebits sophiebits Apr 19, 2023

Choose a reason for hiding this comment

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

could make a $RF helper for this I guess?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oh you mean to call a global. Yea, but this might also be opened in new tabs etc. which is annoying.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I suppose it doesn't matter much if the error message isn't great for that case.

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah it's fine

innerHTML = propValue;
break;
case 'action':
formAction = propValue;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: confusing to call these variables form_

continue;
} else if (hasFormActionURL) {
extraAttributes.delete(propKey.toLowerCase());
warnForPropDifference(propKey, 'function', value);
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: quotes in the warning :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The reason I did it this way for now, instead of a custom warning, is that we probably want to join all the differences together in a larger diff. So this will have to change as part of that. We could pass a fake function or something to indicate how this should be rendered I guess.

This can still possibly have a null formAction in which case React still
handles it.
const root = ReactDOMClient.createRoot(container);
await act(async () => {
root.render(
<form
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If the form is not a React element is already handled by the event system itself.

@sebmarkbage sebmarkbage merged commit c826dc5 into facebook:main Apr 19, 2023
2 checks passed
kassens pushed a commit that referenced this pull request Apr 21, 2023
This lets you pass a function to `<form action={...}>` or `<button
formAction={...}>` or `<input type="submit formAction={...}>`. This will
behave basically like a `javascript:` URL except not quite implemented
that way. This is a convenience for the `onSubmit={e => {
e.preventDefault(); const fromData = new FormData(e.target); ... }`
pattern.

You can still implement a custom `onSubmit` handler and if it calls
`preventDefault`, it won't invoke the action, just like it would if you
used a full page form navigation or javascript urls. It behaves just
like a navigation and we might implement it with the Navigation API in
the future.

Currently this is just a synchronous function but in a follow up this
will accept async functions, handle pending states and handle errors.

This is implemented by setting `javascript:` URLs, but these only exist
to trigger an error message if something goes wrong instead of
navigating away. Like if you called `stopPropagation` to prevent React
from handling it or if you called `form.submit()` instead of
`form.requestSubmit()` which by-passes the `submit` event. If CSP is
used to ban `javascript:` urls, those will trigger errors when these
URLs are invoked which would be a different error message but it's still
there to notify the user that something went wrong in the plumbing.

Next up is improving the SSR state with action replaying and progressive
enhancement.
// late. The easiest way to do this is to switch the form field to hidden,
// which is always included, and then back again. This does means that this
// is observable from the formdata event though.
// TODO: This tricky doesn't work on button elements. Consider inserting
Copy link
Contributor

Choose a reason for hiding this comment

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

You might consider leveraging https://www.npmjs.com/package/formdata-submitter-polyfill or borrowing its approach ... in addition to handling <button>, it also does the right thing with <input type="image">

Copy link
Contributor

Choose a reason for hiding this comment

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

Or alternatively don't attempt to emulate it at all, but instead just pass the submitter to the constructor so that new browsers do the right thing for all submitter types, and if developers want to support old browsers they can polyfill it.

// is observable from the formdata event though.
// TODO: This tricky doesn't work on button elements. Consider inserting
// a fake node instead for that case.
// TODO: FormData takes a second argument that it's the submitter but this
Copy link
Contributor

Choose a reason for hiding this comment

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

This is available in the latest versions of all major browsers now: https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#browser_compatibility ... should be available in jest+jsdom very soon too 🤞

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Unfortunately not all browsers are truly evergreen. We support a lot of old Samsung TVs and many iOS users don't upgrade for years. So we can't use it yet. Not worth the added code to do the image one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Contributor

@jenseng jenseng Apr 26, 2023

Choose a reason for hiding this comment

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

Edit GitHub didn't show me your second message when I wrote this reply, web apps are hard 😆

That makes sense, thanks for the added context 👍

Though if you're supporting old browsers, many don't support SubmitEvent so you won't have a submitter here in the first place. And for modern browsers, a typical submitter won't work with the type="hidden" hack (i.e. IME <button> is much more prevalent than <input type=submit>). edit new way looks more robust 👍

Since browsers that support SubmitEvent are fairly likely to be evergreen, would it be better to just remove the hack altogether and pass the submitter (if any) to FormData? Then you'd get more correct 1 behavior with less code.

Footnotes

  1. In addition to not supporting image button submitters, the latest approach also doesn't support form-associated submitters that aren't descendants of the form (e.g. <form id=foo>...</form><button form=foo name=outside />)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't support very old, like we don't support IE anymore, however we support somewhat old browser like TVs and iOS versions a year or so old. Point taken that maybe it's not even enough to rely on the submitter property on the event.

I'd expect us to only keep this hack around for maybe 6 months or so until we switch to the native version given this awkward transition stage.

EdisonVan pushed a commit to EdisonVan/react that referenced this pull request Apr 15, 2024
This lets you pass a function to `<form action={...}>` or `<button
formAction={...}>` or `<input type="submit formAction={...}>`. This will
behave basically like a `javascript:` URL except not quite implemented
that way. This is a convenience for the `onSubmit={e => {
e.preventDefault(); const fromData = new FormData(e.target); ... }`
pattern.

You can still implement a custom `onSubmit` handler and if it calls
`preventDefault`, it won't invoke the action, just like it would if you
used a full page form navigation or javascript urls. It behaves just
like a navigation and we might implement it with the Navigation API in
the future.

Currently this is just a synchronous function but in a follow up this
will accept async functions, handle pending states and handle errors.

This is implemented by setting `javascript:` URLs, but these only exist
to trigger an error message if something goes wrong instead of
navigating away. Like if you called `stopPropagation` to prevent React
from handling it or if you called `form.submit()` instead of
`form.requestSubmit()` which by-passes the `submit` event. If CSP is
used to ban `javascript:` urls, those will trigger errors when these
URLs are invoked which would be a different error message but it's still
there to notify the user that something went wrong in the plumbing.

Next up is improving the SSR state with action replaying and progressive
enhancement.
bigfootjon pushed a commit that referenced this pull request Apr 18, 2024
This lets you pass a function to `<form action={...}>` or `<button
formAction={...}>` or `<input type="submit formAction={...}>`. This will
behave basically like a `javascript:` URL except not quite implemented
that way. This is a convenience for the `onSubmit={e => {
e.preventDefault(); const fromData = new FormData(e.target); ... }`
pattern.

You can still implement a custom `onSubmit` handler and if it calls
`preventDefault`, it won't invoke the action, just like it would if you
used a full page form navigation or javascript urls. It behaves just
like a navigation and we might implement it with the Navigation API in
the future.

Currently this is just a synchronous function but in a follow up this
will accept async functions, handle pending states and handle errors.

This is implemented by setting `javascript:` URLs, but these only exist
to trigger an error message if something goes wrong instead of
navigating away. Like if you called `stopPropagation` to prevent React
from handling it or if you called `form.submit()` instead of
`form.requestSubmit()` which by-passes the `submit` event. If CSP is
used to ban `javascript:` urls, those will trigger errors when these
URLs are invoked which would be a different error message but it's still
there to notify the user that something went wrong in the plumbing.

Next up is improving the SSR state with action replaying and progressive
enhancement.

DiffTrain build for commit c826dc5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants