提供新的initialValue时无法更改Slate.js组件的内容,编辑器内容保持不变

问题描述 投票:0回答:1

我正在尝试制作一个文本编辑器应用程序,其中用户可以拥有多个编辑器,例如 notion,我使用 Slate.js、React、TypeScript 制作了文本编辑器。目前我只有一个编辑器,我将其内容存储在 localStorage 中。

我还实现了一个显示编辑器的侧边栏,当用户选择任何编辑器时,当前编辑器状态会发生变化,但 Slate 编辑器中的内容不会改变。虽然标题发生了变化,这意味着

currentEditor
状态正在发生变化,但即使在传递新编辑器的值之后,我也无法更改编辑器的内容。

之前我使用的是自定义钩子。现在我已经转向 Redux Toolkit 来管理编辑器。

附上我的代码供参考:

App.tsx

function App() {
    const currentEditor = useAppSelector((state) => state.editors.currentEditor);

    // to make editor to be stable across renders, we use useState without a setter
    // for more reference
    const [editor] = useState(withCustomFeatures(withReact(createEditor())));

    // defining a rendering function based on the element passed to 'props',
    // useCallback here to memoize the function for subsequent renders.
    // this will render our custom elements according to props
    const renderElement = useCallback((props: RenderElementProps) => {
        return <Element {...props} />;
    }, []);

    // a memoized leaf rendering function
    // this will render custom leaf elements according to props
    const renderLeaf = useCallback((props: RenderLeafProps) => {
        return <Leaf {...props} />;
    }, []);

    if (!currentEditor) {
        return <div>loading</div>;
    }

    return (
        <div className="h-full flex">
            <div className="flex h-screen w-60 flex-col inset-y-0">
                <div className="h-full text-primary w-full bg-white">
                    <NavigationSidebar />
                </div>
            </div>
            <main className="w-full">
                <div className="bg-sky-200 flex flex-col h-screen w-full">
                    <div className="text-center mt-4">
                        <h1 className="text-xl">{currentEditor?.title}</h1>
                    </div>
                    <div className="bg-white mx-auto rounded-md my-10 w-4/5">
                        {currentEditor && (
                            <EditorComponent
                                editor={editor}
                                renderElement={renderElement}
                                renderLeaf={renderLeaf}
                            />
                        )}
                    </div>
                </div>
            </main>
        </div>
    );
}

export default App;

Editor.tsx

const EditorComponent: React.FC<EditorProps> = ({
    editor,
    renderElement,
    renderLeaf,
}) => {
    const { setSearch, decorate } = useDecorate();
    const dispatch = useAppDispatch();

    const currentEditor = useAppSelector((state) => state.editors.currentEditor);

    if (!currentEditor) {
        return <div>Loading</div>;
    }

    return (
        // render the slate context, must be rendered above any editable components,
        //  it can provide editor state to other components like toolbars, menus
        <Slate
            editor={editor}
            initialValue={currentEditor.value}
            // store value to localStorage on change
            onChange={(value) =>
                dispatch(
                    storeContent({
                        id: currentEditor.id,
                        title: currentEditor.title,
                        value,
                        editor,
                    })
                )
            }
        >
            {/* Toolbar */}
            <Toolbar/>
            <HoveringToolbar />
            {/* editable component */}
            <div className="p-3 focus-within:ring-2 focus-within:ring-neutral-200 focus-within:ring-inset border">
                <Editable
                    spellCheck
                    autoFocus
                    className="outline-none max-h-[730px] overflow-y-auto"
                    renderElement={renderElement}
                    renderLeaf={renderLeaf}
                    decorate={decorate}
                     
                     
                />
            </div>
        </Slate>
    );
};

export default EditorComponent;

NavigationSidebar.tsx

const NavigationSidebar = () => {
    const editors = useAppSelector((state) => state.editors.editors);
    const currentEditor = useAppSelector((state) => state.editors.currentEditor);
    const dispatch = useAppDispatch();
    return (
        <div className="flex flex-col gap-y-4 items-center">
            <div className="text-center py-4 px-2 border-b-2 w-full">
                <h3 className="font-semibold text-xl text-indigo-500">Editors list</h3>
            </div>
            <div className="w-full">
                <ol>
                    {editors.map((editor) => (
                        <li key={editor.id}>
                            <button
                                className={`w-full py-2 border-b hover:bg-indigo-100 text-center ${
                                    currentEditor!.id === editor.id ? "bg-indigo-200" : ""
                                }`}
                                onClick={() => dispatch(setCurrentEditor(editor.id))}
                            >
                                {editor.title}
                            </button>
                        </li>
                    ))}
                </ol>
                <button
                    className="border-b py-2 w-full hover:bg-indigo-100"
                    onClick={() => dispatch(addNewEditor())}
                >
                    New blank editor
                </button>
            </div>
        </div>
    );
};

editorSlice
减速机:

