Home > database >  Updating State in React Component causing it to get unmounted
Updating State in React Component causing it to get unmounted

Time:12-04

I have a component where-in I need to fetch some data and render it. The component gets rendered initially. The problem I'm facing is when the handler function is called after clicking the button for a particular type, the whole component gets unmounted/un-rendered.

While debugging on my own I found this happens after setDocumentType is run inside event handler function. What is wrong in the below code snippet that could possibly cause this issue? I can see the useEffect is not going in infinite-loop as well.

Code snippet:

import * as React from 'react';

const MyComponent = (props) => {
  const [documentType, setDocumentType] = React.useState('alpha');
  const [documentData, setDocumentData] = React.useState('');
  const types = ['alpha', 'beta', 'gamma'];

  React.useEffect(() => {
    myDataFetch('https://example.com/foo/?bar=123').then(async (response) => {
      const data = await response.json();
      setDocumentData(data.terms); // html string
      const myDiv = document.getElementById('spacial-div');
      myDiv.innerHTML = data; // need to render raw HTML inside a div
    });
  }, [documentType]);

  const switchDocumentType = (type) => {
    setDocumentType(type);
    // send some analytics events
  };

  const convertToPDF = () => {
    // uses documentData to generate PDF
  };

  return (
    <div className="container-div">
      {types.map((type) => {
        return (
          <button key={type} onClick={(type) => switchDocumentType(type)}>
            {type}
          </button>
        );
      })}
      <div id="special-div" />
    </div>
  );
};

export default MyComponent;

CodePudding user response:

You shouldn't edit the DOM directly. React has two DOMs, a virtual DOM and a real DOM. Rendering can be a bit finicky if you decide to edit the real DOM.

You can parse html safely, by using html-react-parser. This is the best way to do it, because it becomes part of the react tree whereas dangerouslySetInnerHTML will replace the entire HTML to flush changes to the DOM. With reconciliation, it can create exponential load times.

It will also sanitize your inputs, you know.. for safety. :)

import parse from 'html-react-parser';

const SpecialDiv = ({html}) => {
   const reactElement = parse(html);
   return reactElement
}

If you decide that you must use dangerouslySetInnerHTML you can do it as so:

const [someHTML, setSomeHTML] = useState(null)

const someFunction = async() => {
   const response = await getData();
   const data = await response.json();

   setSomeHTML(data);
}

return( 
   <div>
      {someHTML && <div dangerouslySetInnerHTML={{__html: someHTML}} id="special-div"/>}
   </div>
)

That being said, I would say that by allowing this, you open yourself up to the possibility of a XSS attack, without properly parsing and purifying your inputs.

CodePudding user response:

Do not use useEffect as handler, use useEffect hooks for initializations. Instead of using/setting innerHtml, let react do it for you. I suppose you have myDataFetch defined somewhere and I don't see your data fetch using the type.

Anyways, try to use the modified code below.

   import * as React from 'react';

const MyComponent = (props) => {
  const [documentType, setDocumentType] = React.useState('alpha');
  const [documentData, setDocumentData] = React.useState('');
  const types = ['alpha', 'beta', 'gamma'];

  const fetchData = async () => {
    const response = await myDataFetch('https://example.com/foo/?bar=123')
    const data = await response.json();
    setDocumentData(data);
  }

  React.useEffect(() => {
    fetchData();
  }, []);

  const switchDocumentType = async (e, type) => {
    e.preventDefault();
    setDocumentType(type);
    await fetchData();
    // send some analytics events
  };

  return (
    <div className="container-div">
      {types.map((type) => {
        return (
          <button key={type} onClick={(e) => switchDocumentType(e, type)}>
            {type}
          </button>
        );
      })}
      <div id="special-div">{documentData}</div>
    </div>
  );
};

export default MyComponent;
  • Related