Add a chidren at first of component children - reactjs

I create an wrapper for Picker component And I want to add a child in condition
I try this code
render() {
const { children, ...rest } = this.props;
return (
<Picker {...rest}>
{rest.selectedValue === undefined ? (
<Picker.Item value="1" label="1" />
) : null}
{children}
</Picker>
);
}
And I get this error
TypeError: null is not an object(evaluating 'o.props')
I also try with undefiened instead of null and short circuit condition but I get error in that case too.
Also note If I remove condition and just add an element before children its work. something like this
<Picker.Item value="1" label="1" />
{children}
UPDATED
I found the problem is when condition is false for example this make error
{false && (<Picker.Item value="1" label="1" />)}
expo project https://snack.expo.io/#cooper47/mad-waffle
( change picker and see the error )
I think the problem is when I concat children with undefined ( result of condtion ) because when I try this I get same error
<Picker {...rest}>
{undefined} // result of short circuit
{children}
</Picker>
Why I get this error with conditon and get no error without condition?
How I can add an element before this.props.children?
Is there anyway add element at first of children? for example children.add(<Picker.Item...>)
I found out actually its a bug in react-native
Here is the relative issue https://github.com/facebook/react-native/issues/25141#issuecomment-498651856

Note: There is no problem with the condition, the error depends on the wrapper (Picker) internal calls.
My guess is that Picker internal calls its children props (expects Picker.Item), it may check your condition props when it falsy and therefore throw an error of undefined props.
<Picker>
{condition && <Picker.Item value="1" label="1" />}
// ^ Picker wrapper may check null.props, undefined.props, false.props etc
{children}
</Picker>
In this case, make the condition outside the render:
const conditionChildren = condition ? (
<> <Picker /> {children} </> ) : (children);
return {conditionChildren}
How I can add an element before this.props.children?
Use React.createElement or call the component inline:
function ConditionChildren({ children, condition }) {
return (
<>
{condition && <Picker />}
// or {condition && React.createElement(Picker)}
{children}
</>
);
}
Is there anyway add an element at first of children? for example children.add()
const addFirstToChildren = [
React.createElement(Picker),
...React.Children.toArray(children)
];
return {addFirstToChildren}
Check the demo with the generic solution:

Try below code for PureComponent
import React, { PureComponent } from 'react'
import { View, Text, TouchableOpacity, Picker } from 'react-native'
export default class SelectBox extends React.PureComponent {
constructor(props) {
super(props)
}
render() {
const { options, value, onChageText } = this.props
let Alloptions = options.map((item, key) => {
return (<Picker.Item label={item.name} value={item.value} key={key} />)
})
return (
<Picker selectedValue={value} onValueChange={onChageText}>
{/* If you want to add default option here */}
{/* <Picker.Item label="Select Location..." value="" /> */}
{Alloptions}
</Picker>
)
}
}
and your Component Like
<SelectBox
options={[{name:'aaa',value:'11'},{name:'bbbb',value:'22'}]}
value={''}
onChageText={this.selectLocation}
/>
selectLocation(itemValue, itemIndex) {
...your code
}
Thanks

Related

Passing component as prop to child component

