0

I have the following React component. The top-level component is <App />.

Let's say the <div id="keyDiv"> element were to get deleted (line 10 of the code snippet below). What triggers this isn't really important to me - maybe another part of the App did it, or maybe a user did it via Chrome Devtools. Either way, deleting <div id="keyDiv"> will effectively kill all the children of <Container> (at line 9).

Is there any way to re-render the children of <Container>, so that those children will get re-generated? (particularly <div id="keyDiv"><Modal/></div>)

I have tried updating the state of Container, but this does not restore its children.

import React, {FC, useState} from 'react';
const App : FC = () => {
  const styles = {
    border:'1px solid black',
    backgroundColor: 'palegoldenrod',
    padding:'10px'
  }
  return (
    <Container>
      <div style={styles} id="keyDiv">
        <Modal />
      </div>
    </Container>
  )
}
const Container : FC = ({children}) => {
  const styles = {
    border:'1px solid black',
    padding:'10px'
  }
  const [label, setLabel] = useState('This is my label');
  return (
    <div>
      <div style={styles}>
        {label}
        {children}
      </div>
      <button onClick={() => {setLabel(`This is the date/time: ${new Date().toLocaleTimeString()}`)}}>Click me</button>
    </div>
  )
}
const Modal : FC = () => {
  const styles = {
    border:'1px solid black',
    padding:'10px',
    backgroundColor: 'lightgreen'
  }
  return (
    <div style={styles}>
      This should be the modal
    </div>
  )
}
Vendetta
  • 2,078
  • 3
  • 13
  • 31
Cog
  • 1,545
  • 2
  • 15
  • 27
  • Why would a user be modifying your code in dev tools? If they are doing that, I don't think they can expect the code to work properly after deleting something. – dmikester1 Feb 06 '20 at 18:05
  • I can't open up the source code on Stack Overflow and start deleting elements and then complain to Stack Overflow that their site isn't working for me. ;) – dmikester1 Feb 06 '20 at 18:08
  • @dmikester1 A sample use case is - think of a blocking modal that pops over a news site when you're not a subscriber. A user could use devtools to kill the modal and bypass it. I'd want to be able to bring that modal back up. – Cog Feb 06 '20 at 18:24
  • I don't think that is even possible. Once a user opens up the source in the dev tools, they can do anything they want, all bets are off. Someone else can chime in if I'm wrong, but I don't think there is any possible way for your app to watch for something like that. – dmikester1 Feb 06 '20 at 18:29
  • @dmikester1 Reworded the question. Whether the
    was deleted by user intervention or other means isn't really important for the purposes of this question. Thanks for your input though.
    – Cog Feb 06 '20 at 18:44
  • There is a similar question [here](https://stackoverflow.com/questions/44935865/detect-when-a-node-is-deleted-or-removed-from-the-dom-because-a-parent-was). Perhaps listen for the mutation, and then leverage that to re-render? – Seth Lutske Feb 07 '20 at 00:18
  • @SethLutske Thanks, that's certainly worth investigating, but then how would I re-render all the children of in my example? – Cog Feb 07 '20 at 05:52
  • I figured it out. Let me post the answer... – Seth Lutske Feb 07 '20 at 19:00
  • I've been messing around and I realize that there's a whole bunch of other ways that a user messing around in the devtools can bypass your div. Messing with the css, doing a 'Hide element', god knows what else is in there that we never played with before. This is probably not a great security measure. Probably better to conditionally render the content if a person is logged in.... – Seth Lutske Feb 07 '20 at 20:59

1 Answers1

1

You need to use MutationObserver. Here's how I did it - some code with notes for each step:

import React, { useState } from 'react';

// Transfer to class component so I can use componentDidMount
class App extends React.Component {

  styles = {
    border:'1px solid black',
    backgroundColor: 'palegoldenrod',
    padding:'10px'
  }

  // When component mounts, create a reference to the div you want to target
  componentDidMount(){
    const immortalDiv = document.getElementById('keyDiv')
    // get a reference to its parent
    const immortalDivParent = immortalDiv.parentNode

    // config for your MutationObserver
    const MOconfig = { attributes: true, childList: true, subtree: true };

    // callback for your MutationObserver
    const callback = (mutationsList, observer) => {
      for(let mutation of mutationsList) {
        // check mutations in the childlist of the parent of the div you're trying to protect 
        //there's probably other things you can use to check that the mutation that occurs is the one you're trying to detect
        if (mutation.type === 'childList') {
          // if the childlist changes, you can run this code
          console.log('! Someone tried to remove your div - The Hamburgler perhaps !')
          // append your div back into its parent
          immortalDivParent.appendChild(immortalDiv)
          // diconnect the observer, otherwise you'll get stuck in an infinite loop
          observer.disconnect()
          // reconnect it right away to keep listening
          observer.observe(immortalDivParent, MOconfig)
        }
      }
    }

    const observer = new MutationObserver(callback)

    observer.observe(immortalDivParent, MOconfig)

  }


  render(){
    return (
      <Container>
        <div style={this.styles} id="keyDiv"><Modal /></div>
      </Container>
    )
  }
}

Now this is not a foolproof answer, as it will respond to any change in the parent div's children. But I'm sure a few more lines of code can help check for that, and then only re-render the div if that div was deleted. This answer doesn't use a react re-render. I tried things with this.forceUpdate or using state and componentDidUpdate to make it work, but this seems the most straightforward. I'd be curious to know how to do it with React lifecycle methods.

Here is a working codesandbox. If you open up the dev tools and delete your div, you'll see that the Hamburgler may have been the culprit, but the code puts it right back.

Seth Lutske
  • 9,154
  • 5
  • 29
  • 78