Undefined
Undefined

Reputation: 1021

How to avoid re-render in React?

I am making a simple accordion which has text editor inside it.

If we click expand text then the text editor gets opened and if we enter some text inside the editor and click shrink, then the accordion gets closed.

Again if click on the expand text of accordion where we made the changes, then the text already entered is missing inside it.

I can understand that this re render every time we click on the expand text. Also this code,

<Text>  {toggleValue === index && item.content && <EditorContainer />} </Text>

check for the item clicked then it gets opened so re render happens here and hence I am losing the entered text.

Complete working example:

https://codesandbox.io/s/react-accordion-forked-dcqbo

Could you please kindly help me to retain the value entered inside the text editor despite of the clicks over the text Expand/Shrink?

Upvotes: 0

Views: 1198

Answers (2)

CertainPerformance
CertainPerformance

Reputation: 371193

Put the editor's state into a persistent parent component. Since the NormalAccordion encompasses all editors, and you want persistent state just one editor, use another component, so that the state doesn't get lost when the editor unmounts, then pass it down for the editor to use:

const OuterEditorContainer = ({ toggleValue, setToggleValue, item, index }) => {
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  const toggleHandler = (index) => {
    index === toggleValue ? setToggleValue(-1) : setToggleValue(index);
  };
  return (
    <Accordion>
      <Heading>
        <div
          style={{ padding: "10px", cursor: "pointer" }}
          className="heading"
          onClick={() => toggleHandler(index)}
        >
          {toggleValue !== index ? `Expand` : `Shrink`}
        </div>
      </Heading>
      <Text>
        {toggleValue === index && item.content && (
          <EditorContainer {...{ editorState, setEditorState }} />
        )}
      </Text>
    </Accordion>
  );
};
const NormalAccordion = () => {
  const [toggleValue, setToggleValue] = useState(-1);
  return (
    <div className="wrapper">
      {accordionData.map((item, index) => (
        <OuterEditorContainer
          {...{ toggleValue, setToggleValue, item, index }}
        />
      ))}
    </div>
  );
};
// text_editor.js
export default ({ editorState, setEditorState }) => (
  <div className="editor">
    <Editor
      editorState={editorState}
      onEditorStateChange={setEditorState}
      toolbar={{
        inline: { inDropdown: true },
        list: { inDropdown: true },
        textAlign: { inDropdown: true },
        link: { inDropdown: true },
        history: { inDropdown: true }
      }}
    />
  </div>
);

You could also put the state into the text_editor itself, and always render that container, but only conditionally render the <Editor.

Upvotes: 1

Pedro Silveira
Pedro Silveira

Reputation: 342

You need to save the entered text and pass it as props from the parent component to EditorContainer.

Right now everytime you render it (e.g. when we click expand) It looks like you set an empty state.

Something like:

EditorContainer

  editorState: this.props.editorState || EditorState.createEmpty()

  onEditorStateChange = (editorState) => {
    // console.log(editorState)
    this.props.setEditorState(editorState);
  };

And in Accordion:

{toggleValue === index && 
 item.content && 
<EditorContainer 
editorState={this.state.editorState[index]} 
setEditorState={newText => this.setState({...this.state, newText}) />}

Didn't try to execute it, but I think that's the way to achieve it. Ps: Class components are almost not used anymore. Try to use function components and learn about useState hook, looks so much cleaner in my opinion

Upvotes: 1

Related Questions