I want to pass multiple badge components as props to a component.
<MouseOverPopover
data={
row.original.region == null ? (
"-"
) : !Array.isArray(row.original.region) ? (
<Badge variant="info"> {row.original.region} </Badge>
) : (
row.original.region.map((region) => (
<Badge variant="info" style={{ margin: "2px" }} key={region}>
{region}
</Badge>
))
)
}
/>
Here is the MouseOverPopover component
export default function MouseOverPopover({ data }) {
I then use { data } to display the content.
However, the badges are not rendering and I got this error in the log
Failed prop type: Invalid prop variant of value info supplied to ForwardRef(Badge)
I am guessing that passing component inside props is probably the wrong way to do that. Is there a right way I can do this?
First point: error it self says
Failed prop type: Invalid prop variant of value info supplied to ForwardRef(Badge)
Means below Badge with props variant="info" is invalid.
<Badge variant="info"> {row.original.region} </Badge>
^^^
There are only two variant for Badge 'dot'| 'standard' check here
Second point: different way to pass your props
Here React official site Composition vs Inheritance
Containment is one of the way to pass your props Badges/any other components to MouseOverPopover (as it represent generic β€œboxes”) component;
As per my opinion do this
const getBadges = () => {
if (row.original.region == null) {
return "-";
} else if (!Array.isArray(row.original.region)) {
return <Badge variant="standard">{row.original.region}</Badge>;
} else {
return row.original.region.map((region) => (
<Badge variant="standard" style={{ margin: "2px" }} key={region}>
{region}
</Badge>
));
}
};
<MouseOverPopover>{getBadges()}</MouseOverPopover>;
MouseOverPopover component:
export default MouseOverPopover = ({ children }) => {
return (
...
{children} //badge will render here
...
)
}
Your code will also works you used your own convention (data={...}) and I used children as props

can you set outer HTML in return function of a React component?

The default behavior of the MailchimpSubscribe component displays a status alert upon user signup. I want to move this status alert outside of it's position in the DOM so that it shows up at the top of the page.
My code looks like this:
import MailchimpSubscribe from "react-mailchimp-subscribe"
import SimpleForm from './SimpleForm.js'
function Info() {
return (
<div className="canary">
<MailchimpSubscribe url={process.env.REACT_APP_MAILCHIMP_URL}
render={({subscribe, status, message}) => <SimpleForm
status={status}
message={message}
className="form"
style={{}}
onSubmitted={formData => subscribe(formData)}
/>
}/>
</div>
);
}
export default Info;
To change the default button text, I create my own SimpleForm.js component:
import React from "react";
// a basic form
const SimpleForm = ({ status, message, className, style, onSubmitted }) => {
let input;
const submit = () =>
input &&
input.value.indexOf("#") > -1 &&
onSubmitted({
EMAIL: input.value
});
return (
<div className={className} style={style}>
{status === "sending" && <div style={{ color: "blue" }}>sending...</div>}
{status === "error" && (
<div
style={{ color: "red" }}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
{status === "success" && (
<div
style={{ color: "green" }}
dangerouslySetInnerHTML={{ __html: message }}
/>
)}
<input
ref={node => (input = node)}
type="email"
placeholder="Your email"
/>
<button onClick={submit}>Subscribe</button>
</div>
);
};
export default SimpleForm;
How would I modify this return function so that the dangerouslySetInnerHTML={{ __html: message }} part is set on <div className="canary">?
From what I see in component definition you can pass render props
MailchimpSubscribe.defaultProps = {
render: ({ subscribe, status, message }) => (
<SimpleForm
status={status}
message={message}
onSubmitted={formData => subscribe(formData)}
/>
)
};
with SimpleForm and include specific className style
<MailchimpSubscribe url={process.env.REACT_APP_MAILCHIMP_URL}
render={({subscribe, status, message}) => <SimpleForm
status={status}
message={message}
className="form"
style={{}}
onSubmitted={formData => subscribe(formData)}
/>
}/>
If you need more customization you can create your own component based on what's inside SimpleForm
This is how I understand the problem πŸ˜€:
Question
How do you render data passed as props to Component A within Component B?
MailchimpSubscribe holds message inside of its internal state and passes it as a prop to the results of its render function
Answer
React aims to only pass data from parents to children in unidirectional data flow,
so the data passed as props to MailchimpSubscribe cannot directly be rendered in another component which is not its child πŸ˜•
The best way I can think of to circumvent this is to leverage the Context API to create shared state between the SimpleForm rendered inside MailchimpSubscribe and a DisplayMessage rendered elsewhere in your app.
In a new file SharedState.js
Create SharedContext to allow components to access context
Create SharedProvider which is a parent that will make the context available to all its children
In SimpleForm
Read SharedContext
Set up an effect to push the status and message values up to the context. This effect will be called each time those values change.
return null when the status is not null, as you would like to render the message elsewhere and hide the form.
In DisplayMessage
Read SharedContext
Use status and message from SharedContext to render your message
Demo
I put together this CodeSandbox which illustrates this strategy.
References
React docs for useContext
React docs for Context
Hope that helps! Let me know if there's anything you'd like clarified further πŸ‘

React.Children.only expected to receive a single React element child Stuck

Get an error Error: React.Children.only expected to receive a single React element child.
Really stuck, Anybody help?
componentDidMount() {
axios.get(this.props.url).then((res) => {
const data = res.data._embedded.districts;
this.setState({ data });
console.log(this.state.data);
});
}
render() {
console.log("render");
if (this.props.terr === "districts") {
return (
<FeatureGroup>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</FeatureGroup>
);
}
}
}
I got same error. In my case there was an empty space inside component with tag. Empty space {""} inside was considered another component and since <Link
accepts only one component I got that error.
<Link href="/"> {" "} // I removed empty space
<a className="navbar-brand">GitTix</a>
</Link>
Without seeing all the code it is hard to tell. I have a tingling that your this.state.data is null or empty in your constructor and the first time you are trying to render your map function isn't returning any data. componentDidMount will run after it has already been rendered.
try checking to see if there is data in your data first.
{this.state.data.length && <FeatureGroup>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</FeatureGroup>}
As the error describes, FeatureGroup is expecting a single element as a child. Currently you have one to many elements given the Array.prototype.map you are using to process an array and generate elements. Likely this is coming from prop-types on FeatureGroup that specify how many children are allowed. This is not uncommon, elements like Provider from react-redux expect a single child. At minimum wrap the output of the Array.prototype.map with an element:
<FeatureGroup>
<div>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</div>
</FeatureGroup>
Or you can use React.Fragment:
<FeatureGroup>
<React.Fragment>
{this.state.data.map((data) => {
return (
<GeoJSON
key={data.name}
data={data.geometry}
style={this.myStyle}
>
<Popup>{data.name}</Popup>
</GeoJSON>
);
})}
</React.Fragment>
</FeatureGroup>
Hopefully that helps!

antd SubMenu "TypeError: Cannot read property 'isRootMenu' of undefined"

I use antd 3.15 and GraphQL to fetch data and generate a list of SubMenu and Menu.Item inside of Menu. However, I got the error message like this Uncaught TypeError: Cannot read property 'isRootMenu' of undefined I have no idea what is wrong with my code. isRootMenu is not a prop listed anywhere on the doc. ant.design/components/menu/#header and when I hardcoded all the SubMenu and Menu.List there is no problem. Can I iterate data from GraphQL to generate the SubMenu and Menu.List?
Can someone help me with this issue, please? Thank you! Here is my code:
import * as React from 'react';
import './SideNav.scss';
import { Menu, Icon } from 'antd';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
const FLOORS_QUERY = gql`
query {
getAllFloors {
id
floorName
rooms {
id
roomName
roomNumber
roomDescription
}
}
}
`;
export default class SideNav extends React.Component {
render() {
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>;
if (error) console.log(error);
console.log(data);
return (
<React.Fragment>
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
}
>
<React.Fragment>
{floor.rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</SubMenu>
))}
</React.Fragment>
);
}}
</Query>
</Menu>
);
}
}
You should pass the props to the submenu.
const CustomComponent = (props) => (
<Menu.SubMenu title='SubMenu' {...props}>
<Menu.Item>SubMenuItem</Menu.Item>
</Menu.SubMenu>
)
so a solution to your question would be to do the following;
move the query outside of the containing menu
pass the props to the SubMenu
const FloorMapSubMenu = ({ id, floorName, rooms, ...other }) => {
return (
<Menu.SubMenu
key={id}
title={
<span>
<Icon type="plus" />
<span>{floorName}</span>
</span>
}
{...other} // notice the other props, this is were the 'isRootMenu' is injected from the <Menu> children
>
<React.Fragment>
{rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</Menu.SubMenu>
)
}
class SideNav extends React.Component {
render() {
return (
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>
if (error) console.log(error)
console.log(data)
return (
<Menu theme='light' defaultSelectedKeys={['1']} mode='inline'>
{data.getAllFloors.map((floor, i) => (
<FloorMapSubMenu key={i} id={floor.id} floorName={floor.floorName} rooms={floor.rooms} />
))}
</Menu>
)
}}
</Query>
)
}
}
I found out the Ant design SubMenu needs to use the parent to check some properties like isRootMenu at
SubMenu.js:260
getPopupContainer = props.parentMenu.isRootMenu ? props.parentMenu.props.getPopupContainer : function (triggerNode) {
return triggerNode.parentNode;
}
In order to solve it you need to manually pass parent props into SubMenu like
<Menu.SubMenu {...this.props}/>
to solve the problem. Hope this helps u
Related Github issue item https://github.com/react-component/menu/issues/255
I had this issue while trying to add a <div> as a child of Menu. I just added an empty Menu.Item as the first child of my menu, and the error went away.
<Menu>
<Menu.Item style={{display: 'none'}} />
<div>...</div>
</Menu>
I ran into the same issue. It seems Antd does not allow to place arbitrary components into a Menu/SubMenu. My guess is that Menu.Item needs to get some props, which are passed from Menu/SubMenu to its children.
So you can either create a custom component that passes all props down, or remove the inner <React.Fragment> declaration (the one that is inside the SubMenu), which is not needed anyway.
I was able to make it work by putting the <Query> Component at the top:
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <Spin />;
if (error) console.log(error);
console.log(data);
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<Link to="/{floor.id}">
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
</Link>
}
>
{floor.rooms.map((room) => (
<Menu.Item key={room.id} onClick={this.showRoomProfile}>
{room.roomNumber}
</Menu.Item>
))}
</SubMenu>
))}
</Menu>
);
}}
</Query>
According to the Typescript definitions the childrens of Menu should be of kind Item, SubMenu, ItemGroup or Divider. If you must place a different component on the Header, wrap the Menu and the desired component on a Header component component as such:
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
<Layout>
<Layout.Header>
<div className="logo" />
<Menu>
<Menu.Item key="1">nav 1</Menu.Item>
<Menu.Item key="2">nav 2</Menu.Item>
</Menu>
<Layout.Header>
</Layout>
I have run into the same issue. But my issues was I have using ternary condition to show some menu's dynamically inside part used the <></> element. That caused me this issue. Once removed that everything work fine.