reducers: {
        addNewEditor: (state) => {
            const newEditor: EditorInstance = {
                id: `editor${state.editors.length + 1}`,
                title: `Editor ${state.editors.length + 1}`,
                value: [
                    {
                        type: "paragraph",
                        children: [
                            { text: "This is new editable " },
                            { text: "rich", bold: true },
                            { text: " text, " },
                            { text: "much", italic: true },
                            { text: " better than a " },
                            { text: "<textarea>", code: true },
                            { text: "!" },
                        ],
                    },
                ],
            };
            state.editors.push(newEditor);
            localStorage.setItem("editorsRedux", JSON.stringify(state.editors));
            state.currentEditor = newEditor;
        },
        setCurrentEditor: (state, action: PayloadAction<string>) => {
            const selectedEditor = state.editors.find(
                (editor) => editor.id === action.payload
            );

            if (selectedEditor) {
                state.currentEditor = selectedEditor;
            }
        },
        loadEditorsFromLocalStorage: (state) => {
            const storedEditors = localStorage.getItem("editorsRedux");
            if (storedEditors) {
                state.editors = JSON.parse(storedEditors);
                state.currentEditor = state.editors[0];
            } else {
                const initialEditor: EditorInstance = {
                    id: "editor1",
                    title: "Untitled",
                    value: [
                        {
                            type: "paragraph",
                            children: [
                                { text: "This is editable " },
                                { text: "rich", bold: true },
                                { text: " text, " },
                                { text: "much", italic: true },
                                { text: " better than a " },
                                { text: "<textarea>", code: true },
                                { text: "!" },
                            ],
                        },
                    ],
                };

                state.editors = [initialEditor];
                state.currentEditor = initialEditor;
                localStorage.setItem("editorsRedux", JSON.stringify(state.editors));
            }
        },

        storeContent: (
            state,
            action: PayloadAction<{
                id: string;
                title: string;
                value: Descendant[];
                editor: Editor;
            }>
        ) => {
            const title = action.payload.title;
            const value = action.payload.value;
            const isAstChange = action.payload.editor.operations.some(
                (op) => "set_selection" !== op.type
            );
            if (isAstChange) {
                if (state.currentEditor) {
                    const updatedEditors = state.editors.map((editor) => {
                        if (editor.id === action.payload.id) {
                            return { ...editor, title, value };
                        }
                        return editor;
                    });
                    state.editors = updatedEditors;
                    localStorage.setItem("editorsRedux", JSON.stringify(state.editors));
                }
            }
        },
    },

我想知道,每次用户更改时,我们是否必须初始化一个新的板岩编辑器,或者仅通过传递

initialValue
来更改编辑器的
currentEditor.value
属性,还是必须使用路由来更改
initialValue
Slate
 
。非常感谢您的帮助。

javascript reactjs rich-text-editor slate.js
1个回答
0
投票

您似乎遇到了这样的问题:当您在不同编辑器之间切换时,Slate.js 编辑器的内容不会更新,即使

currentEditor
状态发生更改并正确更新标题。此行为表明 Slate 编辑器没有根据新的
currentEditor.value
重新初始化或更新其内容。让我们解决您在不同编辑器之间切换时如何更新编辑器内容的问题。

方法 1:关键道具

强制 React 组件重新初始化的一种常见方法是更改其

key
属性。 React 使用
key
属性来确定是否应该重新渲染组件或重用以前的实例。通过分配一个随
currentEditor
变化而变化的唯一键,您可以强制 React 使用新内容重新初始化 Slate 编辑器。

在渲染

App.tsx
EditorComponent
中,您可以添加一个关键道具,如下所示:

<EditorComponent
  key={currentEditor.id}
  editor={editor}
  renderElement={renderElement}
  renderLeaf={renderLeaf}
/>

这将导致

EditorComponent
在您切换编辑器时重新安装,确保编辑器使用正确的
currentEditor.value
进行初始化。

方法2:更新Slate的值

另一种方法是在更高级别管理 Slate 的值状态,并在

currentEditor
发生变化时显式更新它。这涉及将状态提升到
App
组件或通过 Redux 存储对其进行管理,然后将编辑器的值作为 prop 传递给您的
EditorComponent

首先,确保您的

EditorComponent
接受编辑器值的新 prop 并将其用作 Slate 的初始值。您可能还需要管理编辑器值的本地状态,以便在更改事件时更新它。这是一个简化的示例:

const EditorComponent = ({ editor, renderElement, renderLeaf, initialValue }) => {
  const [value, setValue] = useState(initialValue);

  useEffect(() => {
    // Update local state when initialValue changes
    setValue(initialValue);
  }, [initialValue]);

  return (
    <Slate
      editor={editor}
      value={value}
      onChange={newValue => setValue(newValue)}
    >
      {/* Your editor setup */}
    </Slate>
  );
};

然后,在您的

App.tsx
中,将
currentEditor.value
作为
initialValue
属性传递给
EditorComponent

<EditorComponent
  editor={editor}
  renderElement={renderElement}
  renderLeaf={renderLeaf}
  initialValue={currentEditor.value}
/>

结论

两种方法都有各自的用例。使用

key
属性是一个更简单的解决方案,它强制编辑器组件完全重新初始化,这可能适合您的情况。从外部管理值状态可以为您提供更多控制,对于更复杂的场景(在编辑器的内容更改时需要执行其他操作)可能是必要的。

请记住,在使用 Redux 等外部状态管理时,请确保状态更新正确触发组件中的重新渲染。调试状态管理问题可能很棘手,因此请确保在编辑器之间切换时验证状态是否按预期更新。

© www.soinside.com 2019 - 2024. All rights reserved.