React - Each child in array .. unique "key" prop warning - javascript

I appear to have a decent understanding of this principal, which allows me to get by, until now. I am applying a key prop to all children of all iterators, and yet I'm still getting this warning.
A FacilitiesContainer is rendering a FacilitiesComponent, which in turn renders a list of Facilities, which renders a list of Courses. A Course does not use an iterator. However, the FacilitiesContainer is passing the FacilitiesComponent through a HOC, which is returning the final component. There's nothing in the HOC that modifies the passed components, so I'm not sure if this is a problem.
// The render method of FacilitiesContainer
render = () => {
let FacilitiesWithSearch = SearchHOC(
BasicSearch,
FacilitiesComponent,
{data: this.state.facilities }
);
return <FacilitiesWithSearch />;
}
class FacilitiesComponent extends Component {
renderFacilities = () => (
this.props.data.map((facilityData, index) =>
<Facility key={index} data={facilityData} />
)
)
render = () => (
<Grid>
<Row>
<Col xs={12} sm={8} smOffset={2} md={8} mdOffset={1}>
{
this.props.data.length > 0
? this.renderFacilities()
: <div>No results</div>
}
</Col>
</Row>
</Grid>
)
}
const Facility = ({ data }) => (
<Panel>
<Panel.Heading>
<Panel.Title>{data.Name}</Panel.Title>
</Panel.Heading>
<Panel.Body>
<Grid>
<Row>
<p><b>Address:</b><br />
{data.Street}<br />
{data.City}, {data.State} {data.Zip}
</p>
<p><b>Phone:</b> {data.Phone}</p>
{
data.Courses.map((courseData, index) =>
<p><Course key={index} data={courseData} /></p>)
}
</Row>
</Grid>
</Panel.Body>
</Panel>
);

You indeed didn't provide keys to p elements here:
{
data.Courses.map((courseData, index) =>
<p><Course key={index} data={courseData} /></p>)
}
Should be
{
data.Courses.map((courseData, index) =>
<p key={index}><Course data={courseData} /></p>)
}

Try to append a string to the index before assigning it to the key. That's because you are only using index (0,1,2...) both for your list of facilities and list of courses, so there will be duplicated indexes in the final rendered component. If you do as below you ensure that each index is unique:
<Facility key={`facility_${index}`} data={facilityData} />
and
<Course key={`course_${index}`} data={courseData} />

Related

Is there a React higher order component to replace someData.map(functionReturningComponent)?

