I'm writing a small Toolbar component in React. Here's how it should be used:
<Toolbar>
<ToolbarButtonSearch />
<ToolbarButtonFold />
</Toolbar>
or
<Toolbar>
<ToolbarButtonSearch />
</Toolbar>
or
<Toolbar>
<ToolbarButtonFold />
</Toolbar>
I'd like to be able to specify that the only children Toolbar accepts are one of ToolbarButtonSearch, one of ToolbarButtonFold, or one of each.
Here's what I have now (not including imports):
export default class Toolbar extends React.Component {
static get propTypes() {
const acceptedChildren =
React.PropTypes.oneOfType([
React.PropTypes.instanceOf(ToolbarButtonFold),
React.PropTypes.instanceOf(ToolbarButtonSearch)
]);
return {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(acceptedChildren),
acceptedChildren
]).isRequired
};
}
render() {
return (
<div className="toolbar">
{this.props.children}
</div>
);
}
}
Here's how I'm using it:
<Toolbar>
<ToolbarButtonFold />
</Toolbar>
This results in the following error:
Warning: Failed prop type: Invalid prop 'children' supplied to 'Toolbar'.
I'm not sure what I'm doing wrong. Any insight would be very helpful. Thanks!
Did you tried to do:
customProp: function (props, propName, componentName) {
props.children.forEach(child => {
if (!child instanceof ToolbarButtonSearch && !child instanceof ToolbarButtonFold) {
return new Error(
'Invalid children supplied to' +
' `' + componentName + '`. Validation failed.'
)
}
})
}
Instead of passing components (since they have to be imported into Toolbar, anyway), you could just pass props defining if the specific component should be displayed.
const Toolbar = ({showButtonSearch = false, showButtonFold = false, buttonSearchProps = {}, buttonFoldProps = {}}) => {
return (
<div className='Toolbar'>
{showButtonSearch ? <ButtonSearch {...buttonSearchProps} /> : null}
{showButtonFold ? <ButtonFold {...buttonFoldProps} /> : null}
</div>
)
}
This method would also allow you to pass props for the children as well as any further functionality you need before displaying the children (such as ordering the children a specific way).
Related
I can see item and item id as it loops to render on the screen but i don't see the value of id when i click on any of the Tile where Tile is a div and react styled component.
class CategoryOffers extends React.Component {
passidtopointscreen =(id)=>{
console.log("id is", id);
localStorage.setItem('points_id',id);
this.props.history.push('/marketplacepoints')
debugger
}
render() {
debugger
return (
<Wrapper>
{this.props &&
this.props.cards_data &&
this.props.cards_data.map(item => {
return (
<Tile onClick={(item)=>this.passidtopointscreen(item.id)}>
<ImageWrapper>
<Image src={item.logo} height={'24px'} width={'73px'} />
</ImageWrapper>
<CardString>{item.offer_summary}</CardString>
</Tile>
)
})}
</Wrapper>
)
}
}
onClick={()=>this.passidtopointscreen(item.id)}
while adding item there you create new instance for this keyword for no reason
By having the same argument-name as your already decleared argument (item), you overwrite the outer argument. There should be no reason for you here to use the event-argument, if I have understood your question correctly.
I would also suggest avoiding localstorage and instead make use of the state.
I made the component into functional one here:
import React from "react";
const CategoryOffers = ({history,cards_data}) => {
const passidtopointscreen =(id)=>{
localStorage.setItem('points_id',id);
history.push('/marketplacepoints')
}
return (
<Wrapper>
{
cards_data?.map(item => {
return (
<Tile onClick={(event)=>passidtopointscreen(item.id)}>
<ImageWrapper>
<Image src={item.logo} height={'24px'} width={'73px'} />
</ImageWrapper>
<CardString>{item.offer_summary}</CardString>
</Tile>
)
})}
</Wrapper>
)
}
}
I'm using a HOC component to bind an action to many different types of element, including SVG cells, which, when an onClick is bound normally, it works, but when I use my HOC it returns un-intended results.
Minimally reproducible example: https://codesandbox.io/s/ecstatic-keldysh-3viw0
The HOC component:
export const withReport = Component => ({ children, ...props }) => {
console.log(Component); //this only prints for ListItem elements for some reason
const { dispatch } = useContext(DashboardContext);
const handleClick = () => {
console.log('clicked!'); //even this wont work on some.
const { report } = props;
if (typeof report === "undefined") return false;
dispatch({ type: SET_ACTIVE_REPORT, activeReport: report });
dispatch({ type: TOGGLE_REPORT });
};
return (
<Component onClick={handleClick} {...props}>
{children}
</Component>
);
};
Usage working:
const ListItemWIthReport = withReport(ListItem); //list item from react-mui
{items.map((item, key) => (
<ListItemWithReport report={item.report} key={key} button>
{/* listitem children*/}
</ListItemWithReport>
))}
Usage not working:
const BarWithReport = withReport(Bar); //Bar from recharts
{bars.map((bar, index) => (
<BarWithReport
report={bar.report}
key={index}
dataKey={bar.name}
fill={bar.fill}
/>
))}
The ListItem works 100% as anticipated, however, the bars will not render inside of the BarChart. Similarly, with a PieChart the Cells will actually render, with the correct sizes according to their values, however, props like "fill" do not appear to pass down.
Am I using the HOC incorrectly? I don't see an option other than HOC for the inside of Charts as many types of elements will be considered invalid HTML?
You might be dealing with components that have important static properties that need to be hoisted into the wrapped component or need to have ref forwarding implemented in order for their parent components to handle them. Getting these pieces in place is important, especially when wrapping components where you don't know their internals. That Bar component, for example, does have some static properties. Your HOC is making those disappear.
Here's how you can hoist these static members:
import hoistNonReactStatic from 'hoist-non-react-statics';
export const withReport = Component => {
const EnhancedComponent = props => {
const { dispatch } = useContext(DashboardContext);
const handleClick = () => {
const { report } = props;
if (typeof report === "undefined") return false;
dispatch({ type: SET_ACTIVE_REPORT, activeReport: report });
dispatch({ type: TOGGLE_REPORT });
};
return (
<Component onClick={handleClick} {...props}/>
);
};
hoistNonReactStatic(EnhancedComponent, Component);
return EnhancedComponent;
};
Docs on hoisting statics and ref forwarding can be found in this handy guide to HOCs.
There may be some libraries that can take care of all these details for you. One, addhoc, works like this:
import addHOC from 'addhoc';
export const withReport = addHOC(render => {
const { dispatch } = useContext(DashboardContext);
const handleClick = () => {
const { report } = props;
if (typeof report === "undefined") return false;
dispatch({ type: SET_ACTIVE_REPORT, activeReport: report });
dispatch({ type: TOGGLE_REPORT });
};
return render({ onClick: handleClick });
});
Of course, if the parent component is checking child components by type explicitly, then you won't be able to use HOCs at all. In fact, it looks like recharts has that issue. Here you can see the chart is defined in terms of child components which are then searched for explicitly by type.
I think your HOC is invalid, because not every wrapper-Component (e.g. HTML element) is basically clickable. Maybe this snipped can clarify what I am trying to say:
const withReport = Component => (props) => {
const handleClick = () => console.log('whatever')
// Careful - your component might not support onClick by default
return <Component onClick={handleClick} {...props} />
// vs.
return <div onClick={handleClick} style={{backgroundColor: 'green'}}>
<Component {...props} />
{props.children}
</div>
}
// Your import from wherever you want
class SomeClass extends React.Component {
render() {
return <span onClick={this.props.onClick}>{this.props.children}</span>
// vs.
return <span style={{backgroundColor: 'red'}}>
{
// Careful - your imported component might not support children by default
this.props.children
}
</span>
}
}
const ReportedListItem = withReport(SomeClass)
ReactDOM.render(<ReportedListItem>
<h2>child</h2>
</ReportedListItem>, mountNode)
You can have the uppers or the unders (separated by vs.) but not crossed. The HOC using the second return (controlled wrapper-Component) is sure more save.
I've used 4 methods successfully to wrap Recharts components.
First Method
Wrap the component in a HOC and use Object.Assign with some overloads. This breaks some animation and difficult to use an active Dot on lines. Recharts grabs some props from components before rendering them. So if the prop isn't passed into the HOC, then it won't render properly.
...
function LineWrapper({
dataOverload,
data,
children,
strokeWidth,
strokeWidthOverload,
isAnimationActive,
dot,
dotOverload,
activeDot,
activeDotOverload,
...rest
}: PropsWithChildren<Props>) {
const defaultDotStroke = 12;
return (
<Line
aria-label="chart-line"
isAnimationActive={false}
strokeWidth={strokeWidthOverload ?? 2}
data={dataOverload?.chartData ?? data}
dot={dotOverload ?? { strokeWidth: defaultDotStroke }}
activeDot={activeDotOverload ?? { strokeWidth: defaultDotStroke + 2 }}
{...rest}
>
{children}
</Line>
);
}
export default renderChartWrapper(Line, LineWrapper, {
activeDot: <Dot r={14} />,
});
const renderChartWrapper = <P extends BP, BP = {}>(
component: React.ComponentType<BP>,
wrapperFC: React.FC<P>,
defaultProps?: Partial<P>
): React.FC<P> => {
Object.assign(wrapperFC, component);
if (defaultProps) {
wrapperFC.defaultProps = wrapperFC.defaultProps ?? {};
Object.assign(wrapperFC.defaultProps, defaultProps);
}
return wrapperFC;
};
Second Method
Use default props to assign values. Any props passed into the HOC will be overridden.
import { XAxisProps } from 'recharts';
import { createStyles } from '#material-ui/core';
import { themeExtensions } from '../../../assets/theme';
const useStyles = createStyles({
tickStyle: {
...themeExtensions.font.graphAxis,
},
});
type Props = XAxisProps;
// There is no actual implementation of XAxis. Recharts render function grabs the props only.
function XAxisWrapper(props: Props) {
return null;
}
XAxisWrapper.displayName = 'XAxis';
XAxisWrapper.defaultProps = {
allowDecimals: true,
hide: false,
orientation: 'bottom',
width: 0,
height: 30,
mirror: false,
xAxisId: 0,
type: 'category',
domain: [0, 'auto'],
padding: { left: 0, right: 0 },
allowDataOverflow: false,
scale: 'auto',
reversed: false,
allowDuplicatedCategory: false,
tick: { style: useStyles.tickStyle },
tickCount: 5,
tickLine: false,
dataKey: 'key',
};
export default XAxisWrapper;
Third Method
I didn't like this so I've worked around it, but you can extend the class.
export default class LineWrapper extends Line {
render(){
return (<Line {...this.props} />
}
}
Fourth Method
I don't have a quick example of this, but I always render the shape or children and provide functions to help. For example, for bar cells I use this:
export default function renderBarCellPattern(cellOptions: CellRenderOptions) {
const { data, fill, match, pattern } = cellOptions;
const id = _uniqueId();
const cells = data.map((d) =>
match(d) ? (
<Cell
key={`cell-${id}`}
strokeWidth={4}
stroke={fill}
fill={`url(#bar-mask-pattern-${id})`}
/>
) : (
<Cell key={`cell-${id}`} strokeWidth={2} fill={fill} />
)
);
return !pattern
? cells
: cells.concat(
<CloneElement<MaskProps>
key={`pattern-${id}`}
element={pattern}
id={`bar-mask-pattern-${id}`}
fill={fill}
/>
);
}
// and
<Bar {...requiredProps}>
{renderBarCellPattern(...cell details)}
</Bar>
CloneElement is just a personal wrapper for Reacts cloneElement().
I have a dropdown with a few options using semantic-ui-react. I want to be able to show the user a brief description on the choice they made after selection from the dropdown. Semantic also has a <Popup/> module that I've been trying to use along with <Dropdown/> to make this work.
I'm looking through the prop list of the dropdown module and don't see anything in particular that fits my case. I've tried using dropdown inside of popup, but with no luck.
Sandbox with the example: https://codesandbox.io/s/5zo52qyrxk
import React from "react";
import ReactDOM from "react-dom";
import { Dropdown, Popup, Input } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import "./styles.css";
const offsetOptions = [
{
text: "fromEarliest",
value: "fromEarliest"
},
{
text: "fromLatest",
value: "fromLatest"
}
];
const DropdownExample = () => (
<Dropdown
placeholder="Select offset position"
clearable
fluid
selection
options={offsetOptions}
header=" Something about offset "
/>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
offset: ""
};
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div>
<fieldset>
<h1> Implement Popup on this Dropdown - semantic ui </h1>
<DropdownExample />
</fieldset>
</div>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If you're trying to show a popup on each dropdown option, then you can use the subcomponent API to create the dropdown options rather than using the options prop.
<Dropdown text='File'>
<Dropdown.Menu>
<Popup trigger={<Dropdown.Item text='Close' description="Close" value="Close" />} content="Hello there" />
<Popup trigger={<Dropdown.Item text='Open' description='Open' value="Open" />} content="Open me"/>
{/* more options would go here... */}
</Dropdown.Menu>
</Dropdown>
There is a warning on the Semantic-UI-React site that states
Dropdown state is not fully managed when using the subcomponent API. The shorthand props API fully manages state but needs to be extended to support the markup shown here.
So I would take this suggestion with a grain of salt, but it does seem to accomplish what you're looking for.
Here is a solution that allows you to use a <Popup/> inside your <Dropdown.Item/> while still using the shorthand props like options, value and onChange:
// Main component
const DropdownWithPopups = ({ className, options, value, onChange, ...props }) => {
return (
<Dropdown
className='dropdown-with-popups'
selection
options={options}
value={value}
onChange={onChange}
{...props}
/>
);
};
// Popup that will be inside the default <Item/>
const ItemPopup = ({ text, popupContent }) => {
return (
<Popup
basic
position='left center'
hoverable
pinned
trigger={(
<div className='item-popup-trigger'>
{text}
</div>
)}
content={(
<div className='item-popup-content'>
{popupContent}
</div>
)}
popperModifiers={{
preventOverflow: {
boundariesElement: 'window',
},
}}
/>
);
};
// What your options should look like
const getOptions = (optionsData) => {
return _.map(optionsData, (option) => {
return {
value: option.value,
// `text` so the selected option is displayed
text: option.text,
// `children`is incompatible with `text` prop, so Semantic UI React will throw a warning,
// but it works as expected
children: (
<ItemPopup
text={option.text}
// whatever you need to render inside your Popup
popupContent={...}
/>
),
};
});
};
However I only tested this on an old version of semantic-ui-react (v0.88.2)
Actually there is another way to render items of dropdown with popups with using additional parameter content during data mapping
For example you have some received data to place in dropdown, then map of options would be:
const options = data.map(elem => ({
key: elem.id,
text: elem.name,
value: elem.name,
content: (
<Popup
content={ elem.example }
trigger={ <div>{ elem.name }</div> } />
),
})
this.setState({
dropdown: {
options: options
}
})
Then pass options as <Dropdown /> parameter:
<Dropdown
fluid
selection
placeholder="Data"
options={ this.state.dropdown.options }
value={ this.state.dropdown.selected } />
am refactorizing huge project in which
some components look very similar
there s looots of files (very high level of components vs files granulation)
I've thought and searched for ways to handle this issue here and there and found this great article about Higher Order Components (HOC) - basically being components that wrap another components.
https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.8vr464t20
I will now give you (A) example of two out of eight similar components types that I need to handle, than (B) I will paste the code I came up with that unifies those eight files into one. Finally (C) will paste example of usage of that unified component.
I will try to be consistent and naming will be not domain driven (I can't post project details here) but same names in different code excerpts below will always point to same components and data. Otherwise I will point it.
(A) - similar components types
1. TabA - Simple one
export default class TabA extends Component {
render() {
return (
<PageWrapper>
<Grid>
<GridItem xsSize="3">
<SmartComponent something={this.props.something }/>
</GridItem>
<GridItem xsSize="9">
<Tabs
permalink = { this.props.permalink }
history={ this.props.history }
activeTab={ Paths.somePath }
/>
<TabAContent
data={ this.props.data }
name={ this.props.name }
someValue={ this.props.someValue }
/>
</GridItem>
</Grid>
</PageWrapper>
);
}
}
Notice that SomeComponentA does not take any children in. Also there is no conditional rendering of any kind here.
2. TabB - More complex one
Similarly here, notice that renderSomeData method conditionally renders SmartComponentToBeConditionallyRendered and also SomeComponentB takes children in from the props.
export default class TabB extends Component {
renderSomeData() {
let someData = {
header: "Header text",
searchPlaceHolder: 'Search (name)',
buttonCaption: 'button caption'
};
return (
<SmartComponentToBeConditionallyRendered
type={ 'some_type' }
permalink={ this.props.permalink }
data={ someData }
/>
)
}
render() {
let { data } = this.props;
return (
<div>
<PageWrapper>
<Grid>
<GridItem xsSize="3">
<SmartComponent something={this.props.something}/>
</GridItem>
<GridItem xsSize="9">
<Tabs
permalink = { this.props.permalink }
history = { this.props.history }
activeTab = { Paths.somePage }
/>
<TabBContent data = { data }>
{this.props.children}
</TabBContent>
</GridItem>
</Grid>
</PageWrapper>
{
this.context.hasPermission('somePermission') ?
this.renderSomeData() :
null
}
</div>
)
}
static contextTypes = {
hasPermission: React.PropTypes.func.isRequired
}
}
Those eight components I've wrote about at the beginning - they all represent one of three possibilities.
Two pictured above and possibility C, but differences in C are just another conditionally rendered components so not worth mentioning cause it will finally come down to passing more flags in props.
Those two components above - they differ in:
kind of SomeComponentX - in place of X there may A, B but also C, D, E etc. in every of those eight similar components. Each of SomeComponentX take in different props as well.
Paths.VALUE_HERE
if SomeComponentX takes in any children or not
if they conditionally render data from renderSomeData method
if YES - someData defined inside the method varies as well
permalink
some_type
(B) What I came up with
let availablePartials = {
PartialA: PartialA,
PartialB: PartialB,
PartialC: PartialC
}
export default class GenericTab extends Component {
renderSomeData() {
return (
<SomeData
type = { this.props.type }
permalink = { this.props.permalink }
data = { this.props.someData } //PASSED FROM PROPS NOW
/>
);
}
render() {
let tabContent = React.createElement(
availablePartials[this.props.partialView.name],
this.props.partialView.props,
this.props.renderChildren ? this.props.children : null
);
return (
<div>
<PageWrapper>
<Grid>
<GridItem xsSize="3">
<SmartComponent something = { this.props.permalink }/>
</GridItem>
<GridItem xsSize = "9">
<Tabs
permalink = { this.props.permalink }
history = { this.props.history }
activeTab = { this.props.activeTab }
/>
{ tabContent }
</GridItem>
</Grid>
</PageWrapper>
{
this.context.hasPermission(this.props.requiredPermission) && this.props.dataForSomeDataMethod ?
this.renderSomeData()
: null
}
</div>
)
}
static contextTypes = {
hasPermission: React.PropTypes.func.isRequired
}
};
CityPageTab.propTypes = {
permalink: PropTypes.string,
dataForSomeDataMethod: PropTypes.object,
type: PropTypes.string,
activeTab: PropTypes.string,
renderChildren: PropTypes.bool,
partialView: PropTypes.object,
requiredPermission: PropTypes.string
};
Basically EVERYTHING is constructed from props. The only part I don't like is availablePartials[this.props.partialView.name].
It requires developers to keep the state of availablePartials object consistent and tangles it a bit. Not nice solution but still it is best I came up with so far.
(C) New GenericTab usage example
componentThatUseGenericTabRenderMethod() {
let { valueA, valueB, valueC, history } = this.props;
let someData = {
header: 'header text',
searchPlaceHolder: 'Search (name)',
buttonCaption: 'buttonCaption'
}
return (
<GenericTab
partialView = {{
name: 'PartialA',
props: {
A: valueA,
B: valueB,
C: valueC,
history: history,
permalink: this.props.params.permalink
}
}}
permalink = { this.props.params.permalink }
activeTab = { Paths.somePath }
someData = { someData }
type = { 'SOME_TYPE' }
renderChildren = { false }
requiredPermission = { 'some_required_permision' }
/>
);
}
So that is that. Usage got bit more complex, but I got rid of seven files (and getting rid of files is main objective as there is too many of them) and am going to further push it in similar manner - generic one.
Thing with genericity - it is more difficult to use but saves lots of space.
Project utilises Redux so dont be too concerned about passing props down the tree. They always only come from some SmartParentComponent that renders GenericTab
Below is the visualisation of how it looks on the page.
GenericTab is responsible for rendering Tabs and TabContent parts.
Yes I know it is shitty solution, but am not responsible for architecture of it. There are so many things to be refactorized here and what am asking about is just a step in a journey. So please lets focus on the question asked and not other things that are so wrong with this code. I know.:)
Guess I could make an article out of it but I don't really have blog to do it:).
Please tell me what you think, propose upgrades, different ways of handling this problem etc.
When dealing with such architecture (i.e, tabs in your case), you basically don't want to hide the architecture under the hood, because in this case your ending up adding more and more properties with each new case you want to handle.
Instead, you wan't to let react handles the nested structure since it's where react really shines. That let you write something very generic by handling the built in children props. You typically want to write something like :
const PageWithTabs = (props) => (
<Tabs defaultActive={'targaryens-panel'}>
<TabBar>
<TabLink href="#starks-panel">{'Starks'}</TabLink>
<TabLink href="#lannisters-panel">{'Lannisters'}</TabLink>
<TabLink href="#targaryens-panel">{'Targaryens'}</TabLink>
</TabBar>
<TabPanel id="starks-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Eddard</li>
<li>Catelyn</li>
<li>Robb</li>
<li>Sansa</li>
<li>Brandon</li>
<li>Arya</li>
<li>Rickon</li>
</ul>
</TabPanel>
<TabPanel id="lannisters-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Tywin</li>
<li>Cersei</li>
<li>Jamie</li>
<li>Tyrion</li>
</ul>
</TabPanel>
<TabPanel id="targaryens-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Viserys</li>
<li>Daenerys</li>
</ul>
</TabPanel>
</Tabs>
)
The point here is that you don't have to "predict" all the things that might appear under each TabPanel, simply let the developper put whatever he wants ! BUT me need some logic to handle the "go to tab" sort of things.
React provides some very handy utilities methods to dynamically clone elements, map over elements, and render element whether its type is the one you expect or not (in our case, we expect TabBar or TabPanel type, nothing prevent the developper to put any other components than this two but nothing prevent him neither to put any built in <table> html element inside of <a> tag or something weird like that).
Here is a little implementation with Material Design Lite, it's not perfect but you should get the point :
class Tabs extends React.Component {
constructor(props) {
super(props),
this.state = {
activeTabId: props.defaultActive
}
}
tabClickHandlerFactory(id) {
return (e) => {
e.preventDefault()
this.setState({
activeTabId: id
})
}
}
getPanelIdFromLink(href) {
return href.split('#')[1]
}
render() {
const self = this
return (
<div className='mdl-tabs is-upgraded' {...self.props}>
{React.Children.map(self.props.children, (child, index) => {
if (child.type == TabBar) {
return React.cloneElement(child, {}, React.Children.map(child.props.children, (link) => {
const id = self.getPanelIdFromLink(link.props.href)
return (
React.cloneElement(link, {
onClick: self.tabClickHandlerFactory(id),
active: self.state.activeTabId === id
})
)
}))
}
if (child.type == TabPanel) {
const { id } = child.props
const active = self.state.activeTabId === id
return active && React.cloneElement(child, { active: true })
}
})}
</div>
)
}
}
Tabs.propTypes = {
defaultActive: React.PropTypes.string.isRequired,
}
const TabBar = (props) => <div className='mdl-tabs__tab-bar' {...props}>{props.children}</div>
const TabLink = ({ active, ...props }) => {
return (
<a className={`mdl-tabs__tab${active ? ' is-active' : ''}`} {...props}>{props.children}</a>
)
}
const TabPanel = ({ active, ...props }) => (
<div className={`mdl-tabs__panel${active ? ' is-active' : ''}`} {...props}>{props.children}</div>
)
const PageWithTabs = (props) => (
<Tabs defaultActive={'targaryens-panel'}>
<TabBar>
<TabLink href="#starks-panel">{'Starks'}</TabLink>
<TabLink href="#lannisters-panel">{'Lannisters'}</TabLink>
<TabLink href="#targaryens-panel">{'Targaryens'}</TabLink>
</TabBar>
<TabPanel id="starks-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Eddard</li>
<li>Catelyn</li>
<li>Robb</li>
<li>Sansa</li>
<li>Brandon</li>
<li>Arya</li>
<li>Rickon</li>
</ul>
</TabPanel>
<TabPanel id="lannisters-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Tywin</li>
<li>Cersei</li>
<li>Jamie</li>
<li>Tyrion</li>
</ul>
</TabPanel>
<TabPanel id="targaryens-panel">
<ul style={{ listStyleType: 'none' }}>
<li>Viserys</li>
<li>Daenerys</li>
</ul>
</TabPanel>
</Tabs>
)
ReactDOM.render(<PageWithTabs/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.brown-orange.min.css" />
<div id='app'></div>
I know there are many answers out there for this issue but I couldn't find one that exactly solved my problem. I am getting the following error : Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of QuestionItem. See https://fb.me/react-warning-keys for more information.
I am setting a key for the component but I can't get the warning to go away.
Main component :
renderData() {
return this.state.data.map((data) => {
return (
<QuestionItem key={data._id} data={data} delete={this.deleteItem} edit={this.editItem} />
)
})
}
QuestionItem component :
import React, { Component, PropTypes } from 'react';
import Card from 'material-ui/lib/card/card';
import CardActions from 'material-ui/lib/card/card-actions';
import CardHeader from 'material-ui/lib/card/card-header';
import CardMedia from 'material-ui/lib/card/card-media';
import CardTitle from 'material-ui/lib/card/card-title';
import FlatButton from 'material-ui/lib/flat-button';
import CardText from 'material-ui/lib/card/card-text';
import Delete from 'material-ui/lib/svg-icons/action/delete';
import ModeEdit from 'material-ui/lib/svg-icons/editor/mode-edit';
import IconButton from 'material-ui/lib/icon-button';
import Button from '../UI/Button';
class QuestionItem extends Component {
renderTags() {
return this.props.data.tag.map((tag) => {
return (
<FlatButton label={tag} />
)
})
}
renderCompany() {
return this.props.data.company.map((company) => {
return (
<FlatButton label={company} />
)
})
}
edit = () => {
this.props.edit(this.props.data);
}
delete = () => {
this.props.delete(this.props.data._id);
console.log(this.props.data._id);
}
render() {
return (
<Card style={{margin: 50}}>
<CardTitle title={this.props.data.text} />
<CardText>
{this.props.data.answer}
</CardText>
<CardActions>
{ this.renderTags() }
{ this.renderCompany() }
<IconButton onClick={this.delete} style={{float: 'right'}}>
<Delete />
</IconButton>
<IconButton onClick={this.edit} style={{float: 'right'}}>
<ModeEdit />
</IconButton>
</CardActions>
</Card>
)
}
}
export default QuestionItem;
What am I missing here?
Well you'll need to log out the data._id and verify that they are all unique. Or you can do this:
renderData() {
return this.state.data.map((data, index) => {
return (
<QuestionItem key={index} data={data} delete={this.deleteItem} edit-{this.editItem} />
);
});
}
As the other answer pointed out, the other calls to map that go to a render need to set the key prop too to a unique value.
So these:
renderTags() {
return this.props.data.tag.map((tag) => {
return (
<FlatButton label={tag} />
)
})
}
renderCompany() {
return this.props.data.company.map((company) => {
return (
<FlatButton label={company} />
)
})
}
Should become:
renderTags() {
return this.props.data.tag.map((tag, index) => {
return (
<FlatButton key={index} label={tag} />
);
});
}
renderCompany() {
return this.props.data.company.map((company, index) => {
return (
<FlatButton key={index} label={company} />
);
});
}
Note we are using index which is the array index. It is basically like a synthetic identifier in SQL. If what you're actually rendering has unique identifiers already, it is better to use those! For example, the key prop for a tag could be just the tag -- the string itself. The key prop supports multiple types:
react - nodes-and-elements:
key : string | boolean | number | null,
So if your tags are unique (I would expect them to be but obviously don't want to assume), you could do this:
renderTags() {
return this.props.data.tag.map((tag) => {
return (
<FlatButton key={tag} label={tag} />
);
});
}
You might consider doing instead something like (tag || '').toLowerCase().replace(' ', '_') however I think React is already doing some manipulation there (besides potentially character case). So just passing the tag itself should be good! You can inspect the DOM to see data-reactid if you're not running a version that got rid of it (I think 0.15 gets rid of it). The React developer tools might let you inspect the key with 0.15.
Update
I do not recommend using the array index as the key. It causes subtle bugs. To see this in action, make an array of objects, render them using the array index and then mutate the array by removing say the 2nd element (and ensure React renders again). Now the indexes don't correspond to the same objects. My recommendation is to always set a key to a unique value. During development, it might be best not to set a key until you find one rather than using the array index because then the errors on the console will remind you to fix this before deploying/committing your change.
In renderTags() and renderCompany() you have iterators with no keys.