最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Set new intiial SlateJS editor value with React onEffect - Stack Overflow

programmeradmin0浏览0评论

How to easily set entire SlateJS editor value using React onEffect hook?

The initial editor value is set when creating the useState hook, however, I want to set a new initial value afterwards.

Currently, we can do that by deleting all elements with Transform and then inserting new elements, but is an easier way, like override all? I can't seem to find it in the SlateJS docs.

Saving to database slatejs example Don't work, but is how my setup functions

const App = () => {
  const editor = useMemo(() => withReact(createEditor()), [])
  // Update the initial content to be pulled from Local Storage if it exists.
  const [value, setValue] = useState(
    [
      {
        type: 'paragraph',
        children: [{ text: 'A line of text in a paragraph.' }],
      },
    ]
  )

  useEffect(() => {
    // Get saved SlateJS text from local storage
    const savedSlateTextData = getSavedSlateJSData(localStorage)

    // In theory, this should set the SlateJS values to the loaded data
    // In practice, I think because the editor is immutable outside transform, it don't work 
    setValue(savedSlateTextData)
  }, [])

  return (
    <Slate
      editor={editor}
      value={value}
      onChange={value => {
        setValue(value)
        const isAstChange = editor.operations.some(
          op => 'set_selection' !== op.type
        )
        if (isAstChange) {
          // Save the value to Local Storage.
          const content = JSON.stringify(value)
          localStorage.setItem('content', content)
        }
      }}
    >
      <Editable />
    </Slate>
  )
}

How to easily set entire SlateJS editor value using React onEffect hook?

The initial editor value is set when creating the useState hook, however, I want to set a new initial value afterwards.

Currently, we can do that by deleting all elements with Transform and then inserting new elements, but is an easier way, like override all? I can't seem to find it in the SlateJS docs.

Saving to database slatejs example Don't work, but is how my setup functions

const App = () => {
  const editor = useMemo(() => withReact(createEditor()), [])
  // Update the initial content to be pulled from Local Storage if it exists.
  const [value, setValue] = useState(
    [
      {
        type: 'paragraph',
        children: [{ text: 'A line of text in a paragraph.' }],
      },
    ]
  )

  useEffect(() => {
    // Get saved SlateJS text from local storage
    const savedSlateTextData = getSavedSlateJSData(localStorage)

    // In theory, this should set the SlateJS values to the loaded data
    // In practice, I think because the editor is immutable outside transform, it don't work 
    setValue(savedSlateTextData)
  }, [])

  return (
    <Slate
      editor={editor}
      value={value}
      onChange={value => {
        setValue(value)
        const isAstChange = editor.operations.some(
          op => 'set_selection' !== op.type
        )
        if (isAstChange) {
          // Save the value to Local Storage.
          const content = JSON.stringify(value)
          localStorage.setItem('content', content)
        }
      }}
    >
      <Editable />
    </Slate>
  )
}
Share Improve this question asked Feb 21, 2022 at 5:49 wanna_coder101wanna_coder101 2061 gold badge12 silver badges30 bronze badges
Add a comment  | 

5 Answers 5

Reset to default 8

This is the cleanest way I could think of using what's available via slate

const INITIAL_SLATE_VALUE =  [
   {
      type: 'paragraph',
      children: [{ text: 'A line of text in a paragraph.' }],
   },
]

const [value, setValue] = useState(INITIAL_SLATE_VALUE)

// Delete all entries leaving 1 empty node
Transforms.delete(editor, {
   at: {
      anchor: Editor.start(editor, []),
      focus: Editor.end(editor, []),
   },
})

// Removes empty node
Transforms.removeNodes(editor, {
   at: [0],
})

// Insert array of children nodes
Transforms.insertNodes(
   editor,
   value
)

Don't think what I want is possible — you need to delete all the nodes and then insert new ones.

