I'm getting the following error whenever I try to use a FontAwesome spinner icon (with className='fa-spin'
) in React:
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
at removeChild (http://localhost:5000/public/bundle.js:19553:22)
at unmountHostComponents (http://localhost:5000/public/bundle.js:13683:11)
at commitDeletion (http://localhost:5000/public/bundle.js:13727:5)
at commitAllHostEffects (http://localhost:5000/public/bundle.js:14419:13)
at HTMLUnknownElement.callCallback (http://localhost:5000/public/bundle.js:5035:14)
at Object.invokeGuardedCallbackDev (http://localhost:5000/public/bundle.js:5074:16)
at invokeGuardedCallback (http://localhost:5000/public/bundle.js:4931:27)
at commitRoot (http://localhost:5000/public/bundle.js:14508:9)
at performWorkOnRoot (http://localhost:5000/public/bundle.js:15510:42)
at performWork (http://localhost:5000/public/bundle.js:15460:7)
EDIT: The issue has come up a couple of times now, and there's really nothing special about the code itself. I've been using the spinner as a loading icon, and the error occurs whenever the spinner is replaced with content. Example:
return (
<div>
{this.state.loading === true ? <i className="fa-spin fas fa-sync"></i> : (
this.state.recipes.length === 0 ? (
<div className='text-center'>
<h2>There doesn't seem to be anything here...</h2><br />
<h2 style={buttonStyle}>Get started by </h2><button style={buttonStyle} className='btn btn-md btn-success' onClick={(e) => this.props.setView(e, 'browserecipes')}>browsing existing recipes</button><h2 style={buttonStyle}> or </h2><button style={buttonStyle} className='btn btn-success btn-md' onClick={(e) => this.props.setView(e, 'addrecipe')}>adding a recipe.</button>
</div>
) : (
<div>
<h1 className='text-center title'>My Recipe Cloud</h1>
<RecipeContainer
recipes={this.state.recipes}
user={this.state.user}
tags={this.props.tags}
setView={this.props.setView}
changeUser={this.changeUser}
/>
</div>
)
)}
</div>
Answers
The error you're encountering, Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node
, typically occurs when React tries to remove a DOM node that doesn't exist or isn't a direct child of the expected parent node during its reconciliation process. This can happen if there's a mismatch or unexpected change in the structure of the DOM while React is updating.
In your case, it seems to be related to how you conditionally render content based on this.state.loading
and then replace the spinner (<i className="fa-spin fas fa-sync"></i>
) with other content when loading
is false
.
Possible Causes and Solutions:
-
Incorrect Removal or Replacement of DOM Nodes:
- Ensure that when you switch from showing the spinner to showing other content, you're not directly manipulating the DOM outside of React's control.
- React's reconciliation algorithm expects that if a component is removed (like your spinner) and replaced with other content, the removal and addition of DOM nodes should align with React's virtual DOM structure.
-
Key Prop for Lists:
- If you're rendering arrays of components conditionally (e.g., in a list), make sure each item has a unique
key
prop assigned. This helps React efficiently update and reorder elements.
- If you're rendering arrays of components conditionally (e.g., in a list), make sure each item has a unique
-
Conditional Rendering Logic:
- Check your conditional rendering logic (
this.state.loading === true ? ... : ...
) to ensure it correctly handles transitions between states without causing unexpected DOM changes or inconsistencies.
- Check your conditional rendering logic (
Specific to Your Code Example:
Your code snippet shows a complex conditional rendering structure. Here are a few recommendations based on common issues that can lead to the error you're encountering:
-
Ensure Consistent Structure: Make sure that when
this.state.loading
transitions fromtrue
tofalse
, the DOM structure that replaces the spinner (<i className="fa-spin fas fa-sync"></i>
) is correctly aligned with what React expects. -
Use Fragments or Containers: Wrap your conditional rendering in React Fragments (
<React.Fragment>...</React.Fragment>
) or<div>
containers to maintain a consistent parent-child relationship in your JSX. -
Avoid Direct DOM Manipulation: React manages the DOM updates based on its virtual DOM reconciliation. Avoid direct DOM manipulations that can interfere with React's expectations.
-
Review Event Handlers: If
this.props.setView
or other event handlers manipulate state directly or asynchronously, ensure they are correctly integrated with React's lifecycle to prevent side effects during rendering.
Example Adjustments:
Here’s a slightly adjusted version of your code with emphasis on correct JSX structure and conditional rendering:
return (
<div>
{this.state.loading === true ? (
<i className="fa-spin fas fa-sync"></i>
) : (
this.state.recipes.length === 0 ? (
<div className='text-center'>
<h2>There doesn't seem to be anything here...</h2><br />
<h2 style={buttonStyle}>Get started by </h2>
<button style={buttonStyle} className='btn btn-md btn-success' onClick={(e) => this.props.setView(e, 'browserecipes')}>browsing existing recipes</button>
<h2 style={buttonStyle}> or </h2>
<button style={buttonStyle} className='btn btn-success btn-md' onClick={(e) => this.props.setView(e, 'addrecipe')}>adding a recipe.</button>
</div>
) : (
<div>
<h1 className='text-center title'>My Recipe Cloud</h1>
<RecipeContainer
recipes={this.state.recipes}
user={this.state.user}
tags={this.props.tags}
setView={this.props.setView}
changeUser={this.changeUser}
/>
</div>
)
)}
</div>
);
Summary:
To resolve the Uncaught DOMException
, ensure your conditional rendering transitions between states (loading
true/false) without causing unexpected changes in the DOM structure that React manages. Review your JSX structure, event handling, and state transitions to align with React's expectations for reconciliation and rendering.