From 6ec09741acd40eb1835cbf6d02bfae642413b6ef Mon Sep 17 00:00:00 2001 From: Daniel Schulz Date: Tue, 15 Mar 2022 19:33:58 +0100 Subject: [PATCH] fix(editable): #5670 call setPrevValue in onFocus to avoid outdated data when controlled (#5684) --- .changeset/dry-worms-invent.md | 6 ++ packages/editable/src/use-editable.ts | 21 ++++++- .../editable/stories/editable.stories.tsx | 35 +++++++++++ packages/editable/tests/editable.test.tsx | 53 +++++++++++++++++ .../editable/tests/editableTextarea.test.tsx | 59 ++++++++++++++++++- 5 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 .changeset/dry-worms-invent.md diff --git a/.changeset/dry-worms-invent.md b/.changeset/dry-worms-invent.md new file mode 100644 index 00000000000..b41bb6d3065 --- /dev/null +++ b/.changeset/dry-worms-invent.md @@ -0,0 +1,6 @@ +--- +"@chakra-ui/editable": patch +--- + +Call `setPrevValue` `onFocus` to avoid an outdated prev value when the field is +controlled diff --git a/packages/editable/src/use-editable.ts b/packages/editable/src/use-editable.ts index ca3f1f6e1f2..79dff1ffb22 100644 --- a/packages/editable/src/use-editable.ts +++ b/packages/editable/src/use-editable.ts @@ -161,6 +161,10 @@ export function useEditable(props: UseEditableProps = {}) { } }, [isInteractive]) + const onUpdatePrevValue = useCallback(() => { + setPrevValue(value) + }, [value]) + const onCancel = useCallback(() => { setIsEditing(false) setValue(prevValue) @@ -247,7 +251,7 @@ export function useEditable(props: UseEditableProps = {}) { hidden: isEditing, "aria-disabled": ariaAttr(isDisabled), tabIndex, - onFocus: callAllHandlers(props.onFocus, onEdit), + onFocus: callAllHandlers(props.onFocus, onEdit, onUpdatePrevValue), } }, [ @@ -257,6 +261,7 @@ export function useEditable(props: UseEditableProps = {}) { isPreviewFocusable, isValueEmpty, onEdit, + onUpdatePrevValue, placeholder, value, ], @@ -277,8 +282,18 @@ export function useEditable(props: UseEditableProps = {}) { onBlur: callAllHandlers(props.onBlur, onBlur), onChange: callAllHandlers(props.onChange, onChange), onKeyDown: callAllHandlers(props.onKeyDown, onKeyDown), + onFocus: callAllHandlers(props.onFocus, onUpdatePrevValue), }), - [isDisabled, isEditing, onBlur, onChange, onKeyDown, placeholder, value], + [ + isDisabled, + isEditing, + onBlur, + onChange, + onKeyDown, + onUpdatePrevValue, + placeholder, + value, + ], ) const getTextareaProps: PropGetterV2< @@ -296,6 +311,7 @@ export function useEditable(props: UseEditableProps = {}) { onBlur: callAllHandlers(props.onBlur, onBlur), onChange: callAllHandlers(props.onChange, onChange), onKeyDown: callAllHandlers(props.onKeyDown, onKeyDownWithoutSubmit), + onFocus: callAllHandlers(props.onFocus, onUpdatePrevValue), }), [ isDisabled, @@ -303,6 +319,7 @@ export function useEditable(props: UseEditableProps = {}) { onBlur, onChange, onKeyDownWithoutSubmit, + onUpdatePrevValue, placeholder, value, ], diff --git a/packages/editable/stories/editable.stories.tsx b/packages/editable/stories/editable.stories.tsx index 014805d9975..b9b848e91e5 100644 --- a/packages/editable/stories/editable.stories.tsx +++ b/packages/editable/stories/editable.stories.tsx @@ -8,6 +8,7 @@ import { EditableTextarea, useEditableControls, } from "../src" +import { Heading } from "@chakra-ui/layout" export default { title: "Components / Forms / Editable", @@ -125,3 +126,37 @@ export const TextareaAsInput = () => { ) } + +export const EditableEventHandler = () => { + const [name, setName] = React.useState("") + console.log("State 'name' is ", name) + + React.useEffect(() => { + setName("John") + }, []) + + return ( + <> + Name State=[{name}] + { + console.log("onChange called with ", value) + setName(value) + }} + onSubmit={(value) => { + console.log("onSubmit called with ", value) + setName(value) + }} + onCancel={(value) => { + console.log("onCancel called with ", value) + setName(value) + }} + placeholder="Enter your name" + > + + + + + ) +} diff --git a/packages/editable/tests/editable.test.tsx b/packages/editable/tests/editable.test.tsx index 6e7e3abc04d..f826357e2dc 100644 --- a/packages/editable/tests/editable.test.tsx +++ b/packages/editable/tests/editable.test.tsx @@ -224,3 +224,56 @@ test("startWithEditView when true focuses on the input ", () => { expect(document.activeElement === input).toBe(true) }) + +test.each([ + { startWithEditView: true, text: undefined }, + { startWithEditView: false, text: undefined }, + { startWithEditView: true, text: "Bob" }, + { startWithEditView: false, text: "Bob" }, +])( + "controlled: sets value toPrevValue onCancel, startWithEditView: $startWithEditView", + ({ startWithEditView, text }) => { + const Component = () => { + const [name, setName] = React.useState("") + + React.useEffect(() => { + setName("John") + }, []) + + return ( + { + setName(value) + }} + onSubmit={(value) => { + setName(value) + }} + onCancel={(value) => { + setName(value) + }} + placeholder="Enter your name" + > + + + + ) + } + + render() + const input = screen.getByTestId("input") + const preview = screen.getByTestId("preview") + if (!startWithEditView) { + fireEvent.focus(preview) + } else { + fireEvent.focus(input) + } + if (text) { + userEvent.type(input, text) + } + fireEvent.keyDown(input, { key: "Escape" }) + + expect(preview).toHaveTextContent("John") + }, +) diff --git a/packages/editable/tests/editableTextarea.test.tsx b/packages/editable/tests/editableTextarea.test.tsx index 6cd27f61231..1ac85adcefb 100644 --- a/packages/editable/tests/editableTextarea.test.tsx +++ b/packages/editable/tests/editableTextarea.test.tsx @@ -6,7 +6,12 @@ import { userEvent, } from "@chakra-ui/test-utils" import * as React from "react" -import { Editable, EditablePreview, EditableTextarea } from "../src" +import { + Editable, + EditableInput, + EditablePreview, + EditableTextarea, +} from "../src" test("matches snapshot", () => { render( @@ -209,3 +214,55 @@ test("editable textarea can submit on blur", () => { fireEvent.blur(textarea) expect(onSubmit).toHaveBeenCalledWith("testing") }) +test.each([ + { startWithEditView: true, text: undefined }, + { startWithEditView: false, text: undefined }, + { startWithEditView: true, text: "Bob" }, + { startWithEditView: false, text: "Bob" }, +])( + "controlled: sets value toPrevValue onCancel, startWithEditView: $startWithEditView", + ({ startWithEditView, text }) => { + const Component = () => { + const [name, setName] = React.useState("") + + React.useEffect(() => { + setName("John") + }, []) + + return ( + { + setName(value) + }} + onSubmit={(value) => { + setName(value) + }} + onCancel={(value) => { + setName(value) + }} + placeholder="Enter your name" + > + + + + ) + } + + render() + const input = screen.getByTestId("input") + const preview = screen.getByTestId("preview") + if (!startWithEditView) { + fireEvent.focus(preview) + } else { + fireEvent.focus(input) + } + if (text) { + userEvent.type(input, text) + } + fireEvent.keyDown(input, { key: "Escape" }) + + expect(preview).toHaveTextContent("John") + }, +)