Not sure if you could use match instead of a bunch of for loops, perhaps.

  // Get initial total nodes to prevent deleting affecting the loop
  let totalNodes = editor.children.length;

  // No saved content, don't delete anything to prevent errors
  if (savedSlateJSContent.length <= 0) return;

  // Remove every node except the last one
  // Otherwise SlateJS will return error as there's no content
  for (let i = 0; i < totalNodes - 1; i++) {
      console.log(i)
      Transforms.removeNodes(editor, {
          at: [totalNodes-i-1],
      });
  }

  // Add content to SlateJS
  for (const value of savedSlateJSContent ) {
      Transforms.insertNodes(editor, value, {
          at: [editor.children.length],
      });
  }

  // Remove the last node that was leftover from before
  Transforms.removeNodes(editor, {
      at: [0],
  });

An alternative is to just reinstantiate a new Slate editor when initialValue changes:

const App = () => {
  // OLD way to do async stuff in React
  const [initialValue, setInitialValue] = useState([
      {
        type: 'paragraph',
        children: [{ text: 'A line of text in a paragraph.' }],
      },
    ])
  useEffect(async () => {
    setValue(await getSavedSlateJSData(localStorage))
  }, [])

  // NEW way
  // const initialValue = use(getSavedSlateJSData(localStorage))

  return <MyEditor initialValue={initialValue} />
  // (alternatively don't render editor until value is loaded)
}

const MyEditor = ({ initialValue }) => {
  const [value, setValue] = useState(initialValue)
  const editor = useMemo(() => withReact(createEditor()), [])
  return (
    <Slate
      editor={editor}
      value={value}
      onChange={value => {
        setValue(value)
        const isAstChange = editor.operations.some(
          op => 'set_selection' !== op.type
        )
        if (isAstChange) {
          // Save the value to Local Storage.
          const content = JSON.stringify(value)
          localStorage.setItem('content', content)
        }
      }}
    >
      <Editable />
    </Slate>
  )
}

The solution for me was to re-mount the component. I am attaching an example which wraps the Rich text editor example with a React Hook Form Controller:

import { ComponentProps, useRef } from "react";
import { Control, Controller, FieldValues, Path } from "react-hook-form";
import TextEditor from "../text-editor/TextEditor";

interface RHFTextEditorProps<TField extends FieldValues> extends Omit<ComponentProps<typeof TextEditor>, "onChange"> {
   name: Path<TField>;
    control: Control<TField>;
    value?: string;
}

const RHFTextEditor = <TField extends FieldValues>({
  control,
  name,
  value,
  onValueChange,
  ...textEditorProps
}: RHFTextEditorProps<TField>) => {
  const ref = useRef<string | null>(null);
  const uuidRef = useRef<string>("0");
  return (
    <Controller
      control={control}
      name={name}
      render={({ field }) => {
        if (ref.current !== field.value) {
          ref.current = field.value;
          uuidRef.current = Math.random().toString(36).substring(7);
        }
        return (
          <TextEditor
            key={`text-editor-${name}-${uuidRef.current}`}
            initialValue={field.value || value}
            onValueChange={(e) => {
              ref.current = e;
              field.onChange(e);
              onValueChange && onValueChange(e);
            }}
            {...textEditorProps}
          />
        );
      }}
   />
  );
};

export default RHFTextEditor;

It is not the most elegant way to solve the key choice but it works. After several days of research, this was how it could receive programmatically values from the react hook form API and update the form control value. I haven't found any other possible solution.

Years later I'm looking at this again and the initial value is an initial value that cannot be changed dynamically by simply setting it to something else. You'd need to use onChange if you wanted to set a new value after the API response comes back.

Otherwise, re-initialise the entire slate editor. I found the easiest way is with a key. Basically, when the key value changes, then slate will reinialise itself. So then update the key when the backend data arrives.

import { v7 as uuidv7 } from 'uuid'

const backendData = useAppSelector(selectBackendData)
const [uniqueKey, setUniqueKey] = useState(uuidv7())

const populatedInitialValue: Descendant[] = useMemo(() => {
    if (backendData.length <= 0) return [
    {
        type: ElementTypes.PARAGRAPH,
        children: [{ text: 'Default Initial Paragraph Content' }],
    },
]

    setUniqueKey(uuidv7())

    return backendData
}, [backendData])

return (
        <Slate
            key={uniqueKey}
            editor={editor}
            initialValue={populatedInitialValue}
            >
        </Slate>
)

App Version

"slate": "^0.112.0",

发布评论

评论列表(0)

  1. 暂无评论