PageSourceSearch

https://www.draftail.org/assets/js/722e889f.7900d49a.js

js draftail.org collected 2026-10-03 23:19:51 UTC 8,589 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkdraftail_org=globalThis.webpackChunkdraftail_org||[]).push([[3346],{28453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>r});var o=n(96540);const i={},s=o.createContext(i);function a(e){const t=o.useContext(s);return o.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:a(e.components),o.createElement(s.Provider,{value:t},e.children)}},41620:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/editorstate-race-condition-2-handlers-2de3feac5d7f85906e420e1ddbdeb2da.png"},64364:(e,t,n)=>{n.d(t,{A:()=>o});const o=n.p+"assets/images/editorstate-race-condition-1-handler-cf94f4b3987564dca74f1729f21debb9.png"},86412:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>r,default:()=>l,frontMatter:()=>a,metadata:()=>o,toc:()=>c});const o=JSON.parse('{"id":"draft-js/advanced-topics/editorstate-race-conditions","title":"EditorState Race Conditions","description":"Draft Editor is a controlled input component (you can read about this in detail in the API Basics section), meaning that changes made to the Editor state are propagated upwards through onChange and it\'s up to the app to feed it back to the Editor component.","source":"@site/docs/draft-js/advanced-topics/EditorState-Race-Conditions.md","sourceDirName":"draft-js/advanced-topics","slug":"/draft-js/advanced-topics/editorstate-race-conditions","permalink":"/docs/next/draft-js/advanced-topics/editorstate-race-conditions","draft":false,"unlisted":false,"editUrl":"https://github.com/thibaudcolas/draftail.org/edit/main/docs/draft-js/advanced-topics/EditorState-Race-Conditions.md","tags":[],"version":"current","lastUpdatedBy":"Thi","lastUpdatedAt":1791068756000,"frontMatter":{"id":"editorstate-race-conditions","title":"EditorState Race Conditions"},"sidebar":"docs","previous":{"title":"Decorators","permalink":"/docs/next/draft-js/advanced-topics/decorators"},"next":{"title":"Entities","permalink":"/docs/next/draft-js/advanced-topics/entities"}}');var i=n(74848),s=n(28453);const a={id:"editorstate-race-conditions",title:"EditorState Race Conditions"},r=void 0,d={},c=[{value:"Best Practices",id:"best-practices",level:2}];function h(e){const t={a:"a",code:"code",em:"em",h2:"h2",img:"img",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(t.p,{children:["Draft ",(0,i.jsx)(t.code,{children:"Editor"})," is a ",(0,i.jsx)(t.em,{children:"controlled input"})," component (you can read about this in detail in the ",(0,i.jsx)(t.a,{href:"/docs/next/draft-js/quickstart/api-basics",children:"API Basics"})," section), meaning that changes made to the ",(0,i.jsx)(t.code,{children:"Editor"})," state are propagated upwards through ",(0,i.jsx)(t.code,{children:"onChange"})," and it's up to the app to feed it back to the ",(0,i.jsx)(t.code,{children:"Editor"})," component."]}),"\n",(0,i.jsx)(t.p,{children:"This cycle usually looks like:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:'...\nthis.onChange = function(editorState) {\n  this.setState({editorState: editorState});\n}\n...\n<Editor\n  editorState={this.state.editorState}\n  onChange={this.onChange}\n  placeholder="Enter some text..."\n/>\n'})}),"\n",(0,i.jsxs)(t.p,{children:["Different browser events can trigger the ",(0,i.jsx)(t.code,{children:"Editor"})," to create a new state and call ",(0,i.jsx)(t.code,{children:"onChange"}),". For instance, when the user pastes text into it, Draft parses the new content and creates the necessary data structure to represent it."]}),"\n",(0,i.jsxs)(t.p,{children:["This cycle works great, however, it is an asynchronous operation because of the ",(0,i.jsx)(t.code,{children:"setState"})," call. This introduces a delay between setting the state and rendering the ",(0,i.jsx)(t.code,{children:"Editor"})," with the new state. During this time period other JS code can be executed."]}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Race condition diagram 1",src:n(64364).A+"",width:"739",height:"276"})}),"\n",(0,i.jsxs)(t.p,{children:["Non-atomic operations like this can potentially introduce race conditions.\nHere's an example: Supp
1ose you want to remove all the text styles that come from the paste. This can be implemented by listening to the onPaste event and removing all styles from the ",(0,i.jsx)(t.code,{children:"EditorState"}),":"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:"this.onPaste = function () {\n  this.setState({\n    editorState: removeEditorStyles(this.state.editorState),\n  })\n}\n"})}),"\n",(0,i.jsxs)(t.p,{children:["However, this won't work as expected. You now have two event handlers that set a new ",(0,i.jsx)(t.code,{children:"EditorState"})," in the exact same browser event. Since the event handlers will run one after the other only the last ",(0,i.jsx)(t.code,{children:"setState"})," will prevail. Here's how it looks like in the JS timeline:"]}),"\n",(0,i.jsx)(t.p,{children:(0,i.jsx)(t.img,{alt:"Race condition diagram 2",src:n(41620).A+"",width:"1051",height:"423"})}),"\n",(0,i.jsxs)(t.p,{children:["As you can see, since ",(0,i.jsx)(t.code,{children:"setState"})," is an asynchronous operation, the second ",(0,i.jsx)(t.code,{children:"setState"})," will override whatever it was set on the first one making the ",(0,i.jsx)(t.code,{children:"Editor"})," lose all the contents from the pasted text."]}),"\n",(0,i.jsxs)(t.p,{children:["You can observe and explore the race condition in ",(0,i.jsx)(t.a,{href:"https://jsfiddle.net/qecccw3r/",children:"this running example"}),". The example also has logging to highlight the JS timeline so make sure to open the developer tools."]}),"\n",(0,i.jsxs)(t.p,{children:["As a rule of thumb avoid having different event handlers for the same event that manipulate the ",(0,i.jsx)(t.code,{children:"EditorState"}),". Using setTimeout to run ",(0,i.jsx)(t.code,{children:"setState"})," might also land you in the same situation.\nAnytime you feel you're \u201closing state\u201d make sure you're not overriding it before the ",(0,i.jsx)(t.code,{children:"Editor"})," re-rendering."]}),"\n",(0,i.jsx)(t.h2,{id:"best-practices",children:"Best Practices"}),"\n",(0,i.jsxs)(t.p,{children:["Now that you understand the problem, what can you do to avoid it? In general be mindful of where you're getting the ",(0,i.jsx)(t.code,{children:"EditorState"})," from. If you're using a local one (stored in ",(0,i.jsx)(t.code,{children:"this.state"}),") then there's the potential for it to not be up to date.\nTo minimize this problem Draft offers the latest ",(0,i.jsx)(t.code,{children:"EditorState"})," instance in most of its callback functions. In your code you should use the provided ",(0,i.jsx)(t.code,{children:"EditorState"})," instead of your local one to make sure you're basing your changes on the latest one.\nHere's a list of supported callbacks on the ",(0,i.jsx)(t.code,{children:"Editor"}),":"]}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"handleReturn(event, editorState)"})}),"\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"handleKeyCommand(command, editorState)"})}),"\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"handleBeforeInput(chars, editorState)"})}),"\n",(0,i.jsx)(t.li,{children:(0,i.jsx)(t.code,{children:"handlePastedText(text, html, editorState)"})}),"\n"]}),"\n",(0,i.jsx)(t.p,{children:"The paste example can then be re-written in a race condition free way by using these methods:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-js",children:'this.handlePastedText = (text, styles, editorState) => {\n  this.setState({\n    editorState: removeEditorStyles(text, editorState),\n  })\n}\n//...\n;<Editor\n  editorState={this.state.editorState}\n  onChange={this.onChange}\n  handlePastedText={this.handlePastedText}\n  placeholder="Enter some text..."\n/>\n'})}),"\n",(0,i.jsxs)(t.p,{children:["With ",(0,i.jsx)(t.code,{children:"handlePastedText"})," you can implement the paste behavior by yourself."]}),"\n",(0,i.jsxs)(t.p,{children:["NOTE: If you need to have this behavior in your Editor, you can achieve it by setting the ",(0,i.jsx)(t.code,{children:"Editor"}),"'s ",(0,i.jsx)(t.code,{children:"stripPastedStyles"})," property to ",(0,i.jsx)(t.code,{children:"true"}),"."]})]})}function l(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}}}]);

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.