React contenteditable in stateless component - javascript

I am trying to implement a contenteditable div inside a stateless react component.
I keep getting the below warning:
warning.js:36 Warning: A component is `contentEditable` and contains `children` managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.
How do I fix this?
Also how do I read contents of div on change?

Add suppressContentEditableWarning="true" to contenteditable div.
Reference: https://github.com/facebook/draft-js/issues/81

As with any React application, browser plugins and extensions that modify
the DOM can cause Draft editors to break.
Grammar checkers, for instance, may modify the DOM within
contentEditable elements, adding styles like underlines and
backgrounds. Since React cannot reconcile the DOM if the browser does
not match its expectations, the editor state may fail to remain in
sync with the DOM.
https://github.com/facebook/draft-js/issues/53
A known error. As for reading whats in a div, assign the element an id and..
oDoc = document.getElementById("divelement");
sDefTxt = oDoc.innerHTML;

Warning: A component is `contentEditable` and contains `children` managed by React
Resolved by adding...
//...
<div
suppressContentEditableWarning={true} // <-- Add this
className="MyClass"
onClick={ ()=> { onEidtHandler() } }
onBlur={ ()=> { onSaveHandler() }
>
Editable content
</div>
//...

Related

React Hooks - Using useRef without direct access to HTML Element Code?

With React I'm inside of one repository, and the HTML elements are loading from another repo, which I watch for using pageLoaded. Inside updateHeader there is just more HTML element selecting and attribute/class manipulation.
useEffect(() => {
if (pageLoaded.status) {
if (someCondition) {
updateHeader(ubeName);
} else {
document.querySelector('.headerbar-menu').style.display = 'block';
if (document.querySelector('.headerbar-menu.affiliates-wrapper')) {
document.querySelector('.headerbar-menu.affiliates-wrapper').style.display = 'none';
}
}
}
}, [pageLoaded.status])
The problem here is obviously we shouldn't be using querySelector, and i think it may be causing some unexpected functionality. The elements dont properly evaluate and render until some piece of state changes i.e. a scroll state handler, so on initial page load the elements dont show with their new attributes until a scroll.
I'd like to attempt to resolve using useRef, but don't have direct access to the html. Is there a way to dynamically connect a ref to an element without access to the HTML code?
Thanks for your time and attention!
The best way to do this, in my opinion, is in this sequence:
Attempt to select the element
If non-existant, set up a DOMSubtreeModified event handler or a MutationObserver
Clean up DOMSubtreeModified event handler or a MutationObserver (or keep it around to watch for updates)
This allows for use of query selectors in a safe and modern way which doesn't stray too far from React's recommendations

byRole is not returning the DOM element

I am migrating some of my unit test cases which were previously written using Jest and Enzyme to React Testing Library. I am using Material UI's Select component and I know that, in order to open the dropdown, we have to trigger the mouseDown event on the corresponding div. Here is how I did it in Enzyme (working):
wrapper.find('[role="button"]').simulate('mousedown', { button: 0 });
I am trying to achieve the same using React Testing Library in the following manner, which is not working:
const { container, getAllByRole, getByRole } = renderComponent(mockProps);
fireEvent.mouseDown(getByRole('button')); // trigger the mouseDown on div having role=button
After this I am trying to access the listbox element which is ul element:
getByRole('listbox')
which throws an error and says:
TestingLibraryElementError: Unable to find an accessible element with the role "listbox"
There are no accessible roles. But there might be some inaccessible roles. If you wish to access them, then set the `hidden` option to `true`. Learn more about this here: https://testing-library.com/docs/dom-testing-library/api-queries#byrole
I have verified and the ul element is visible (neither it nor its parent have display none or visibility hidden)
UPDATE - 1
I have tried all the following approach to wait for element to appear in DOM:
waitFor
findByRole instead of getByRole
in both the case it throws error that
Unable to find role="listbox"
What is wrong?
If the element is not accessible at the first render this error could appear, you can try passing the option hidden to the options key in the second argument of the getByRole, if this not works you can try using findByRole, that method wait for the element, and if you want to be sure of the visibility of the element you can try adding a waitFor inside of the test.
getByRole('listbox', { options: { hidden: true } });
Make sure animations are not compromising your results. If the animations change opacity or display and they did not finish before assertion from jest... it is likely to throw an error.
I found the root cause of this issue. I am using the Select component from MUI in disablePortal mode, which make the menu list to be rendered inside the parent component instead of document body. But while MUI does that, it doesn't remove the aria-hidden attribute from the parent component's div, and because of that testing library is not able to locate the listbox (ul) element inside.
There is an issue reported here:
https://github.com/mui/material-ui/issues/19450
So as a work around, I passed the data-testid to the menu component (MenuListProps) and using it to get the listbox.

React.js: Getting "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node."

I have a <span> element that is inside of a contentEditable <div>. In certain situations, when I try to delete everything in the <div> at once with Command-Delete, or when I try to delete individual characters from the <span> element when it is the only thing inside of the <div>, I get the error in the title.
How can I go about fixing this?
I put together a sandbox example of the issue:
https://codesandbox.io/s/nostalgic-wildflower-52eul?file=/src/App.js
It throws the error under two circumstances (the keyword in the example is test, it should get highlighted in blue when you type it):
Type any string with test inside of it or just test and highlight everything and delete.
Type any string with test inside of it, delete everything else except for test, then delete any character in test.
Thanks for any help!
You most probably read the following error when you set contentEditable of your div to true:
Warning: A component is `contentEditable` and contains `children` managed by React. It is now your responsibility to guarantee that none of those nodes are unexpectedly modified or duplicated. This is probably not intentional.
The problem here is that you let React add a child node to the content editable div and remove it through the actual DOM when interacting with it and removing all of its content.
React tries to re-render, and remove the span that should not be existent on next render however, you already removed it through your keyboard input.
You should only suppress the warning if you're absolutely sure about what you're doing, as it is warning you about the exact error you're getting.
The solution you're going for will not work as you would like it to.
You could be better off trying to implement an edit mode and highlighting the words after the user saves the content. This is only an idea as I don't know exactly what you're trying to achieve.
More info on this: https://stackoverflow.com/a/49639256/7381466
Just give the key to the parent element i.e div className='app ' according to your code or add a new parent element and just give the key to that element.
and if anything changes and you get this error, just update the key of the parent element 
Because updating that parent element key causes react to detach the parent element from its dom, you can avoid this error because the problem is with the child element.
In onInput just check if the elemeent with id="addItemInput" is present or not and if it is not there just update the parent element key

Get parent div in React Component

I have a table within a bootstrap panel.
Something like this:
<Panel className='userPanel'>
<Table/>
</Panel>
Now I need to have a reference to Panel within Table. There can be a lot of tables/panels on one site the same time, so I don't want to use ids to use them with ReactDom.findDOMNode.... What is the best solution for this?
Edit:
I need a reference in the table because I use Facebooks datatable. These tables do have a static width/height so to make them fluid, I need to add a resize handler that checks the width and height of the parent to adjust.
What you are suggesting breaks the way that React encourages you to work. Components should be encapsulated, all the data they need should be passed to them as props. Every time you make calls out from a component you invalidate their referential transparency i.e. the output of a component (usually what is rendered) should be the result of the props passed to it.
Having said that, people use React in different ways (it even gives you setState to wreak havoc with purity) however, passing the actual DOM node to children is difficult as when they are instantiated their parents' DOM node does not exist.
I dont know the Facebook DataTable API so I can't really suggest a better way of restructuring your app, but it sounds like you just need a dynamic width/height to size the table so pass down a function to get that.
class Parent extends React.Component {
constructor( props ) {
super( props )
}
getDimensions() {
// Do some stuff here to return dimensions
return {
width: XXX,
height: XXX
}
}
render() {
<Parent ref="el">
<Table getDimension={ this.getDimensions } />
</Parent>
}
}
You still can't call this.props.setDimensions from within Table:render but you cant with the .parentNode solution anyway so I'm guessing you’re already rendering the table in componentDidMount (or later).

Updating content in contenteditable container with React

I want to break the text the user inputs into the content editable container and replace the content of the container with the same text wrapped in <span> elements.
Here's my render method:
render: function() {
var children = [],
index = 0;
this.state.tokens.forEach(function(token) {
children.push(<span key={index++}>{token}</span>, <span key={index++}> </span>);
});
return <div
ref="input"
className="input"
contentEditable="true"
onKeyPress={this.keyPress}
>{children}</div>;
}
(entire example in JSFiddle)
What happens is that after the interval (600ms) from user input, when the state changes and the component is rendered, the children are added but React for some reason adds the original text that was in the container, so it kind of duplicates the text.
Another things is that if then the user selects text and deletes it, in the next update React will throw all kind of errors such as:
Uncaught TypeError: Cannot read property 'parentNode' of undefined
and
Uncaught Error: Invariant Violation: findComponentRoot(..., .0.$2):
Unable to find element. This probably means the DOM was unexpectedly
mutated (e.g., by the browser), usually due to forgetting a
when using tables or nesting or tag......
Any ideas of why this happens?
Thanks
Unfortunately contenteditable doesn't work properly with React-generated children right now: Uncaught Error when using ContentEditable="true" within Chrome.
One current workaround is to build the HTML yourself or use React.renderToStaticMarkup and use React's dangerouslySetInnerHTML, though you lose some of React's benefits by doing so.

Categories