Pass parameter to component 2 levels deep React Native

Im trying to pass parameters through to a component two levels deep.
I have 2 screens (MainScreen & UserProfileScreen) with a flat list on both screens, both flat lists use the same component EventCard in its renderItem. EventCard is made up of 3 three nested components EventCardHeader,EventCardBody & EventCardFooter. How do I pass certain arguements only from the UserProfileScreen? I have posted code below to give a better understanding of what I have.
MainScreen
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<EventCard
openEventDetail={() => this.openEventDetail(item)}
{...item}
/>}
/>
UserProfileScreen
<FlatList
data={this.state.events}
// Get the item data by referencing as a new function to it
renderItem={({item}) =>
<EventCard
openEventDetail={() => this.openEventDetail(item)}
openEditEvent={() => this.openEditEvent(item)}
openDeleteEventAlert={() => this.openDeleteEventAlert(item)}
{...item}
/>}
/>
openEditEvent = (event) => {
this.props.navigation.navigate('EventFormScreen', {
event: event,
eventKey: this.state.eventKey,
editMode: true,
});
};
EventCard
export default class EventCard extends Component {
render() {
return (
<Card>
<EventCardHeader
eventOrganiserImage={this.props.eventOrganiserImage}
eventVenue={this.props.eventVenue}
openEditEvent={() => this.openEditEvent()}
/>
<EventCardBody
openEventDetail={() => this.props.openEventDetail()}
imageDownloadUrl={this.props.imageDownloadUrl}
/>
<EventCardFooter
openEventDetail={() => this.props.openEventDetail()}
eventName={this.props.eventName}
eventStartDate={this.props.eventStartDate}
eventVenue={this.props.eventVenue}
eventAddressLine1={this.props.eventAddressLine1}
eventAddressLine2={this.props.eventAddressLine2}
/>
</Card>
);
}
};
EvenCardHeader
export default class EventCardHeader extends Component {
render() {
return (
<CardSection style={styles.eventCardHeader}>
<Thumbnail small
style={styles.eventOrganiserImage}
source={{uri: this.props.eventOrganiserImage}}/>
<Text style={styles.eventPromoterName}>{this.props.eventVenue}</Text>
{!!this.props.openEditEvent &&
<Button onPress={() => this.props.openEditEvent()}>
Edit
</Button>
}
{!!this.props.openDeleteEventAlert &&
<Button onPress={() => this.props.openDeleteEventAlert()}>
Delete
</Button>
}
</CardSection>
);
}
}
I can see that because I have hardcoded the this.openEditEvent() function into my EventCard component that what's causing me the problem, because then the if statement in EventCardHeader that checks if the this.openEditEvent() exists always evaluates to true. Would someone be able to help show me the right way to do this? Thanks in advance for any help.
UPDATE:
Added in openEditEvent
Where is openEditEvent() declared? It should be in the parent component and passed as props to whatever children you need it in. You can continue to pass it as props from children to children.
EDIT:
Ok so you can pass openEditEvent as props like so:
<EventCard
openEditEvent = this.openEditEvent
openEventDetail={() => this.openEventDetail(item)}
openDeleteEventAlert={() => this.openDeleteEventAlert(item)}
{...item}
/>}
That function can be available in EventCard, and can then be passed AGAIN as props to another child component:
render() {
var openEditEvent = this.props.openEditEvent;
return (
<Card>
<EventCardHeader
eventOrganiserImage={this.props.eventOrganiserImage}
eventVenue={this.props.eventVenue}
openEditEvent = openEditEvent
/>
<EventCardBody
openEventDetail={() => this.props.openEventDetail()}
imageDownloadUrl={this.props.imageDownloadUrl}
/>
<EventCardFooter
openEventDetail={() => this.props.openEventDetail()}
eventName={this.props.eventName}
eventStartDate={this.props.eventStartDate}
eventVenue={this.props.eventVenue}
eventAddressLine1={this.props.eventAddressLine1}
eventAddressLine2={this.props.eventAddressLine2}
/>
</Card>
);
}

Resources