I find code like this to be hard to read:
<Row>
<Column>
{someData.map((value, idx) => (
<Component key={idx}>
<ChildComponent value={value.foo}/>
<ChildComponentTwo value={value.bar}/>
</Component>
)}
</Column>
</Row>
Is there a HOC to replace Array.prototype.map? Say, List that takes a component and generates N components given an array of N items of data mappable to the props of the passed-in component?
declare const data: {value:string}[]
const MyComponentList = List(MyComponent)
<Row>
<Column>
<MyComponentList data={data}/>
</Column>
</Row>
and then Component = ({foo, bar}) => (<>
<ChildComponent value={foo}/>
<ChildComponentTwo value={bar}/>
</>)
Now I don't need map anywhere except I guess in the definition of List. The JSX is much easier to read. But when I search things like "HOC react replace array map JSX" I don't find anything. I suppose List should also inject key={idx} into the "child" components where idx comes from dataArray.map(dataToGoToChild, idx) => <ChildComponent key={idx} {...dataToGoToChild}/>

React recursion - can't get correct value

I'm having Array of Objects named items. Each Object looks like this
interface IItem {
id: string;
title: string;
items: IItem[] | null
}
So I'm doing recursion, as in
parent component:
{items?.map((folder, index) => (
<Item
onClick={(event) => {
event.stopPropagation();
event.preventDefault();
console.log(
event.target.innerHTML,
folder.id,
folder.title
);
}}
key={folder.id}
data={folder}
/>
))}
WhileItem component looks like this:
const nestedItems =
data.items
? data.items.map((item, index) => (
<Item
onClick={onClick}
key={item.id}
data={item}
/>
))
: null;
return (
<div>
<button onClick={onClick}>
<Typography>
{data.title} {data.id}
</Typography>
</button>
<div>{nestedItems}</div>
</div>
Html output and whats rendered in browser looks correct,
but when I try to get {id}, or {title} from item in parent component
the output of any click which has parent doesn't log it's own id, but it's parent id.
The strange thing is that console.log from above on any "not first level items" looks like this:
event.target.innerHTML - correct value
folder.id, folder.title - has parent ? parent id value : it's own id value
So I'm not really sure what's happening and where is the catch :/
Thanks in advance!
You only define onClick for each of the root-level Items and then within the Item component you just pass the onClick prop to the child. Thus every child has the same onClick function as its parent.
To fix this you can change onClick to be more generic by accepting data (the id and/or other information) rather than having data curried into it. This way the onClick function passed to the Item works for any item, but the onClick function passed to the button within each Item is unique to that item.
For example:
Parent component:
{items?.map((folder, index) => (
<Item
onClick={(event, clickedFolder) => {
event.stopPropagation();
event.preventDefault();
console.log(
event.target.innerHTML,
clickedFolder.id,
clickedFolder.title
);
}}
key={folder.id}
data={folder}
/>
))}
Item component:
const nestedItems =
data.items
? data.items.map((item, index) => (
<Item
onClick={onClick}
key={item.id}
data={item}
/>
))
: null;
return (
<div>
<button onClick={event => onClick(event, data)}>
<Typography>
{data.title} {data.id}
</Typography>
</button>
<div>{nestedItems}</div>
</div>
)

How can I access props from inside a component

I'm trying to access "props" from a component for which I'm passing an object. I'm a bit lost with JS here ; basically what I'm trying to do is to build a Master/Detail view (so show/hide 2 different components based on user clicks on a table).
How can I access "props" from the object rowEvent once a user clicks on a table row ?
const rowEvents = {
onClick: (e, row, rowIndex) => {
console.log(row.itemId);
//this.currentItemId= row.itemId; //////////// THIS DOESNT WORK...
}
};
const TableWithSearch = (props) => {
const { SearchBar } = Search;
const { ExportCSVButton } = CSVExport;
return (
<Card>
<CardBody>
<h4 className="header-title">Search and Export</h4>
<p className="text-muted font-14 mb-4">A Table</p>
<ToolkitProvider
bootstrap4
keyField="itemId"
data={props.data}
columns={columns}
search
exportCSV={{ onlyExportFiltered: true, exportAll: false }}>
{props => (
<React.Fragment>
<Row>
<Col>
<SearchBar {...props.searchProps} />
</Col>
<Col className="text-right">
<ExportCSVButton {...props.csvProps} className="btn btn-primary">
Export CSV
</ExportCSVButton>
</Col>
</Row>
<BootstrapTable
{...props.baseProps}
bordered={false}
rowEvents={ rowEvents }
defaultSorted={defaultSorted}
pagination={paginationFactory({ sizePerPage: 5 })}
wrapperClasses="table-responsive"
/>
</React.Fragment>
)}
</ToolkitProvider>
</CardBody>
</Card>
);
};
And the component looks like this :
render() {
let show;
if (this.props.currentItemId === null){
show = (<TableWithSearch data={this.props.data} />)
}
else {
show = (<DisplayItem />)
}
return (
<React.Fragment>
<Row>
<Col>
{ show }
</Col>
</Row>
</React.Fragment>
)
}
}
Your issue is a bit complex because you seem to be needing to update the prop currentItemId from parent's parent.
You can solve your issue by doing the following:
Move the declaration of rowEvents objects in side TableWithSearch functional component.
In TableWithSearch component, receive a callback say updateCurrentItemId from parent which updates the currentItemId in the parent
In parent component, the currentItemId is being passed from parent(again). So maintain a state for it.
TableWithSearch Component
const TableWithSearch = (props) => {
const { SearchBar } = Search;
const { ExportCSVButton } = CSVExport;
const {updateCurrentItemId} = props; //<--------- receive the prop callback from parent
const rowEvents = {
onClick: (e, row, rowIndex) => {
console.log(row.itemId);
updateCurrentItemId(row.itemId) // <--------- use a callback which updates the currentItemId in the parent
//this.currentItemId= row.itemId; //////////// THIS DOESNT WORK...
},
};
return (
<Card>
<CardBody>
<h4 className="header-title">Search and Export</h4>
<p className="text-muted font-14 mb-4">A Table</p>
<ToolkitProvider
bootstrap4
keyField="itemId"
data={props.data}
columns={columns}
search
exportCSV={{ onlyExportFiltered: true, exportAll: false }}
>
{(props) => (
<React.Fragment>
<Row>
<Col>
<SearchBar {...props.searchProps} />
</Col>
<Col className="text-right">
<ExportCSVButton
{...props.csvProps}
className="btn btn-primary"
>
Export CSV
</ExportCSVButton>
</Col>
</Row>
<BootstrapTable
{...props.baseProps}
bordered={false}
rowEvents={rowEvents}
defaultSorted={defaultSorted}
pagination={paginationFactory({ sizePerPage: 5 })}
wrapperClasses="table-responsive"
/>
</React.Fragment>
)}
</ToolkitProvider>
</CardBody>
</Card>
);
};
Parent Component
class ParentComp extends React.Component {
state = {
curItemId: this.props.currentItemId
}
updateCurrentItemId = (udpatedCurId) => {
this.setState({
curItemId: udpatedCurId
})
}
render() {
let show;
// if (this.props.currentItemId === null){
if (this.state.curItemId === null){
show = (<TableWithSearch data={this.props.data} updateCurrentItemId={this.updateCurrentItemId}/>)
}
else {
show = (<DisplayItem />)
}
return (
<React.Fragment>
<Row>
<Col>
{ show }
</Col>
</Row>
</React.Fragment>
)
}
}
}
this.props should give you access for class components
In addition you should create a bind to the click function so it can correctly resolve this, in the constuctor of the rowEvent

