I've created a Row and a Col components for an Card in Accordion using Bootstrap - simple, not React.Bootstrap or whatever. Row and Col just show their children, the Card just has more props and also just shows the children. Like that:
class Col extends React.Component {render () {return (
<div className='col' id={'col_'+this.props.colid} key={shortid.generate()}>
{this.props.children}
</div>)}}
class Row extends React.Component {render () { return (
<div className='row' id={'row_'+this.props.rowid} key={shortid.generate()}>
{this.props.children}
</div>)}}
Then I want to insert a textarea, which is provided with a function to make some changes to the text and I display these changes within a div below the textarea. Also I've got a button, that copies the text to buffer, based on clipboard.js
The first version of render function works just fine, but it doesn't give me the needed design, so I came up with the second version, based on Row and Col components, described above.
The contents mainly does not differ - the same textarea, checkbox, button and div. What differs is the layout. In the first version there is no layout at all, in the second I tried my best :)
So:
render () { return (<React.Fragment>
<textarea id='xng0' onChange={this.handleChange} ref={this.xng0ref} />
<CopyButton target="st0" message="Copy text" /> <br />
<input type='checkbox' onChange={this.toggleTranslit}
defaultChecked={this.state.tranParam} /><span>Translit?</span><br />
<div id='st0' border='1' ref={this.st0ref} >
{this.state.st0value}
</div></React.Fragment>)}
The second version:
render () {return ( <React.Fragment><Row><Col>
<textarea id='xng0' onChange={this.handleChange} ref={this.xng0ref} />
</Col><Col><Row><Col>
<CopyButton target="st0" message="Copy text" />
</Col></Row>
<Row><Col>
<input type='checkbox' onChange={this.toggleTranslit}
defaultChecked={this.state.tranParam}
/><span>Translit?</span>
</Col></Row>
</Col>
</Row>
<Row>
<div id='st0' border='1' ref={this.st0ref}>
{this.state.st0value}
</div></Row></React.Fragment>)}
My problem is: while the first render() version keeps focus in the textarea, when I type, the second version throws the focus off the textarea after the first letter was typed AND clears the textarea. That is I have no possibility to input some long text - I get only one letter.
What did I miss? Why this happens and how to make the focus stable?
As you were already prompted in the comments, you have unique keys for each render on the Col and Row components, because key={shortid.generate()}.
After writing a character in the textarea, you most likely change the state, then rerender happens and in the place of the current textarea, a new one appears, naturally without focus.
I recommend that you carefully read why the keys are created in the React. Link https://reactjs.org/docs/lists-and-keys.html#keys.
I must say right away that they were created to identify the list items, which is completely violated when you generate unique keys on each render.
please add 'value' property to your textarea
Related
Updated My Question based on comments. Not able to make small snippets as its a legacy code with lot of interacting components and I am very new to React to understand all the components.
I have the following GUI:
If you see carefully I have multiple fields with corresponding dropdowns. This combination is represented within a Div in my code under a class within render() function.
The problem is the Div code is repeatedly copy-paste to create a new
field and dropdowns. I don't want to repeat the whole div again and
again and want to replace it with some variable or function which
accepts Field name and DROP_DOWN_TYPE.
I have tried many solutions but it resulted in various errors due to
onchange event.
Repeated div is shown below:
<div className="infoBlock">
<div className="row align-items-center">
<div className="col-6">
<div className="row">
<div className="col-4">
<label className="b_label">Category :</label>
</div>
<div className="col-8">
<Dropdown type="Categories" isMultiple={true} onchange={this.handleDDClick} />
</div>
</div>
</div>
<div className="col-6">
</div>
</div>
</div>
I can't show DropDown code because its a huge file. But its a normal drop down which makes a service call to populate its fields.
Can you give more clarity by making a demo kind of thing on codesandbox.io ?
What I feel is you are repeatedly calling the setState function according to the error.
Either your state is being called repeatedly somewhere , or you need to bind that this.handleDDClick onto that onchange in Dropdown tag. Sometimes it gets triggered without actually any change happening in the dropdown.
working on a ReactJs project. It is worth noting I am a uni student is very new to react. Part of the code contains an image that can be clicked like a button. Once clicked a react Modal element is opened and the contents of an array are displayed inside
Screen of arrays printed contents
As you will see from the above image, each array item starts with a different number. The issue is all the array elements are printed on one continuous line without spacing inbetween each element.
here is the code for the Modal
<button className="space boxButton button3" onClick={this.openGreenModal}>
<img className="boxImg" src={greenBox} />
<div onClick={e => e.stopPropagation()}>
<Modal className="ModalGreen" isOpen={this.state.greenVisible}>
<div>
<img className="boxImgPopUp" src={greenBox} />
<h1> Green box testing</h1>
//PRINTING ARRAY ITEMS ON THIS LINE
<p>Items: {this.state.greenArray}</p>
<button onClick={this.closeGreenModal}>Close</button>
</div>
</Modal>
</div>
</button>;
is there a way in which I can display each item on a new line?
if anymore code is needed for the project pls do let me know
Searching for "react render list" put me in the React documentation for exactly this sort of thing: https://reactjs.org/docs/lists-and-keys.html
To give an actual answer though, React is just creating HTML for you so you would want to create HTML tags to render items on different lines just like if you were creating that HTML by hand.
Something like:
{this.state.greenArray.map((item) =>
<p key={item.something_unique}>{item}<p>
)}
I'm building out something that allows users to upload N-number of data files. In service of this, I've added a button that will create an additional file upload option. This is done with a simple for loop in the render function (there is a selection option that only appears if certain conditions are met, which is the 'mergeColumnSelection' variable, you can ignore that but I'm including it in case it somehow ends up being relevant):
let renderedEnrichedDataFields = [];
for(let i = 0; i < this.state.enrichedData.length; i++) {
let mergeColumnSelection = ""
if(this.state.enrichedData[i] !== null) {
mergeColumnSelection = <div className="form-item__select form-item">
<label className="form-label" htmlFor="add-target-owner">Merging Column</label>
<div className="form-item__container">
<select onChange={(e) => {this.setEnrichedMergeColumn(e, i)}} defaultValue={this.state.csvColumns[0]}>
{mainDataColumns}
</select>
</div>
</div>
}
renderedEnrichedDataFields.push(
<div className="form-group">
<button onClick={() => {this.removeEnrichmentData(i)}} type="button" className="modal-close">
<Icon name="close" />
</button>
<div className="form-item">
<label className="form-label" htmlFor="add-target-csv">Enrichment Dataset</label>
<input
className="csv-input"
type="file"
accept="text/csv"
onChange={(e) => {this.acceptNewEnrichedDataFile(e, i)}}
/>
</div>
{mergeColumnSelection}
</div>
)
}
Basically, every time the button is pressed a new element is pushed into the enrichedData array in state. This causes the application to render an additional file input. When a user uploads a file, the placeholder element in the array is replaced with the file. When the user eventually submits the form, an array of files will be submitted which is great.
However! I'm having a lot of trouble getting a clean implementation for the ability to REMOVE these input fields. The function
removeEnrichmentData(index) {
let enrichmentData = this.state.enrichedData
let enrichedMergeColumns = this.state.enrichedMergeColumns;
enrichmentData.splice(index, 1);
enrichedMergeColumns.splice(index, 1)
this.setState({enrichedData: enrichmentData, enrichedMergeColumns: enrichedMergeColumns});
}
As you can see this takes the index of the selected input, then splices it from the array that generates the for loop. The appropriate file is spliced from the array, and the number of file inputs is correct. However, there are problems with what file name is displayed. Pictures will help:
Here you can see a sample where someone is preparing to upload three
files, health, cluster, and starbucks
Now I select to remove the cluster item (item 2) from the list. It is
removed from the file list in state, leaving just health and
starbucks. However, the for loop simply runs through twice and drops
the last item - meaning that it appears that the health and cluster
are the remaining two files, even though in actuality they are health
and starbucks
I thought about moving the JSX block itself into state so I can specifically target the JSX input element I want removed - but have had limited success with this approach and read that it's not advisable to put JSX into the state. React doesn't really have built in ways to easily delete the specific input, and I can't set default values in file inputs so I can't easily tie the individual inputs to their counterpart in state.
It feels like it should be such a simple problem and I'm very stuck. Any help is appreciated!
#Miloš Rašić is correct - your initial problem is that you're probably using array indices for the keys for the inputs. So, if you have 10 inputs numbered 0...9, and you delete the input with index 5, you're still rendering items with keys 0..8, and React thinks the last one was removed.
Per your comment about using UUIDs, it sounds like you're generating unique IDs in the render() method itself. DO NOT DO THAT! Never generate random values for keys in render(). When you do that, you're telling React every time that "this item is different than the last time we rendered, please destroy the existing item here and replace it with a new one".
Instead, you should generate these unique IDs when you add a new entry into your state. For example:
class FileInputList extends Component {
state = { inputs : [] }
addNewFileInput = () => {
const inputID = uuid();
const newInputs = this.state.inputs.concat({id : inputID});
this.setState({inputs : newInputs});
}
render() {
const {inputs} = this.state;
const inputList = inputs.map(inputEntry) => {
return <input type="file" key={inputEntry.id} />
});
return inputList;
}
}
It's hard to be sure without a fully working example, but this awfully similar to a "working" example of how React messes up when you don't give arrays of elements a key prop. Don't you get warnings about this from React? Try giving the divs you are pushing to the array a key prop which won't change for an existing element when an element is deleted (so key={i} won't work).
For example, if you are rendering
<input type="file" key={1} />
<input type="file" key={2} />
<input type="file" key={3} />
<input type="file" key={4} />
when you delete the one with key={2} it should be
<input type="file" key={1} />
<input type="file" key={3} />
<input type="file" key={4} />
Some sort of incrementing id like in relational databases or a generated unique id would do the trick.
Have you tried this:
{this.removeEnrichmentData(index)}}
Here is a working example.
I'm using Preact (for all intents and purposes, React) to render a list of items, saved in a state array. Each item has a remove button next to it. My problem is: when the button is clicked, the proper item is removed (I verified this several time), but the items are re-rendered with the last item missing, and the removed one still there. My code (simplified):
import { h, Component } from 'preact';
import Package from './package';
export default class Packages extends Component {
constructor(props) {
super(props);
let packages = [
'a',
'b',
'c',
'd',
'e'
];
this.setState({packages: packages});
}
render () {
let packages = this.state.packages.map((tracking, i) => {
return (
<div className="package" key={i}>
<button onClick={this.removePackage.bind(this, tracking)}>X</button>
<Package tracking={tracking} />
</div>
);
});
return(
<div>
<div className="title">Packages</div>
<div className="packages">{packages}</div>
</div>
);
}
removePackage(tracking) {
this.setState({packages: this.state.packages.filter(e => e !== tracking)});
}
}
What am I doing wrong? Do I need to actively re-render somehow? Is this an n+1 case somehow?
Clarification: My problem is not with the synchronicity of state. In the list above, if I elect to remove 'c', the state is updated correctly to ['a','b','d','e'], but the components rendered are ['a','b','c','d']. At every call to removePackage the correct one is removed from the array, the proper state is shown, but a wrong list is rendered. (I removed the console.log statements, so it won't seem like they are my problem).
This is a classic issue that is totally underserved by Preact's documentation, so I'd like to personally apologize for that! We're always looking for help writing better documentation if anyone is interested.
What has happened here is that you're using the index of your Array as a key (in your map within render). This actually just emulates how a VDOM diff works by default - the keys are always 0-n where n is the array length, so removing any item simply drops the last key off the list.
Explanation: Keys transcend renders
In your example, imagine how the (Virtual) DOM will look on the initial render, and then after removing item "b" (index 3). Below, let's pretend your list is only 3 items long (['a', 'b', 'c']):
Here's what the initial render produces:
<div>
<div className="title">Packages</div>
<div className="packages">
<div className="package" key={0}>
<button>X</button>
<Package tracking="a" />
</div>
<div className="package" key={1}>
<button>X</button>
<Package tracking="b" />
</div>
<div className="package" key={2}>
<button>X</button>
<Package tracking="c" />
</div>
</div>
</div>
Now when we click "X" on the second item in the list, "b" is passed to removePackage(), which sets state.packages to ['a', 'c']. That triggers our render, which produces the following (Virtual) DOM:
<div>
<div className="title">Packages</div>
<div className="packages">
<div className="package" key={0}>
<button>X</button>
<Package tracking="a" />
</div>
<div className="package" key={1}>
<button>X</button>
<Package tracking="c" />
</div>
</div>
</div>
Since the VDOM library only knows about the new structure you give it on each render (not how to change from the old structure to the new), what the keys have done is basically tell it that items 0 and 1 remained in-place - we know this is incorrect, because we wanted the item at index 1 to be removed.
Remember: key takes precedence over the default child diff reordering semantics. In this example, because key is always just the 0-based array index, the last item (key=2) just gets dropped off because it's the one missing from the subsequent render.
The Fix
So, to fix your example - you should use something that identifies the item rather than its offset as your key. This can be the item itself (any value is acceptable as a key), or an .id property (preferred because it avoids scattering object references around which can prevent GC):
let packages = this.state.packages.map((tracking, i) => {
return (
// ↙️ a better key fixes it :)
<div className="package" key={tracking}>
<button onClick={this.removePackage.bind(this, tracking)}>X</button>
<Package tracking={tracking} />
</div>
);
});
Whew, that was a lot more long-winded that I had intended it to be.
TL,DR: never use an array index (iteration index) as key. At best it's mimicking the default behavior (top-down child reordering), but more often it just pushes all diffing onto the last child.
edit: #tommy recommended this excellent link to the eslint-plugin-react docs, which does a better job explaining it than I did above.
I'm seeing this. It's not a mystery what it is complaining about:
Warning: validateDOMnesting(...): <div> cannot appear as a descendant of <p>. See ... SomeComponent > p > ... > SomeOtherComponent > ReactTooltip > div.
I'm the author of SomeComponent and SomeOtherComponent. But the latter is using an external dependency (ReactTooltip from react-tooltip). It's probably not essential that this is an external dependency, but it lets me try the argument here that it is "some code that's out of my control".
How worried should I be about this warning, given that the nested component is working just fine (seemingly)? And how would I go about changing this anyway (provided I don't want to re-implement an external dependency)? Is there maybe a better design that I'm yet unaware of?
For completeness sake, here's the implementation of SomeOtherComponent. It just renders this.props.value, and when hovered: a tooltip that says "Some tooltip message":
class SomeOtherComponent extends React.Component {
constructor(props) {
super(props)
}
render() {
const {value, ...rest} = this.props;
return <span className="some-other-component">
<a href="#" data-tip="Some tooltip message" {...rest}>{value}</a>
<ReactTooltip />
</span>
}
}
Thank you.
If this error occurs while using Material UI <Typography> https://material-ui.com/api/typography/, then you can easily change the <p> to a <span> by changing the value of the component attribute of the <Typography> element :
<Typography component={'span'} variant={'body2'}>
According to the typography docs:
component : The component used for the root node. Either a string to use a DOM element or a component. By default, it maps the variant to a good default headline component.
So Typography is picking <p> as a sensible default, which you can change. May come with side effects ... worked for me.
Based on the warning message, the component ReactTooltip renders an HTML that might look like this:
<p>
<div>...</div>
</p>
According to this document, a <p></p> tag can only contain inline elements. That means putting a <div></div> tag inside it should be improper, since the div tag is a block element. Improper nesting might cause glitches like rendering extra tags, which can affect your javascript and css.
If you want to get rid of this warning, you might want to customize the ReactTooltip component, or wait for the creator to fix this warning.
If you're looking for where this is happening, in console you can use: document.querySelectorAll(" p * div ")
I got this warning by using Material UI components, then I test the component="div" as prop to the below code and everything became correct:
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
<Typography component="span">
<Grid component="span">
Lorem Ipsum
</Grid>
</Typography>
Actually, this warning happens because in the Material UI the default HTML tag of Grid component is div tag and the default Typography HTML tag is p tag, So now the warning happens,
Warning: validateDOMnesting(...): <div> cannot appear as a descendant of <p>
Details (and some HTML theory regarding the warning) : The <div> cannot appear as a descendant of <p> message is shown due to the fact that the permitted content of a <p> tag is according to the standards set to the Phrasing Context which does not include <div> tags. See the links for more details.
Your component might be rendered inside another component (such as a <Typography> ... </Typography>). Therefore, it will load your component inside a <p> .. </p> which is not allowed.
Fix:
Remove <Typography>...</Typography> because this is only used for plain text inside a <p>...</p> or any other text element such as headings.
This is a constraint of browsers. You should use div or article or something like that in the render method of App because that way you can put whatever you like inside it. Paragraph tags are limited to only containing a limited set of tags (mostly tags for formatting text. You cannot have a div inside a paragraph
<p><div></div></p>
is not valid HTML. Per the tag omission rules listed in the spec, the <p> tag is automatically closed by the <div> tag, which leaves the </p> tag without a matching <p>. The browser is well within its rights to attempt to correct it by adding an open <p> tag after the <div>:
<p></p><div></div><p></p>
You can't put a <div> inside a <p> and get consistent results from various browsers. Provide the browsers with valid HTML and they will behave better.
You can put <div> inside a <div> though so if you replace your <p> with <div class="p"> and style it appropriately, you can get what you want.
Details (and some HTML theory regarding the warning) : The <div> cannot appear as a descendant of <p> message is shown due to the fact that the permitted content of a <p> tag is according to the standards set to the Phrasing Context which does not include <div> tags. See the links for more details.
The warning appears only because the demo code has:
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box p={3}> // <==NOTE P TAG HERE
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
Changing it like this takes care of it:
function TabPanel(props) {
const {children, value, index, classes, ...other} = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Container>
<Box> // <== P TAG REMOVED
{children}
</Box>
</Container>
)}
</div>
);
}
I got this from using a react-boostrap <Card.Text> section of a component in React. None of my components were in p tags. <Card.Text> by default shows up as a in the HTML tree once rendered and basically having a custom React component returning its jsx elements within a <div> it causes a <p><div>...</div></p> which is a bad HTML. So to fix this issue if one is using <Card.Text> you can basically use the 'as' attribute as the following
<Card.Text as='div'> and this will resolve the warning because it allow a tree such as <div><div></div></div>
With Material UI !!!!!
I've spent an embarassing amount of time but thanks to this answer --> here by Alex C which gave this very simple yet smart solution
document.querySelectorAll(" p * div ")
So at my surprise , was the Material UI DialogContentText component DialogContentText API that caused the issue
So just use component={'span'} to fix the problem (it won't effect the style)
<DialogContentText id="alert-dialog-inputs" component={'span'}/>
I had a similar issue and wrapped the component in "div" instead of "p" and the error went away.
I got this from using a custom component inside a <Card.Text> section of a <Card> component in React. None of my components were in p tags
If you are using ReactTooltip, to make the warning disappear, you can now add a wrapper prop with a value of span, like this:
<ReactTooltip wrapper="span" />
Since the span is an inline element, it should no longer complain.
There is a problem in App.js during its rendering.
Don't use <div>...</div> to render just use <>...</>.
I had same issue with react-bootstrap and with Alex C's solution i found that i use <div> in <Card.Text> which is kinda <p>
<Card.Text>
<div>{name}</div>
<div>{address}</div>
<div>{size}</div>
</Card.Text>
I got this error when using Chakra UI in React when doing inline styling for some text. The correct way to do the inline styling was using the span element, as others also said for other styling frameworks. The correct code:
<Text as="span" fontWeight="bold">
Bold text
<Text display="inline" fontWeight="normal">normal inline text</Text>
</Text>
If you couldn't solve problem use bottom method
You have this problem because, you Open Grid tag or div in text tag and It can be false.
For example I write part of the code That have problem.look at it:
<Typography>
text
<Grid>
</Grid>
</Typography>
Be careful I open Grid tag in Typography tag. So It is false if we Open Grid tag in typography tag.
As far as my knowledge know when We open in typograghy tag, We are Deviating from the MUI standard.
So for best way you can us this Code:
<Grid>
<Typography>
text
</Typography>
</Grid>
If you used this code, you would not Any problem.
As result if you have problem again
You have to check components and
See in which component you did not Choose the correct mode.
You should check:
Span typography h1 h2 h3 h4 h5 h6 p
I hope I helped you .
I resolved that issue by removing my tag that I used to wrap around my 2 textfields that were causing that same error log.
You can not descendant of ;
Error: <p><div>aaaa</div></p>
Solution <div><p>aaaa</p></div>
(Extra)
Problem can be <p> elements inside <p> element in other similar errors.
Error:
<p>
<p>
</p>
</p>