key is not available as prop in the Child component in ReactJs - reactjs

I have a parent component in which below component is producing dynamically in map function as below:
const renderListing = this.props.listing.map(function(list, index) {
return (
<Listing
key={index}
title={list.title}
totalWorkers={list.totalWorkers}
/>
);
}, this);
In this <Listing /> component, I have a Checkbox from react-md as below:
import { Checkbox } from "react-md";
class Listing extends React.Component {
render() {
return (
<Checkbox id="`listSector-${this.props.key}`" name="list-sector" />
);
}
}
I wanted to give the props named key concatenated with id="listSector-0" , id="listSector-1" and so on.
I have tried string-literals but all invain.
Can anyone know that how to give dynamic this.props.key concatenated with id of the checkbox ?
Thanks

'key' and 'ref' are reserved words that are not allowed to be passed as props. You can pass another prop with the same value
const renderListing = this.props.listing.map(function(list, index) {
return (
<Listing
key={index}
keyId={index}
title={list.title}
totalWorkers={list.totalWorkers}
/>
);
}, this);
and use it like
<Checkbox id="`listSector-${this.props.keyId}`" name="list-sector" />

Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.
Special Props Warning – React

Related

handling props in a deconstructed child

I have a React component that clones its children with additional props. I'm using the standard childrenWithProps method that works great if your child is another react component but no clear way of doing this without a direct react component as the child.
<DataCmp>
<Fragment>
<h1>Example Code</h1>
<div>{isLoggedIn}</div>
</Fragment>
</DataCmp>
In this example, I have adding the prop myData to the props of its children. However, this doesn't work. The child doesn't see the value. It will say myData is not set when it's passed in by props.
So I attempted this:
<DataCmp>
<Fragment>
<h1>Example Code</h1>
<div>{this.props.isLoggedIn}</div>
</Fragment>
</DataCmp>
This brings up errors as it has no idea what this.props.myData is.
My next attempt was to wrap the child in an inline function and get the prop from that.
<DataCmp>
{({ isLoggedIn}) => (
<Fragment>
<h1>Example Code</h1>
<div>{isLoggedIn}</div>
</Fragment>
)}
</DataCmp>
While this doesn't bring up any errors; The child component is never rendered.
I'm working on updating and modernizing someone else old Github project. here is the link to my project and the wrapping component is Wallet.jsx the location that it's being used is index.jsx
The children are rendered as such:
renderChildren = () => {
const { children } = this.props;
const { accounts } = this.state;
const handleLogin = () => this.login();
const childrenWithProps = React.Children.map(children, (child, index) => {
if(typeof child == 'object') {
return React.cloneElement(child, {
key: index,
loginFn: () => handleLogin(),
isLoggedIn: accounts[0] !== '0x0',
accounts,
});
} else {
return child;
}
});
return childrenWithProps;
}
I guess the error may not be in the destructuring, but in how you are using childrenWithProps.
It would be useful if you shared a condesandbox representing the problem with dummy data, so we can take a look there at that part too.
React.Children.map(children, fn) only iterates over valid react elements
This excludes, for example, functions passed as child. Passing a function-to-be-rendered as prop to a component is known as Render Props pattern. React.Children.map will not iterate over this, hence, your third option returned null.
Fix it by checking whether children is a valid ReactElement first and render it accordingly:
// wallet.tsx
...
const additionalProps = { ... };
if (React.isValidElement(children)) {
return React.Children.map(children,
(child, i) => React.cloneElement(child, { key: i, ...additionalProps });
} else {
// handle null, strings, undefined, booleans etc
return typeof children === 'function' ? children(additionalProps) : children;
}
...
// index.tsx
<Wallet ...>
{/* either a function */}
{(additionalProps) => console.log(additionalProps)}
{/* or components */}
<Layout>
...
</Layout>
</Wallet>
Note that React.isValidElement() also returns true for HTML-Elements. Which will receive the props but you obviously cannot add custom-logic. But let's say you pass a style props, it will be applied.

React render specific children

I have looked through other peoples questions relating to this but cant find a suitable answer. I would like to pass children to a component and then pull out the specific children where I want them, most examples I have seen just have the children render in the same place.
My component looks something like this -
<ParentComponent>
<ChildOne/>
<ChildTwo/>
<ParentComponent/>
When I log the props.children inside the parent component I get an array which contains both children as objects. is there a simple way to pull out the specific child where I need it such as {props.children.ChildOne} at the moment I am using props.children[0] which isn't ideal as we will be passing the children dynamically
in the future and the array length may change.
As always any help is greatly appreciated!
Depending on your exact situation and needs, it might make more sense to pass child components as props than using the special children prop. Then you can render them whichever way you like.
<ParentComponent childOne={ChildOne} childTwo={ChildTwo} />
...
const ParentComponent = ({ childOne, childTwo }) => {
return (
<div>
{childOne}
<div>
{childTwo}
</div>
</div>
);
};
But knowing your exact scenario would help a lot with conceptualising the best way to implement this. Perhaps you can refactor your code to avoid passing an array of children like this.
Actually, the ReactChildren API I was mentioning is useless here.
You can do something like this instead:
import React from 'react';
import { ChildOne } from './YourFile';
export function ParentComponent({children}) {
return children.find(child => child.type === ChildOne)
}
You should define the displayName property for the child components and then use the displayName in the parent to find the specific children from children list and place them where you want it to be.
// define displayName for each component, it can be any string
// You can set the displayName for any react component before exporting as shown
// below
const ChildOne = (props) => { return (<div> Child One </div>)}
ChildOne.displayName = "ChildOne";
export default ChildOne;
const ChildTwo = (props) => { return (<div> Child Two </div>)}
ChildTwo.displayName = "ChildTwo";
export default ChildTwo;
Now in parent component you can filter out the specific child by using their displayName.
const ParentComponent = (props) => {
const getChildByDisplayName = (displayName) => {
const child = React.Children.map(props.children, (child => {
// you can access displayName property by child.type.displayName
if (child.type.displayName === displayName) return child;
return null;
}))
return child;
}
return (
<div>
{/* You can change the order here as per your wish*/}
{getChildByDisplayName("ChildOne")}
{getChildByDisplayName("ChildTwo")}
</div>
)
}
That's it, Now even if you put ChildTwo before ChildOne like below example, parent component will still render the ChildOne first and then ChildTwo because we have defined order in parent.
<ParentComponent>
<ChildTwo/>
<ChildOne/>
<ParentComponent/>
Using the key seems simpler:
whatever is using the parent component:
<ParentComponent>
<ChildOne key="title"/>
<ChildTwo key="picture"/>
<ParentComponent/>
parent component:
export default function ParentComponent(props: any) {
const title = props.children.find((o: any) => o.key === 'title')
const picture = props.children.find((o: any) => o.key === 'picture')
return <div>
<jumbobox>
{title}
</jumbobox>
<fancyframe>
{picture}
</fancyframe>
</div>
}
One way is to control the Child Components being passed to ParentComponent through state/props/redux store management in the component containing ParentComponent and its children.
But to have the solution as per your use case, we can avoid using children prop and use our defined prop on ParentComponent.
<ParentComponent
components={[
{key: 1, component: <div>Hello</div>}
]}
/>
So, we can now filter from the key.
Checkout this demo

ReactJS: Functional component with props defined as Array but seen as Object

When rendering <MyComponent {...docs} />, I kept the following error:
TypeError: docs.map is not a function
Here is how I am rendering <MyComponent /> from a parent class based component:
import * as React from 'react'
import IDoc from '../types/IDoc'
class Doc extends React.Component
{
public render()
{
const docs : IDoc[] = Defs.getDef();
// Example of map working (not doing anything just for an example)
docs.map(x => x);
return (
<div>
<MyComponent {...docs} />
</div>
)
}
}
For some reason when I pass the docs array to the functional <MyComponent/> component, its not seen as an array. I need to convert it to an Array before using .map() which I would prefer to avoid:
import * as React from 'react'
import IDoc from '../types/IDoc'
// docs is an array isn't?
function MyComponent(docs : IDoc[] )
{
if (Array.isArray(docs) === false)
{
//Its not seen as an array so falls into this
return (
<div>
{ Object.keys(docs).map((index) => {
const doc : IDoc = docs[index];
const name = doc.name;
return (
<div>{name}</div>
)
})
}
</div>
)
}else
{
// what I expected to work but it throws the error
return (
<div>
{ docs.map((doc) => {
return (
<div>{doc.name}</div>
)
})
}
</div>
)
}
}
I thought as I defined docs props as IDocs[] it would have been seen as an array due to the square brackets.
The workaround above works, but obviously I don't want to do this every time I use an array function like map(). I am new to React so would appreciate any pointers. I used create-react-app my-app --scripts-version=react-scripts-ts in case that is helpful.
Currently you're spreading the elements of the docs array into the props of MyComponent by doing this:
<MyComponent {...docs} />
Consider revising your MyComponent function so that docs is accessed via it's own prop. This would mean accessings docs via the props object passed to the MyComponent functional component like so:
/* Add props argument, where docs array is an entry of props */
function MyComponent(props : { docs : IDoc[] }) {
const { docs } = props;
/* You can now use docs as before
if (Array.isArray(docs) === false)
*/
}
This change would require that in your Doc component, the MyComponent component is rendered by passing docs as a prop (rather than spreading the elements of the docs array directly into the props of MyComponent) by doing the following:
return (
<div>
{/* Don't spread docs, but instead pass docs via docs prop */}
<MyComponent docs={docs} />
</div>
)
Hope this helps!

Is there a way to access a React component's sub-components?

So I know that you can access a component's children with this.props.children:
<MyComponent>
<span>Bob</span>
<span>Sally</span>
</MyComponent>
Which is great if I'm interested in Bob and Sally, but what if I want to interact with the components that make up MyComponent (i.e. Subcomp1 and Subcomp2 shown below)?
render: function() {
return (
<div className="my-comp">
<Subcomp1 />
<Subcomp2 />
</div>
);
},
Use Case
I'm trying to create a higher order component that manages the tab index (roving tab index: https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex) of the wrapped component's sub-components, so it would be great if I could get a ref to the wrapped component and filter it's subcomponents by type.
So far the only approach that seems possible is to have each component store a ref for each of it's subcomponents, but this is tedious and kind of defeats the purpose of an HOC. Is there a generic way to access these sub-components?
A rough example of what I'm trying to do:
var HOC = (ComposedComponent) => {
return React.createClass({
componentDidMount: function() {
const subComponents = this.composedComponent.subComponents; // Something like this would be nice
const menuItems = subComponents.filter(() => {
// figure out a way to identify components of a certain type
});
this.applyRovingTabIndex(menuItems);
},
render: function() {
return (
<ComposedComponent
ref={(c) => { this.composedComponent = c }}
{...this.props} />
);
}
});
};
The tabIndex manipulation need not be done in the HOC, rather it can be done in the Parent component that renders all the HOCs. Because all you need is to determine which sub component is clicked and adjust the selected state on the Parent component. This selected state can then be propagated back to the sub components who compare their index with selected index and assign tabIndex accordingly.
You can send the respective props to determine whether the current ComposedComponent is selected or not by passing an onClick event handler all the way. Then in your sub component you can access tabIndex using this.props.tabIndex and render your parent div as
<div tabIndex={this.props.tabIndex}> </div>
The code below is almost like pseudo code to give an idea. If you feel that this does not solve your requirement you can try out a Tab example worked out by an awesome developer at this link CODEPEN EXAMPLE
const HOC = (ComposedComponent) => {
return class extends React.Component {
render (
<ComposedComponent
tabIndex={this.props.selected === this.props.index ? "0" : "-1"}
{...this.props}
/>
)
}
}
class Parent extends React.Component {
state = {
selected: 0
}
// Set the current selection based on the currentSelection argument
// that is bound to the function as it is sent along to Props
adjustTabIndices = (currentSelection) => (event) => {
this.setState({selection: currentSelection})
}
render {
return (
<div>
{
// These are your various MenuItem components that
// you want to compose using HOC
[MenuItem1, MenuItem2, MenuItem3].map(index => {
const MenuItem = HOC(MenuItem1);
return (
<MenuItem
key={index}
onClick={this.adjustTabIndices(index)}
selection={this.state.selected}
index={index}
/>
)
})
}
</div>
)
}
}

how to pass props to a specific component in redux when iterating

So I have this component in which I use the map function to call another component :
class configstepper extends React.Component {
...
//in render
if(this.props.selected_devices){
return(
this.props.selected_devices.map( (row, index) => (
<DeviceProps key={index} row={row} index={index} />
)))}
...}
Now my DeviceProps component is fetching data so it has a props to render
my problem is i want the props to be specific to a each DeviceProps not to all of them :
class deviceProperties extends Component {
...
//in render
<RaisedButton style={styles.Button}label="Read From Device" primary={true} onClick={this.handleRead.bind(this)}/>
<TextField id={this.props.index} value={this.props.read_data} readOnly/>
...
//handleRead where i trigger the action
handleRead(){
this.props.read_device()
}
//mapstate to props
function mapStateToProps(state){
return {
read_data:state.config.test}}
connect(mapStateToProps,actions)(deviceProperties)
Data is fetched on button click when i render all the DeviceProps components get the same data while i only want to get it on the clicked component.
I couldn't figure a work around this one
The mapStateToProps function accepts a second argument being the props you passed into the component from the parent, you could use these to the fetch a slice of your state that is specific to each of your DeviceProps instances. Equally the mapDispatchToProps also accepts the props as a second argument allowing you to create actions specific to each of your DeviceProps instances.
For example, say you were passing in a prop called deviceId into your DeviceProps component, with deviceId being a unique identifier for a specific device:
<DeviceProps key={index} row={row} index={index} deviceId={deviceId} />
Then you could use this unique identifier in your map state and props functions like so:
class DeviceProps extends Component {
render() {
...
}
}
function mapStateToProps(state, ownProps){
return {
device: state.devices[ownProps.deviceId]
}
}
function mapActionsToProps(dispatch, ownProps) {
return bindActionCreators({
readDeviceData: () => DeviceActions.readDeviceData(ownProps.deviceId)
}, dispatch)
}
connect(mapStateToProps, mapActionsToProps)(DeviceProps)

Resources