financial_physician
financial_physician

Reputation: 1998

How to prevent component from being re-rendered unnecessarily

I'll start with the code. I have a stateless functional component that resembles this

export const Edit Topic = (_title, _text) {
    const [title, setTitle] = useState(_title)
    const [text, setText] = useState(_text)

    return (
      <>
         <InputText props={{ fieldName:"Title:", value:title, setValue:setTitle, placeHolder:"Topic Title"}}/>
         <InputTextArea props={{ fieldName:"Markdown Text:", text, setText }}/>
         <PreviewBox text={text}/>
      </>
   )
}

I have PreviewBox when it's on, page rendering takes a bit longer because text can be quite long. PreviewBox needs to re-render each time I change text in InputTextArea and that's fine.

The problem I'm having is when I change the value of title it's also updating <PreviewBox/> which is undesired.

How can I make sure that <PreviewBox/> only updates when text changes and not when title changes?

The reason why I believe the re-rendering is occuring is because if I toggle off PreviewBox, there's no lag in when updating title but when PreviewBox is visible the updating the title lags.


import style from "../styles/CreateTopic.module.css"
import { Component } from "react"
import Markdown from "./Markdown";

export class PreviewBox extends Component {
    constructor(props) {
        super(props)

        this.state = {
            isShow: true
        }
    }

    toggleShow = () => {
        console.log("begin isShow", this.state)
        this.setState(state => ({ isShow: !state.isShow}))
    }

    render() {
        return (
            <>
                <div className={style.wrptoggle}>
                        <button className={style.btn} onClick={this.toggleShow}>Preview</button>
                </div>

                {this.state.isShow ?
                    <div className={style.wrppreviewbox}>
                        <div className={style.previewbox}>
                            <Markdown text={this.props.text}/>
                        </div>
                    </div>
                    : null}
            </>
        )
    }
}

Since the above also contains <Markdown/> here's that component:

import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";

const Markdown = ({text}) => {
  return (
    <div>
      <ReactMarkdown
        remarkPlugins={[remarkMath]}
        rehypePlugins={[rehypeKatex]}
        children={text}
      />
    </div>
  );
}
 
export default Markdown;

Upvotes: 1

Views: 515

Answers (1)

Drew Reese
Drew Reese

Reputation: 203512

I don't see any complexity in PreviewBox that would cause any rendering delay so I might assume it's the Markdown component that may take some time "working" when it's rerendered since you say "toggle off PreviewBox, there's no lag in when updating title".

Solution

You can use the memo Higher Order Component to decorate the Markdown component and provide a custom areEqual props compare function.

import { memo } from 'react';
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import ReactMarkdown from "react-markdown";
import "katex/dist/katex.min.css";

const Markdown = ({ text }) => {
  return (
    <div>
      <ReactMarkdown
        remarkPlugins={[remarkMath]}
        rehypePlugins={[rehypeKatex]}
        children={text}
      />
    </div>
  );
};
 
export default memo(Markdown);

By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.

const areEqual = (prevProps, nextProps) => {
  return prevProps.text === nextProps.text;
};
 
export default memo(Markdown, areEqual);

Upvotes: 1

Related Questions