PageSourceSearch

https://shakacode.com/component---src-template-doc-article-tsx-con…g-features-forms-md-1c94b72a7ee47566daf5.js

js shakacode.com collected 2026-10-04 00:26:37 UTC 58,533 bytes, 2 lines download raw bytes

1"use strict";(self.webpackChunkgatsby_starter_default=self.webpackChunkgatsby_starter_default||[]).push([[1949],{95360:function(e,n,a){a.r(n),a.d(n,{Head:function(){return h},default:function(){return g}});var t=a(28453),s=a(96540);function l(e){const n=Object.assign({h1:"h1",a:"a",span:"span",p:"p",strong:"strong",ol:"ol",li:"li",h2:"h2",h3:"h3",ul:"ul",table:"table",thead:"thead",tr:"tr",th:"th",tbody:"tbody",td:"td"},(0,t.R)(),e.components);return s.createElement(s.Fragment,null,s.createElement(n.h1,{id:"forms-and-mutations-with-userailsform",style:{position:"relative"}},s.createElement(n.a,{href:"#forms-and-mutations-with-userailsform","aria-label":"forms and mutations with userailsform permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Forms and Mutations with ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useRailsForm</code>'}})),"\n",s.createElement(n.p,null,"Wiring a React form to a Rails controller by hand means reading the CSRF token,\nbuilding a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">fetch</code>'}})," call, serializing the body, mapping Rails model errors back\nonto fields, and tracking a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">processing</code>'}})," flag. ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useRailsForm</code>'}})," makes that\nturnkey while keeping Rails as the mutation layer: your form posts JSON to a\n",s.createElement(n.strong,null,"real Rails controller action")," — the same strong parameters, ActiveModel\nvalidations, and authorization a server-rendered form would use. No API layer,\nno client-side validation duplication, no new protocol."),"\n",s.createElement(n.p,null,"There are two pieces, both opt-in:"),"\n",s.createElement(n.ol,null,"\n",s.createElement(n.li,null,s.createElement(n.strong,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useRailsForm</code>'}}))," (npm package) — a React hook with ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setData</code>'}}),",\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">processing</code>'}}),", submit verbs, and automatic CSRF attachment."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ReactOnRails::Controller::FormResponders</code>'}}))," (gem) — a controller concern\nwhose ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">render_model_errors(record)</code>'}})," renders ActiveModel errors in the JSON\nshape the hook expects."),"\n"),"\n",s.createElement(n.p,null,"The contract between them is one blessed error shape:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token comment">// HTTP 422</span>\n<span class="token punctuation">{</span> <span class="token property">"errors"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"can\'t be blank"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token property">"email"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">
1"is invalid"</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"The hook works against ",s.createElement(n.strong,null,"any")," endpoint returning that shape; the concern is a\nconvenience, not a requirement."),"\n",s.createElement(n.p,null,"For the framework-level comparison with Next.js Server Actions and TanStack\nStart server functions, see\n",s.createElement(n.a,{href:"/linked-assets/376102a5118b595e05eafcc6fcbbda9c/mutations.md"},"Mutations without Server Actions"),"."),"\n",s.createElement(n.p,null,"Compatibility: ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">react-on-rails/useRailsForm</code>'}})," requires React 16.8 or newer\nbecause it is a hook. Apps still on React 16.0-16.7 can continue using the\npackage's other React 16 APIs, but this subpath throws a clear error until React\nis upgraded to a hooks-capable version."),"\n",s.createElement(n.h2,{id:"quick-start",style:{position:"relative"}},s.createElement(n.a,{href:"#quick-start","aria-label":"quick start permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Quick start"),"\n",s.createElement(n.h3,{id:"client",style:{position:"relative"}},s.createElement(n.a,{href:"#client","aria-label":"client permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Client"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="tsx"><pre class="language-tsx"><code class="language-tsx"><span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">\'react\'</span><span class="token punctuation">;</span>\n<span class="token keyword">import</span> <span class="token punctuation">{</span> useRailsForm <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">\'react-on-rails/useRailsForm\'</span><span class="token punctuation">;</span>\n\n<span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span> <span class="token function">ContactForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>\n  <span class="token keyword">const</span> form <span class="token operator">=</span> <span class="token function">useRailsForm</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">\'\'</span><span class="token punctuation">,</span> email<span class="token operator">:</span> <span class="token string">\'\'</span><span class="token punctuation">,</span> message<span class="token operator">:</span> <span class="token string">\'\'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n\n  <span class="token keyword">const</span> <span class="token function-variable function">handleSubmit</span> <span class="token operator">=</span> <span class="token punctuation">(</span>event<span class="token operator">:</span>
1 React<span class="token punctuation">.</span>FormEvent<span class="token operator">&lt;</span>HTMLFormElement<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n    event<span class="token punctuation">.</span><span class="token function">preventDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n    <span class="token keyword">void</span> form\n      <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">\'/contact_messages\'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>\n        <span class="token function-variable function">onSuccess</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> form<span class="token punctuation">.</span><span class="token function">reset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>\n      <span class="token punctuation">}</span><span class="token punctuation">)</span>\n      <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
1\n        form<span class="token punctuation">.</span><span class="token function">setError</span><span class="token punctuation">(</span><span class="token string">\'base\'</span><span class="token punctuation">,</span> <span class="token string">\'Something went wrong. Please try again.\'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n  <span class="token punctuation">}</span><span class="token punctuation">;</span>\n\n  <span class="token keyword">return</span> <span class="token punctuation">(</span>\n    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">onSubmit</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>handleSubmit<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">\n      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>data<span class="token punctuation">.</span>name<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=></span> form<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token string">\'name\'</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">\n      </span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>name<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>name<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text">\n\n      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>data<span class="token punctuation">.</span>email<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=></span> form<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token string">\'email\'</span><span class="token punctuation">,</span>
1 e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">\n      </span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>email<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>email<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text">\n\n      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>textarea</span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>data<span class="token punctuation">.</span>message<span class="token punctuation">}</span></span> <span class="token attr-name">onChange</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span> <span class="token operator">=></span> form<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token string">\'message\'</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token plain-text">\n      </span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>message<span class="token operator">?.</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span> <span class="token attr-name">className</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>error<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>errors<span class="token punctuation">.</span>message<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">></span></span><span class="token punctuation">}</span><span class="token plain-text">\n\n      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token attr-name">disabled</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>
1processing<span class="token punctuation">}</span></span><span class="token punctuation">></span></span><span class="token plain-text">\n        </span><span class="token punctuation">{</span>form<span class="token punctuation">.</span>processing <span class="token operator">?</span> <span class="token string">\'Sending…\'</span> <span class="token operator">:</span> <span class="token string">\'Send\'</span><span class="token punctuation">}</span><span class="token plain-text">\n      </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">\n    </span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span>\n  <span class="token punctuation">)</span><span class="token punctuation">;</span>\n<span class="token punctuation">}</span></code></pre></div>'}}),"\n",s.createElement(n.h3,{id:"server",style:{position:"relative"}},s.createElement(n.a,{href:"#server","aria-label":"server permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Server"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token keyword">class</span> <span class="token class-name">ContactMessagesController</span> <span class="token operator">&lt;</span> ApplicationController\n  <span class="token keyword">include</span> ReactOnRails<span class="token double-colon punctuation">::</span>Controller<span class="token double-colon punctuation">::</span>FormResponders\n\n  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">create</span></span>\n    contact_message <span class="token operator">=</span> <span class="token class-name">ContactMessage</span><span class="token punctuation">.</span><span class="token keyword">new</span><span class="token punctuation">(</span>contact_message_params<span class="token punctuation">)</span>\n    <span class="token keyword">if</span> contact_message<span class="token punctuation">.</span>save\n      render json<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">message</span><span class="token operator">:</span> <span class="token string-literal"><span class="token string">"Thanks!"</span></span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token symbol">status</span><span class="token operator">:</span> <span class="token symbol">:created</span>\n    <span class="token keyword">else</span>\n      render_model_errors<span class="token punctuation">(</span>contact_message<span class="token punctuation">)</span> <span class="token comment"># 422 + { errors: { field: [messages] } }</span>\n    <span class="token keyword">end</span>\n  <span class="token keyword">end</span>\n\n  <span class="token keyword">private</span>\n\n  <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">contact_message_params</span></span>\n    <span class="token comment"># useRailsForm posts a flat JSON body; Rails JSON params wrapping (on by</span>\n    <span class="token comment"># default for new apps) may also nest it under :contact_message — accept both.</span>\n    <span class="token keyword">if</span> params<span class="token punctuation">.</span>key<span class="token operator">?</span><span class="token punctuation">(</span><span class="token symbol">:contact_message</span><span class="token punctuation">)</span>\n      params<span class="token punctuation">.</span><span class="token keyword">require</span><span class="token punctuation">(</span><span class="token symbol">:contact_message</span><span class="token punctuation">)</span><span class="token punctuation">.</span>permit<span class="token punctuation">(</span><span class="token symbol">
1:name</span><span class="token punctuation">,</span> <span class="token symbol">:email</span><span class="token punctuation">,</span> <span class="token symbol">:message</span><span class="token punctuation">)</span>\n    <span class="token keyword">else</span>\n      params<span class="token punctuation">.</span>permit<span class="token punctuation">(</span><span class="token symbol">:name</span><span class="token punctuation">,</span> <span class="token symbol">:email</span><span class="token punctuation">,</span> <span class="token symbol">:message</span><span class="token punctuation">)</span>\n    <span class="token keyword">end</span>\n  <span class="token keyword">end</span>\n<span class="token keyword">end</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"That's the whole round trip: submitting invalid data renders per-field errors\nunder each input; fixing them and resubmitting succeeds. Validations stay in\nthe model — the client renders whatever the server says."),"\n",s.createElement(n.p,null,"A runnable example lives in the dummy app:\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">react_on_rails/spec/dummy/client/app/startup/RailsFormExample.client.tsx</code>'}}),"\n(rendered at ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">/rails_form</code>'}}),") posting to\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">react_on_rails/spec/dummy/app/controllers/contact_messages_controller.rb</code>'}}),"."),"\n",s.createElement(n.h2,{id:"what-the-hook-sends",style:{position:"relative"}},s.createElement(n.a,{href:"#what-the-hook-sends","aria-label":"what the hook sends permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"What the hook sends"),"\n",s.createElement(n.p,null,"Every submit issues a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">fetch</code>'}})," with:"),"\n",s.createElement(n.ul,null,"\n",s.createElement(n.li,null,"the HTTP method you chose (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">post</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">put</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">patch</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">delete</code>'}}),", or\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">submit(method, url)</code>'}}),"),"),"\n",s.createElement(n.li,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Content-Type: application/json</code>'}})," and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Accept: application/json</code>'}}),","),"\n",s.createElement(n.li,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">X-CSRF-Token</code>'}})," read from the standard Rails ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">&lt;meta name="csrf-token"></code>'}})," tag\n(via the same ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">authenticityToken</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">authenticityHeaders</code>'}})," utilities exposed on\nthe ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ReactOnRails</code>'}})," object) plus ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">
1X-Requested-With: XMLHttpRequest</code>'}}),","),"\n",s.createElement(n.li,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:"<code class=\"language-text\">credentials: 'same-origin'</code>"}})," so the Rails session cookie is included,"),"\n",s.createElement(n.li,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">JSON.stringify(data)</code>'}})," as the body — except for ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">delete</code>'}}),", which sends ",s.createElement(n.strong,null,"no\nbody")," (DELETE bodies are legal per RFC 9110 but are stripped or rejected by\nmany proxies and CDNs in practice; put the resource identifier in the URL)."),"\n"),"\n",s.createElement(n.p,null,"The CSRF token is read from the meta tag at submit time, so a token rendered\nwith the page is picked up without any configuration. Your layout must render\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">&lt;%= csrf_meta_tags %></code>'}})," (the Rails default) — without the meta tag the hook\nthrows before calling ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">fetch</code>'}}),", so the form cannot accidentally submit as an\nanonymous or reset session. If your app rotates tokens during long-lived\nsessions, refresh the\nmeta tag (e.g., re-render it after sign-in) — the next submit reads the\ncurrent value."),"\n",s.createElement(n.p,null,"Field keys are passed through untouched: if your Rails model says\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors: { first_name: [...] }</code>'}}),", the hook gives you\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">form.errors.first_name</code>'}}),". Use snake_case keys in ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}})," to match your strong\nparameters (a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">transform</code>'}})," option for key mapping is planned — see\n",s.createElement(n.a,{href:"#scope-and-roadmap"},"Scope and roadmap"),")."),"\n",s.createElement(n.h2,{id:"hook-api",style:{position:"relative"}},s.createElement(n.a,{href:"#hook-api","aria-label":"hook api permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Hook API"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> form <span class="token operator">=</span> <span class="token function">useRailsForm</span><span class="token punctuation">(</span>initialData<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>'}}),"\n",s.createElement(n.table,null,s.createElement(n.thead,null,s.createElement(n.tr,null,s.createElement(n.th,null,"Member"),s.createElement(n.th,null,"Description"))),s.createElement(n.tbody,null,s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}})),s.createElement(n.td,null,"Current form data (typed from ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">initialData</code>'}}),").")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setData(key, value)</code>'}})),s.createElement(n.td,null,"Set one field. Also accepts a partial object (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setData({ a, b })</code>'}}),") or an updater function.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">{ field: ["message", ...] }</code>'}})," from the last 422 response.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">hasErrors</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">true</code>'}})," when ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})," is non-empty.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">processing</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">true</code>'}})," while a submission is in flight.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">true</code>'}})," once the most recent submission succeeded.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">post/put/patch/delete(url, options?)</code>'}})),s.createElement(n.td,null,"Submit ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}})," with that verb. Returns a promise of the submit result. ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">delete</code>'}})," sends no request body — put the resource id in the URL.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">submit(method, url, options?)</code>'}})),s.createElement(n.td,null,"Submit with an explicit method.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reset(...fields)</code>'}})),s.createElement(n.td,null,"Restore all (or the given) fields to their initial values; clears matching errors and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}}),". Initial values are the ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">initialData</code>'}})," from the first render — later prop changes are not tracked (Inertia ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useForm</code>'}})," semantics); remount to re-seed.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">clearErrors(...fields)</code>'}})),s.createElement(n.td,null,"Clear all (or the given) field errors.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setError(field, messages)</code>'}})),s.createElement(n.td,null,"Set errors for one field manually.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">RailsFormRequestError</code>'}})),s.createElement(n.td,null,"Exported error class (import separately; it is not a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">form</code>'}})," member) for non-2xx responses other than a mappable 422; carries the unread ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Response</code>'}})," plus ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">responseBody</code>'}})," (parsed JSON) for an unmappable 422.")))),"\n",s.createElement(n.p,null,"Per-submit ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">options</code>'}}),": ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">headers</code>'}})," (merged in; CSRF headers always win),\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onSuccess(result)</code>'}}),", and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onError(errors)</code>'}}),"."),"\n",s.createElement(n.p,null,"Calling ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reset()</code>'}})," inside ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onSuccess</code>'}})," starts a fresh editing cycle immediately, so\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}})," is also reset to ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">false</code>'}}),". Keep separate success-message state if\nthe UI needs to keep showing a success banner after clearing the fields."),"\n",s.createElement(n.p,null,"Response handling:"),"\n",s.createElement(n.ul,null,"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"2xx")," — ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}})," flips on, ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})," clears, and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onSuccess</code>'}})," receives\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">{ responseData, redirectTo, response }</code>'}}),"."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"422 with the documented ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})," shape")," — ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}}
1)," is populated per field\n(single strings are normalized to arrays) and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onError</code>'}})," is called. An empty\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})," object is still handled as a validation response with\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">{ ok: false, errors: {}, response }</code>'}}),". The promise resolves with\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">{ ok: false, errors, response }</code>'}}),"."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Anything else")," (including a 422 whose body doesn't match) — the promise\nrejects with ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">RailsFormRequestError</code>'}}),"; network failures reject with the\noriginal fetch error."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Superseded submissions")," — when an older in-flight submit settles after a\nnewer submit has started, it resolves with ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">{ ok: false, stale: true }</code>'}})," plus\nthe stale ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">response</code>'}})," or ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">error</code>'}}),". Stale submits do not update form state, call\nsubmit callbacks, or reject into an older ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">.catch()</code>'}})," handler after the newer\nsubmit has won."),"\n"),"\n",s.createElement(n.h2,{id:"redirects-and-onsuccess",style:{position:"relative"}},s.createElement(n.a,{href:"#redirects-and-onsuccess","aria-label":"redirects and onsuccess permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Redirects and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onSuccess</code>'}})),"\n",s.createElement(n.p,null,"The hook never navigates on its own. After a successful submit it surfaces a\nredirect target as ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">result.redirectTo</code>'}})," when the JSON body contains a\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">redirect_to</code>'}})," (or ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">redirectTo</code>'}}),") string hint pointing to a same-origin URL. Native\nfetch redirects are disabled for CSRF-bearing submissions (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:"<code class=\"language-text\">redirect: 'error'</code>"}}),"),\nso a Rails ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">redirect_to</code>'}})," response rejects instead of being followed in v1.\nRelative hints resolve like browser URLs against the current page URL: for\nexample, ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">"?saved=1"</code>'}})," preserves the current path and changes only the query.\nReturn an explicit root-relative path such as ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">"/posts/1?saved=1"</code>'}})," when the\nredirect should ignore the current page path."),"\n",s.createElement(n.p,null,"Pass it to whatever owns navigation in your app:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="tsx"><pre class="language-tsx"><code class="language-tsx"><span class="token keyword">void</span> form<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">\'/posts\'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>\n  <span class="token function-variable function">onSuccess</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> redirectTo <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n    <span class="token keyword">if</span> <span class="token punctuation">(</span>redirectTo<span class="token punctuation">)</span> window<span class="token punctuation">.</span>location<span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span>redirectTo<span class="token punctuation">)</span><span class="token punctuation">;</span>\n  <span class="token punctuation">}</span><span class="token punctuation">,</span>\n<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"This design is intentionally forward-compatible with the client-routing\nintegration tracked in\n",s.createElement(n.a,{href:"https://github.com/shakacode/react_on_rails/issues/3873"},"Issue 3873"),": when a\nclient router is present, hand ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">redirectTo</code>'}})," to the router instead of\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window.location</code>'}}),"."),"\n",s.createElement(n.h2,{id:"the-controller-concern",style:{position:"relative"}}
1,s.createElement(n.a,{href:"#the-controller-concern","aria-label":"the controller concern permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"The controller concern"),"\n",s.createElement(n.p,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ReactOnRails::Controller::FormResponders</code>'}})," ships with the ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">react_on_rails</code>'}})," gem\nand adds one helper:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby">render_model_errors<span class="token punctuation">(</span>record<span class="token punctuation">,</span> <span class="token symbol">status</span><span class="token operator">:</span> <span class="token number">422</span><span class="token punctuation">)</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"It renders ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">record.errors.messages</code>'}})," (any ActiveModel/ActiveRecord object) as\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">{ errors: { field: [messages] } }</code>'}}),". It is deliberately tiny: if your API\nalready returns errors another way (JSON:API, custom serializers), keep it —\njust map your shape to the documented one at the endpoint the form posts to.\nPass numeric HTTP status codes such as ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">400</code>'}})," or ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">422</code>'}}),"; Rails status symbols are\nintentionally rejected so Rack/Rails status-symbol renames cannot change the\nwire response.\nBecause those ActiveModel messages are sent to the browser verbatim, review\ncustom validations for internal IDs, admin-only details, or security-sensitive\nwording before using the concern on a model."),"\n",s.createElement(n.h2,{id:"comparison-with-inertias-useform",style:{position:"relative"}},s.createElement(n.a,{href:"#comparison-with-inertias-useform","aria-label":"comparison with inertias useform permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Comparison with Inertia's ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useForm</code>'}})),"\n",s.createElement(n.p,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useRailsForm</code>'}})," deliberately mirrors the parts of\n",s.createElement(n.a,{href:"https://inertia-rails.dev/guide/forms"},"Inertia.js ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useForm</code>'}}))," teams use for\nCRUD, without coupling your controllers to a response protocol — the endpoint\nstays a normal Rails JSON action you can curl or reuse for mobile clients."),"\n",s.createElement(n.table,null,s.createElement(n.thead,null,s.createElement(n.tr,null,s.createElement(n.th,null,"Inertia ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useForm</code>'}})),s.createElement(n.th,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useRailsForm</code>'}})))),s.createElement(n.tbody,null,s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setData</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setData</code>'}})," (same overloads)")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})," (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">string</code>'}})," per field)"),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}})," (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">string[]</code>'}})," per field)")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">processing</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">processing</code>'}}))),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}}))),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">post/put/patch/delete(url)</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">post/put/patch/delete(url)</code>'}}))),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reset</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">clearErrors</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setError</code>'}})),s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reset</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">clearErrors</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setError</code>'}}))),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">transform(fn)</code>'}})),s.createElement(n.td,null,"Planned (v2)")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">recentlySuccessful</code>'}})),s.createElement(n.td,null,"Planned (v2)")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">progress</code>'}})," (file uploads)"),s.createElement(n.td,null,"Planned (v2 — requires an XHR transport)")),s.createElement(n.tr,null,s.createElement(n.td,null,"Automatic visit/redirect"),s.createElement(n.td,null,"Not automatic — ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">redirectTo</code>'}})," is surfaced")))),"\n",s.createElement(n.p,null,"Versus React 19 / Next.js ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useActionState</code>'}})," + ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useFormStatus</code>'}}),": those pair with\nServer Actions (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:"<code class=\"language-text\">'use server'</code>"}}),"), which introduce an RPC endpoint per action.\nReact on Rails intentionally keeps mutations in Rails controllers —\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useRailsForm</code>'}})," is the ergonomic path for that, and the separate\n",s.createElement(n.a,{href:"https://github.com/shakacode/react_on_rails/issues/3867"},"Server Functions RFC (Issue 3867)"),"\nexplores the complementary RSC-side story. The two are designed to compose: if\nServer Functions land, this hook remains the plain-controller bridge.\nFor side-by-side migration examples, see\n",s.createElement(n.a,{href:"/linked-assets/376102a5118b595e05eafcc6fcbbda9c/mutations.md"},"Mutations without Server Actions"),"."),"\n",s.createElement(n.h2,{id:"scope-and-roadmap",style:{position:"relative"}}
1,s.createElement(n.a,{href:"#scope-and-roadmap","aria-label":"scope and roadmap permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Scope and roadmap"),"\n",s.createElement(n.p,null,"v1 is fetch-only and covers submit verbs, ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">data</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setData</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">errors</code>'}}),",\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">processing</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">wasSuccessful</code>'}}),", CSRF auto-attach, and 422 error mapping\n(",s.createElement(n.a,{href:"https://github.com/shakacode/react_on_rails/issues/3872"},"Issue 3872"),").\nDeferred to a follow-up release: ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">transform</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">recentlySuccessful</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onFinish</code>'}}),",\nand file uploads with ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">progress</code>'}})," (which needs an ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">XMLHttpRequest</code>'}}),"/duplex-stream\ntransport). Navigation, prefetching, and router integration belong to\n",s.createElement(n.a,{href:"https://github.com/shakacode/react_on_rails/issues/3873"},"Issue 3873"),"."))}var o=function(e){void 0===e&&(e={});const{wrapper:n}=Object.assign({},(0,t.R)(),e.components);return n?s.createElement(n,e,s.createElement(l,e)):l(e)},c=a(63679),r=a(95197),p=a(47388),u=a(23690),i=a(14487);const d=c.default.p.withConfig({displayName:"doc-article__RedirectFallback",componentId:"sc-tic2w5-0"})(["padding:2rem;"]),m=e=>{let{data:n,location:a,children:t}=e;const{headings:l}=n.article.childMdx,o=(0,r.X9)(a.pathname),[c,u]=s.useState(null),m=a.pathname.split("/"),g=m.indexOf("docs"),h=-1===g?a.pathname.length:m.slice(0,g+1).join("/").length,k=a.pathname.slice(0,h),_=a.pathname.slice(h+1).replace(/\/$/,"").split("/").filter((e=>""!==e)),E=_.length>1?_.slice(0,-1).join("/"):"";s.useEffect((()=>{if(!o)return;const{hash:e}=window.location;u(e);const n=""+o+e;window.location.href!==n&&window.location.replace(n)}),[o]);const y=null===o||null===c?null:""+o+c;return s.createElement(s.Fragment,null,null!==y?s.createElement(d,{role:"status","aria-live":"polite"},"Redirecting..."," ",s.createElement("a",{href:y,target:"_blank",rel:"noopener noreferrer"},"Click here if not redirected.")):s.createElement(p.A,{main:s.createElement(i.A,{body:t,headings:l,transformUrl:e=>{if(e.startsWith("http")||e.startsWith("#"))return e;const n=e.replace(/\.md$/,"/");if(n.startsWith("./"))return k+(""===E?"":"/"+E)+n.slice(1);if(n.startsWith("../")){if(""===E)return""+k+n.slice(2);const e=E.split("/").slice(0,-1).join("/");return k+(""===e?"":"/"+e)+n.slice(2)}return k+"/"+(""===E?"":E+"/")+n}})}))};function g(e){return s.createElement(m,e,s.createElement(o,e))}const h=e=>{let{data:n,location:a,pageContext:t}=e;const{headings:l}=n.article.childMdx,{siteUrl:o}=n.site.siteMetadata,{repoDisplayName:c}=t,p=l.find((e=>1===e.depth)),i=(0,r.X9)(a.pathname)||""+o.replace(/\/+$/,"")+a.pathname;return s.createElement(u.A,{title:p?p.value:c,description:p?p.value:c+" documentation",isArticle:!0,canonical:i,pathname:a.pathname})}}}]);
2//# sourceMappingURL=component---src-template-doc-article-tsx-content-file-path-cache-gatsby-source-git-react-on-rails-docs-oss-building-features-forms-md-1c94b72a7ee47566daf5.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.