editor.js icon indicating copy to clipboard operation
editor.js copied to clipboard

Editor render issue with data props

Open altumsoftwareds opened this issue 2 years ago • 8 comments

I am waiting when I will get response with some structure and I need to pass it to Editor.

( I also see such error in console: Block «paragraph» skipped because saved data is invalid )

so on initial render editor triggers onChange method with empty data {time: 1697134792817, blocks: Array(0), version: '2.28.0'} ( I tested it using setTimeout )

and it overrides data I save in useState. That's why I see empty editor.

Full code :

import React from "react";
import { Button, styled, TextField } from "@mui/material";
import { PolicyCybertaskContext } from "..";
import { Column, Row } from "components/containers";
import Widget from "components/widget";
import { CopyAll } from "@mui/icons-material";
import {
    UpdatePolicySectionDtoInput,
    useUpdatePolicySectionMutation,
} from "lib/apollo/types";
import dynamic from "next/dynamic";
import { editorDataTransform } from "lib/utils/editorDataTransform";

interface Props {
    onPrev: () => void;
    onNext: () => void;
    section: number;
    item: number;
}

const Editor = dynamic(() => import("components/Editor"), {
    ssr: false,
  });

const PolicySections: React.FC<Props> = ({ onPrev, onNext, section, item }) => {
    const { template, policy } = React.useContext(PolicyCybertaskContext);
    const [error, setError] = React.useState<string>();
    const [answer, setAnswer] = React.useState<any>({});
    const [updateSection] = useUpdatePolicySectionMutation();

    React.useEffect(() => {
        if (!policy || !policy.policySections[section]) {
            setAnswer({});
            return;
        }
        const currentItem = policy.policySections[section].items[item];
        if (!currentItem) {
            setAnswer({});
        } else {
            setAnswer(editorDataTransform(currentItem));
        }
    }, [section, item, policy]);

    const save = () => {
        setError(undefined);
        if (!policy?.policySections[section]) return;

        const dto: UpdatePolicySectionDtoInput = {
            id: policy.policySections[section].id,
            items: policy.policySections[section].items[item]
                ? policy.policySections[section].items.map((a, index) => {
                      if (index === item) {
                          return JSON.stringify(answer);
                      }
                      return a;
                  })
                : [...policy.policySections[section].items, JSON.stringify(answer)],
        };

        updateSection({ variables: { dto } });
    };

    const saveAndGoBack = () => {
        if (answer.length === 0) {
            setError("This field is required.");
            return;
        }
        save();
        onPrev();
    };

    const saveAndContinue = () => {
        if (answer.blocks.length === 0) {
            setError("This field is required.");
            return;
        }
        save();
        onNext();
    };

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setError(undefined);
        setAnswer(e.target.value);
    };

    const copyExample = () => {
        if (
            template?.templatePolicy.templatePolicySections[section]
                .templatePolicySectionItems[item].exampleAnswer
        )
            setAnswer(
                template?.templatePolicy.templatePolicySections[section]
                    .templatePolicySectionItems[item].exampleAnswer
            );
    };

    console.log(policy?.policySections)
    console.log(item)
    console.log(answer)

    return (
        <Container>
            <QuestionBox color="primary">
                {
                    template?.templatePolicy.templatePolicySections[section]
                        .templatePolicySectionItems[item].question
                }
            </QuestionBox>
            <Row>
                <Widget color="complimentary3.lightest" sx={{ width: "50%" }}>
                    <HeaderRow>
                        <h4>Example answer</h4>
                        <Button
                            variant="text"
                            sx={{ padding: "0" }}
                            onClick={copyExample}
                        >
                            <CopyAll />
                        </Button>
                    </HeaderRow>
                    {template?.templatePolicy.templatePolicySections[
                        section
                    ].templatePolicySectionItems[item].exampleAnswer
                        .split("\n")
                        .map((line, i) => (
                            <p key={i}>{line}</p>
                        ))}
                </Widget>
                <Widget color="complimentary3.lightest" sx={{ width: "50%" }}>
                    <Row>
                        <h4>Your answer</h4>
                    </Row>
                    <p>{JSON.stringify(answer)}</p>
                    <Editor data={answer} onChange={(data) => {
                        console.log('data', data)
                        setAnswer(data)
                        // if(data.blocks.length !== 0){
                        //     setTimeout(()=> {setAnswer(data)},3000)
                        // }
                    }} editorblock={`editorjs-container-${section}`}/>
                </Widget>
            </Row>
        </Container>
    );
};

Безымянный

altumsoftwareds avatar Oct 12 '23 18:10 altumsoftwareds

If I will pass expected data directly as prop ( not waiting for response ) -- there is no issues at all.

altumsoftwareds avatar Oct 12 '23 19:10 altumsoftwareds

@neSpecc could you help, please? 1) Безымянный

Безымянный

  1. onChange triggers with empty data however current answer is exists and it rewrites answer to be empty
  2. even after some additional rerenders of component ( as soon as I get requested from BE data from props ) current answer becomes correct again, but editor doesn't rerenders with correct answer ( data ) object

altumsoftwareds avatar Oct 12 '23 21:10 altumsoftwareds

<Editor 
                        key={`editor-${section}-${item}`} 
                        data={answer} 
                        onChange={(data) => {
                            console.log('editor beforeOnChange answer', answer)
                            console.log('editor onChange', data)
                            setAnswer(data)
                        }}
                        editorblock={`editorjs-container-${section}-${item}`} 
                    />

why I don't see current answer after onChange as soon as I have setAnswer which is the part of useState?

altumsoftwareds avatar Oct 12 '23 21:10 altumsoftwareds

@altumsoftwareds would you test in it 2.29.0-rc.4?

neSpecc avatar Oct 18 '23 21:10 neSpecc

I am not sure if this is related, I am getting the same "warning" when I put editor.save() method inside the onChange function. However It is fine when I trigger editor.save() somewhere else

editor = new EditorJS({
    holder : 'summary',
    onChange: function(api, event) {
        editor.save().then(console.log)
    }
});

ls84 avatar Oct 23 '23 06:10 ls84

@ls84 where to put editor.save() else, for example?

altumsoftwareds avatar Oct 23 '23 13:10 altumsoftwareds

@ls84 where to put editor.save() else, for example?

I am guessing the problem of Block «paragraph» skipped because saved data is invalid is caused by saving data before it's ready to be saved out. My solution was to bind editor.save() to a button click action. Hope this helps

ls84 avatar Oct 27 '23 08:10 ls84

It's because the Paragraph delays saving its data to element. And the save function makes data from its element. (And the warning occurs when the element has empty body)

requestAnimationFrame causes the problem.

https://github.com/editor-js/paragraph/blob/6fff362a536599ba1f05a9dfd642d51668ac557b/src/index.js#L239-L254

There are two solutions.

  1. Override the Paragraph and remove 'requestAnimationFrame' logic.
  2. Save using requestAnimationFrame inside onChange. This will make the save is called after hydrate. (FIFO)

a3626a avatar Nov 28 '23 01:11 a3626a