Trying to iterate over a JSON object in React

I am trying to display a toggle checkbox for every JSON value I have. This is what the json object looks like for element
{
"sourceIP": {
"Primary": ["237.100.100.3", "238.0.4.8"],
"Secondary": ["237.0.1.178", "237.1.1.91"]
},
"multicastIP": {
"Primary": ["10.208.153.129", "238.0.4.8"],
"Secondary": ["10.208.133.58", "238.0.4.8"]
}
}
So I would like to iterate through element instead of hardcoding it like this:
const CustomToggle = ({ element}) => (
<List divided verticalAlign='middle'>
<Segment textAlign='left' style={{paddingLeft: '7.5em'}}>
<Checkbox
toggle
label={(JSON.stringify(element.sourceIP.Primary[0]))}
value={(JSON.stringify(element.sourceIP.Primary[0]))}
/>
<Checkbox
toggle
label={(JSON.stringify(element.sourceIP.Primary[1]))}
value={(JSON.stringify(element.sourceIP.Primary[1]))}
/>
<Checkbox
toggle
label={(JSON.stringify(element.sourceIP.Secondary[1]))}
value={(JSON.stringify(element.sourceIP.Secondary[1]))}
/>
</Segment>
</List>
);
I have been trying to forEach and .map but none of this seems to work. I don't want to have to hardcode every json value in element. How can I just iterate through my element JSON object?
Object.keys(element).reduce((a,c) => a.concat(...Object.values(element[c])),[])
this transformed data gives array of ip adtresses. So, now you can iterate it
const CustomToggle = ({ element}) => {
const elements = Object.keys(element).reduce((a,c) => a.concat(...Object.values(element[c])),[]);
return (
<List divided verticalAlign='middle'>
<Segment textAlign='left' style={{paddingLeft: '7.5em'}}>
{elements.map(ip => <Checkbox toggle label={ip} value={ip} />}
</Segment>
</List>
)
}
Here try this, let me know if it helps :)
const CustomToggle = ({ element }) => (
<List divided verticalAlign="middle">
<Segment textAlign="left" style={{ paddingLeft: "7.5em" }}>
{Object.keys(element).map(key => {
return [...element[key].Primary, ...element[key].Secondary].map(x => {
<Checkbox toggle label={x} value={x} />;
});
})}
</Segment>
</List>
);
You can for example do something like the snipped below:
const List = ({ divided, rest }) => <div {...rest} />;
const Checkbox = ({ toggle, ...props }) => (
<div>
<input type="checkbox" {...props} />
<label>{props.label}</label>
</div>
);
const Segment = (props) => <div {...props} />;
const CustomToggle = ({ element }) => (
<div>
{element.map((ip) => (
<Checkbox toggle label={ip} value={ip} />
))}
</div>
);
function App() {
let result = `{
"sourceIP": {
"Primary": ["237.100.100.3", "238.0.4.8"],
"Secondary": ["237.0.1.178", "237.1.1.91"]
},
"multicastIP": {
"Primary": ["10.208.153.129", "238.0.4.8"],
"Secondary": ["10.208.133.58", "238.0.4.8"]
}
}`;
let data = JSON.parse(result);
let ips = [];
Object.keys(data).map((source) => {
Object.keys(data[source]).map((type) => {
ips.push(...data[source][type]);
});
});
return <CustomToggle element={ips} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So, you'll parse the data, then iterate over it to get the IPs and then pass those as a prop to the CustomToggle. The code above is just an example to get you going as I don't know what your List, Checkbox and Segment components look like.
The reasoning is that CustomToggle should not know or care what the data should look like, it just knows how to render a custom toggle based on some string that it receives, in this case the ip. You should however make sure that each Checkbox has a unique key.

OnRowSelect Set State in JS React - Allowing Filtered Route Selection

I am attempting to pass an argument, setting 'fooId' to a temporary state, allowing to me set the default filter for the user to all 'fooId's within a react-data-grid when they clicked on the row(a cell value used for filtering). I thought I could make a function to set the state and pass that through the filter. The backend and supporting Sagas, Reducer, ect are built to support passing data. I just need to figure out this change in default filtering to set the filter to slice 'filteredData'. Hope it makes sense...Please see applicable code below, thanks:
function FooBar({fooBar, filteredData, fooBarSearch, toDate, fromDate, searchText, foobar}) {
let filterData = fooBar.slice();
function onRowClickFoo(event) { let fooBarId = set.state.fooId return
selectRoute('bar/whee/' + fooId); }
return (
<div>
<Grid fluid>
<Row className={styles.fooPage}>
<Col xs={12} md={3}>
<AutoComplete
floatingLabelText="Search Foo"
filter={AutoComplete.caseInsensitiveFilter}
openOnFocus={true}
dataSource={foobar}
searchText={searchText}
dataSourceConfig={{text: 'fooId', value: 'fooId'}}
onUpdateInput={searchOnUpdateHandler}
onNewRequest={searchOnNewRequest}
maxSearchResults={8}
/>
</Col>
<Col xs={12} md={3}>
<DatePicker
onChange={fromDateOnChangeHandler}
floatingLabelText="Filter Start Date"
autoOk={true}
value={fromDate}
mode="landscape"
firstDayOfWeek={0}
shouldDisableDate={disableStartDays}
/>
</Col>
<Col xs={12} md={3}>
<DatePicker
onChange={toDateOnChangeHandler}
floatingLabelText="Filter End Date"
autoOk={true}
value={toDate}
mode="landscape"
firstDayOfWeek={0}
shouldDisableDate={disableEndDays}
/>
</Col>
<Col xs={12} md={3} className={styles.resetButton}>
<RaisedButton
label="Reset"
secondary={true}
onTouchTap={handleResetFilter}
/>
</Col>
</Row>
<Row>
<Col xs={12}>
<fooBarGrid className={styles.fooBarGrid}
columnHeaders={columnHeaders}
rows={filteredData}
enableRowSelect={true}
onRowSelect={onRowClickFoo}
/>
It's kind of hard to understand what you're going for, but ... I think I understand.
If you want to use component-local state, you'll have to either use a Component class, or use an HOC to store the state for you (the recompose package has withState, which I find very very useful).
example; vanilla React
class Foo extends React.Component {
state = {
filter: null
}
render() {
const { data } = this.props
const { filter } = this.state
const mData = data.filter((dataRow) => dataRow.id === filter)
return (
/* use mData */
)
}
}
using recompose
const Foo = ({ data, filter, setFilter }) => {
const mData = data.filter((dataRow) => dataRow.id === filter)
const onRowClick = (event) => setFilter(/* whatever you want to filter to */)
return (
/* use mData */
)
}
export default withState("filter", "setFilter", null)(Foo)

Categories