Passing component as prop to child component - reactjs

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

Related

How to fix this error in react `onClick` listener to be a function, instead got a value of `string` type

I have this codes in react:
const [categoryId, setCategoryId] = useState("");
{
catName.map((singleCategory, index) => {
const { catName, _id: categoryId } = singleCategory;
return (
<>
<div
className="category-single-div flex-3 center-flex-align-display"
key={index}
>
<p className="text-general-small2 category-custom-text">{catName}</p>
<div className="category-icons-div ">
<FaEdit
className="category-icon-edit"
onClick={() => {
setEditCategory(true);
setCategoryId(categoryId);
}}
/>
<AiFillDelete className="category-icon-edit category-icon-delete" />
</div>
</div>
</>
);
});
}
I used map to get an array of objects, and I needed their individual _id when a user clicks the edit button. I also want to call another function on the same edit button via onClick. It is working but displays an error.
Warning: Expected onClick listener to be a function, instead got a
value of string type.
I need that _id so as to pass it to a state and have access to it globally within the component at the top level.
Is this workable?
Your problem comes from the FaEdit component.
<FaEdit
id={categoryId}
className="category-icon-edit"
onClick={(editCategory, id) => { // you need to have onClick as a prop defined inside the FaEdit component
setEditCategory(editCategory);
setCategoryId(id);
}}
/>
Example...
export default function FaEdit({className, onClick, categoryId}){
const handleChange() => {
onClick(true, categoryId)
}
return(
<div className={className} onClick={() => handleChange()}>Click</div>
)
}

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 πŸ‘

Add a chidren at first of component children

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

Async loading of components in react-virtualized?

I have a react-virtualized masonry grid implemented in a component as follows:
const MasonrySubmissionRender = (media: InputProps) => {
function cellRenderer({ index, key, parent, style }: MasonryCellProps) {
//const size = (media.submissionsWithSizes && media.submissionsWithSizes.length > index) ? media.submissionsWithSizes[index].size : undefined
//const height = size ? (columnWidth * (size.height / size.width)) : defaultHeight;
function getCard(index: number, extraProps: any) {
var Comp = media.cardElement ? media.cardElement : SubmissionCard
return <Comp submission={media.media[index]} {...media.customCardProps} />
}
return (
<div>
<CellMeasurer
cache={cache}
index={index}
key={key}
parent={parent}>
<div style={style}>
{getCard(index, media.customCardProps)}
</div>
</CellMeasurer>
</div>
);
}
return (
<Masonry
overscanByPixels={1000}
autoHeight={false}
cellCount={media.media.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={cellRenderer}
style={{ backgroundColor: 'red' }}
height={900}
width={900}
/>
);
};
It renders a list of fairly complicated components which contain an array of chips, css animations, etc.
Due to this rendering is very slow even with react-virtualized.
I'd like to implement a system like in imgur.com where the component itself won't necessary load immediately displaying only a silhouette while I can have the component preparing to render in the background.
I know there's a way to swap out the component during scrolling butt his hides all components including ones which have already rendered.
Like all react-virtualized cell/row renderers, masonry's cell render is passed an isScrolling property. When the masonry is scrolling you can render a place holder instead of the cell content:
if (isScrolling) return (
<div>placeholder</div>
);
In addition, you are recreating all functions whenever the stateless component is rerendred. This causes an extra overhead for the garbage collector, and might also cause components to rerender unnecessarily.
Convert the component to a class component. The cellRenderer should be an instance method (use class properties or bind in constructor). The getCard can be a class method, or you can extract it from the component, and pass the media when you call the function.
Your code should be something like this (not tested):
function getCard(media: InputProps, index: number) {
var Comp = media.cardElement ? media.cardElement : SubmissionCard
return <Comp submission = {
media.media[index]
} { ...media.customCardProps }
/>
}
class MasonrySubmissionRender extends React.Component {
cellRenderer = ({
index,
key,
parent,
style,
isScrolling
}: MasonryCellProps) => {
if (isScrolling) return (
<div>placeholder</div>
);
return (
<div>
<CellMeasurer
cache={cache}
index={index}
key={key}
parent={parent}>
<div style={style}>
{getCard(media, index)}
</div>
</CellMeasurer>
</div>
);
}
render() {
return (
<Masonry
overscanByPixels={1000}
autoHeight={false}
cellCount={media.media.length}
cellMeasurerCache={cache}
cellPositioner={cellPositioner}
cellRenderer={this.cellRenderer}
style={{ backgroundColor: 'red' }}
height={900}
width={900}
/>
);
}
}

semantic-ui-react List onClick declaration

i'm trying to create a list of documents dynamically with semantic-ui-react. I'd like to get the document title back when the list item is clicked. According to the documentation:
https://react.semantic-ui.com/elements/list
there is an onItemClick prop for this reason, however when im using it i get a warning when it's rendered:
Warning: Failed prop type: Prop onItemClick in List conflicts with props: children. They cannot be defined together, choose one or the other.
Also clicking on the list item does nothing (atm i just want to log the doc title to the console). Here is the code:
handleListItemClick(event, data) {
console.log("list item clicked: " + data.value);
}
buildResultsContainer() {
return this.props.listOfResults.map((document,index) =>
{
return (
<List.Item
as='a'
key={index}>
<Icon name='file' />
<List.Content>
<List.Header>{document.properties.title}</List.Header>
<List.Description>
{document.properties.description}
</List.Description>
</List.Content>
</List.Item>
);
}
);
}
render() {
return (
<div>
<List onItemClick={this.handleListItemClick}>
{this.buildResultsContainer()}
</List>
</div>
)
}
Can you please tell me how to use properly the onItemClick prop for the List component?
Less important, do you have any tip how to refactor the list rendering? Just wanted to keep the render function short and clean, but this function call looks a bit overkill....
Thanks a lot!
I think maybe the intent when using onItemClick is that you would use the items prop on List since then you wouldn't have any children e.g.
render() {
const items = this.props.listOfResults.map(document => {
return {
icon: 'file',
content: document.properties.title,
description: document.properties.description,
onClick: e => console.log(document.title)
}
});
return <List items={items} />
}
If you had your listOfResults prop in the above format, you wouldn't even need to do this map and your render function would be super tight:
render() {
return <List items={this.props.listOfResults} />;
}
Alternately, List.Item takes an onClick prop that you could define in your buildResultsContainer() function. Because each onClick function is unique based on the current document object, you will need to use an anonymous function to call your handleClick function as follows:
<List.Item
onClick={() => this.handleClick(document.title)}
...etc
/>
You would then have:
handleClick = docTitle => {
console.log(docTitle);
};
If what you wanted was obtainable from event.target, you could just pass the reference of handleClick to the onClick i.e.
handleClick = event => {
console.log(e.target.innerText);
};
<List.Item
onClick={this.handleClick}
/>

Resources