Adding additional props to a component passed as a prop - reactjs

I am using React Table along with React Custom Scrollbars in a react-redux application. To connect these two I need to override the TbodyComponent in react table such that I can wrap the default tbodycomponent with the scrollbars and pass additional props to tweak rendering. Here's some stripped down code:
import React from 'react'
import ReactTable from 'react-table'
import {ReactTableDefaults} from 'react-table'
import { Scrollbars } from 'react-custom-scrollbars'
const TableBody = props => {
//get additional props beyond just props.children here
const {autoHeight} = props
return (
<Scrollbars
style={{
height: '100vh'
}}
>
<ReactTableDefaults.TbodyComponent>
{props.children}
</ReactTableDefaults.TbodyComponent>
</Scrollbars>
)
}
const Table = props => {
//props stuff would go here
return (
<div className="react-table-wrapper">
<ReactTable {...props}
TbodyComponent={TableBody} //this works
//TbodyComponent={(props) => {return (<TableBody autoHeight={props.autoHeight} children={props.children} />)}} //this doesn't
data={data}
columns={columns}
...
/>
</div>
)
}
I'm guessing I'm not understanding the proper way to pass a component in the TbodyComponent property, props.children, or something along those lines. This method just ends up looping forever.
In this example, how could I get the autoHeight prop to pass?
Update: Experimented with createElement and cloneElement and still receive the 130 error.

The solution to this was to convert the TableBody stateless component into a full component, that is
class TableBody extends React.Component {
instead of
const TableBody = props => {
That's what React-Table was expecting.

Is there a reason why this wouldn't work?
TbodyComponent={<TableBody autoHeight={props.autoHeight} />}
Also, I don't think you need to pass props.children - that should happen by default.
For reference, I looked at the answer provided here to a similar question: https://stackoverflow.com/a/39655113/8060919

In the case of react-table you could pass the props via the getTbodyProps. It must be a function returning the props as an object.
<ReactTable TbodyComponent={TableBody} getTbodyProps={()=>({autoHeight})}/>
See in the code

Try this
<ReactTable TbodyComponent={v => <DropTbody test={null} />}

Related

backgroundColor and onClick Wont Take Effect React JS In Basic React App

I am going through a React Learning Textbook that is explaining me about Hooks. The hooks part is working fine.
The author is illustrating how to customize Hooks with some easy to use style and related components.
I have the following code.
import React from 'react';
// import logo from './logo.svg';
import './App.css';
import StarRating from './components/StarRating';
import StarRating2 from './components/StarRating2';
import Headline from './components/Headline';
function App() {
return (
<article>
<Headline/>
<StarRating/>
<StarRating2
style={{ backgroundColor: "blue" }}
onClick={e => alert(" click")}
/>
</article>
);
}
export default App;
The component code is like this.
import { useState } from "react";
import React from "react";
import { FaStar } from "react-icons/fa";
const createArray = length => [...Array(length)];
const Star = ({ selected = false, onSelect = f => f }) => (
<FaStar color={selected ? "red" : "grey"} onClick={onSelect} />
);
const numberOfStarts = 10;
const numberDefaultState = 7;
function StarRating2({ totalStars = numberOfStarts })
{
//will hold the user’s rating
// create this variable by adding the useState hook directly to the StarRating component:
const [selectedStars, setSelectedStars] = useState(numberDefaultState);
return (
<>
<p>
This is Star Rating 2 - and it has some imporvements
</p>
{createArray(totalStars).map((n, i) => (
<Star
key={i}
selected={selectedStars > i}
onSelect= {
() => {
setSelectedStars(i + 1);
}
}
/>
))}
<p>
{selectedStars} of {totalStars} stars
</p>
</>
);
}
export default StarRating2;
Unfortunately, neither does the component display change its back ground color. Nor does it respond to a click. The app continues to run with no errors or anything. and I can see that the style properties set are visible in the component tree in the react developer tools in Firefox. So, the code is reflecting on the app for sure.
I am in the 6th chapter now, and so far, every chapter code has worked exactly as it is in the book. This one though, is not. I am unsure if this a wrong code (and perhaps, I should reach out to the author) or this is something that is no longer allowed and the book is simply out of date.
StarRating2 is a React component, you are passing couple of props to StarRating2 but you aren't using those props inside StarRating2 component. CSS styles and event handlers work on native DOM elements.
What you need to do is make use of the props that are passed to StarRating2 in from App component. You can apply the styles prop on the wrapper element that wraps all the JSX code of StarRating2 component and use onClick prop on the element which should react to the click event in some way.
To apply the background color in StarRating2 component, wrap the JSX code in a wrapper element, for example a div and then use the value of style prop on this wrapper element.
function StarRating2({ totalStars = numberOfStarts, style }) {
...
return (
<div style={style}>
...
</div>
);
}
To use the click handler, you will need to use the onClick prop and add it on any native DOM element.
You need to spread the props from the parent to a native react component , styles and eventListeners can only be attached to native components like div , button etc , if the Star component supports adding color and eventListeners through its , you can do ... rest in props and spread it to star component , if you need any help , send me a codesandbox , I will explain in that

Spreading a component's props across two different child components in React

I am trying to create a custom email input component (for a form) that wraps a Material-UI TextField component inside a custom gridding component that I made. Ideally, I would like to be able to pass any TextField prop I want into this component and have it applied to the inner TextField component by spreading the props, but I also would like to be able to pass any props for the custom gridding component and apply them to the grid component also via spreading.
Example (where variant is a TextField prop and width is a CustomGrid prop):
// CustomEmailField.tsx
...
export const CustomEmailField: React.FC<TextFieldProps & CustomGridProps> = (props) => {
return(
<CustomGrid {...props as CustomGridProps}>
<TextField {...props as TextFieldProps} />
</CustomGrid>
);
};
// index.tsx
...
const App = () => {
return(
<>
<h1>Enter your email</h1>
<CustomEmailField variant={'outlined'} width={2} />
</>
);
};
However, when spreading the props for the gridding component, I get an error message saying that the TextField props (variant in this example) do not exist for this gridding component, and likewise that the gridding component's props (width in this example) don't exist for the TextField component.
What would be a good way to solve this issue so that I can still have flexibility over the props I pass in to each (child) component without having to hardcode what props can be accepted by the email (parent) component?
Just create a new props type.
export type CustomEmailFieldProps = {
textField: TextFieldProps;
customGrid: CustomGridProps;
}
export const CustomEmailField: React.FC<CustomEmailFieldProps> = ({textField, customGrid}) => {
return(
<CustomGrid {...customGrid}>
<TextField {...textField} />
</CustomGrid>
);
};
To use just create an object of the props you want to pass to each.
// index.tsx
...
const App = () => {
return(
<>
<h1>Enter your email</h1>
<CustomEmailField textField={{variant: 'outlined'}} customGrid={{width: 2}} />
</>
);
};

Sending a React.FunctionComponent<React.SVGProps<SVGSVGElement>> as a prop to another component

I'm attempting to import a React functionComponent from an SVG and then send that to another component as a prop to render that svg. With the setup below, this compiles fine, but eventually crashes when trying to render the svg in browser with:
Error: Objects are not valid as a React child (found: object with keys {$$typeof, render}). If you meant to render a collection of children, use an array instead.
Classes below are simplified. But the gist of what I'm trying to do is:
In overlay.tsx:
import { ReactComponent as icon } from "/icon.svg";
import CustomItem from "/customItem";
const Overlay: React.FC<OverlayProps> = () => {
return (
<div>
<CustomItem icon={icon}/>
</div>
);
export default Overlay;
}
and in customItem.tsx:
import React from "react";
export interface CustomItemProps {
icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = ({icon}) => {
return (
<div>
{icon}
</div>
);
};
export default ApplicationsDropdownItem;
I assume my problem is somewhere around the syntax of {icon}, but I can not for the life of me find out what I'm suppose to use instead.
Answer
The icon you are importing is a component, therefore it must be called to render the JSX.
<Icon {...props}/> (correct) or {Icon(props)} (not recomended)
Since it is a component, you should also name it Icon and not icon.
Take a look at this blog post that explains SVGR.
TL;DR - Best approach for rendering components
A. Call the component in your render method with component syntax <MyComponent/> not MyComponent().
B. Instantiate your component as a variable, and pass that to your render method's JSX block.
More info
#DustInCompetent brought to light the issue of calling a component as a function inside a JSX block.
As explained here and here, that will lead to react not registering a components hooks and lead to state and other problems.
If you are implementing a High Level Component (HOC), then you should not call a component within the render method (return statement in functional components), as this leads to problems for similar registration issues of the component.
import React from "react";
import { ReactComponent as SampleIcon } from "/sample_icon.svg";
export interface CustomItemProps {
Icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
}
const CustomItem: React.FC<CustomItemProps> = (props) => {
const Temp = props.Icon as React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
return (
<div>
<Temp/>
</div>
);
};
<CustomItem Icon={SampleIcon}/>
I think you should use <Icon /> instead of {icon} because it's a component.

Using composition to split component into base and child in React

I have the following component that is a wrapper around ag-Grid:
<div className="ag-theme-material" style={{height: "100%"}}>
<AgGridReact
pagination
paginationPageSize={this.props.Size}
columnDefs={this.props.Col}
rowData={this.props.Row}
/>
</div>
I want to create a separate wrapper but without the pagination feature. As such I want a base class with the common features from which the two children (with and without paging) can inherit from. I know inheritance is not used much in React, so I'm wondering how to achieve the same effect using composition. Thanks!
you can use the compound components pattern, where you provide basic features for your component, and the user can use more features as he preferred.
the code would be something like this:
import React, { Component } from 'react'
import ReactDom from 'react-dom'
class AgGridReact extends Component {
static Paginator = (props list) => (...) // some jsx that represents the paginator
render() {
return <>
/** your grid jsx code here **/
// other features code
React.Children.map(this.props.children, child =>
React.cloneElement(child, {
// props you need to pass for the components
}),
)
}
</>
}
// the usage of this component will be:
<AgGridReact {...props}>
// the user can git rid of this if without paginator
<AgGridReact.Paginator />
</AgGridReact>

React render array of components

Quick question. Anyone know how to render an array of components? Trying to make it easier for a developer to alter a particular component. (It's like a dashboard).
Component list file
import React from 'react';
export default [
<ComponentOne/>
<ComponentTwo/>
];
Dashboard Component
import React from 'react';
import components from './../../components';
export default class Dashboard extends React.Component
{
render = () => {
//Want to render the array of components here.
return (
<div className="tile is-parent">
{components}
</div>
);
};
}
The issue is I have an array of components that I need to add a key to. However! I can't seem to add a key to the component as well, not sure how to explain it really so here's the code I've tried:
{components.map((component, key) => (
<component key={key}/>
}
If I do the above I get no 'you must apply a key' errors however nothing renders? And I'm guessing it's because 'component' doesn't exist or something weird along those lines.
I've also tried component.key = key; but it doesn't let me do that on this type of Object apparently?
My fallback I suppose is to return a shorthand function instead of an array but I like the array for some reason? Seems simpler for juniors.
Have you consider using the new React Fragments? (in v16)
This would be the simplest solution as it would by pass the whole array/key issue.
If you need to pass key, then I'd suggest to simply require the components to have the keys. This is how React works, so I wouldn't suggest you to hide this behavior behind an interface that might not be predictable.
If you really need to do this, then you can use React.cloneElement to clone the element and inject new properties:
React.cloneElement(element, { key: 'foo' });
If you’re always going to want to render all the components in your components file then you’re probably better off wrapping them in a React.Fragments tag.
Best practise is just to export this as a simple function that returns the components rather than as a constant.
So...
const Components = props => {
return (
<React.Fragment>
<ComponentOne/>
<ComponentTwo/>
</React.Fragment>
)
}
export default Components
That allows you to put multiple components next to each other without a DOM element containing them.
You should then just be able to render that by using it as a normal component and it’ll render all of them, so just import it then...
<Components />
Otherwise, if you want to treat them like an array, you have a function for free on the React object you’ve imported...
React.Children.toArray(arrayOfComponents)
You pass it an array of components (like in your original question) and it allows you to sort and slice it if you need to then you should be able to just drop it in the return of your render function
Following up with my comment, you should be doing this instead:
{components.map((component, index) => (
<span key={index}>
{ component }
</span>
}
With React 16, you can use React.Fragment:
{components.map((component, index) => (
<React.Fragment key={index}>
{ component }
</React.Fragment>
}
All of these answers are almost right. Just remove the <../> from your exports:
export default [
ComponentOne,
ComponentTwo,
]
And in the other file use .map():
export default class Dashboard extends React.Component {
render = () => (
<div className="tile is-parent">
{components.map((Component, key) => (<Component key={key} />))}
</div>
)
}
Also note that if you wanted to use Fragment like others suggested you can just write <>...</> instead.
It's pretty easy, just wrap your component into div and pass key there as i did below:
const Example = ({components}) => (
<div>
{components.map((component, i) => <div key={i}>{component}</div>)}
</div>
)
Worked example
Actually you are exporting array of elements from the file. One way is to export array of component and render them like
import React from 'react';
export default [
ComponentOne
ComponentTwo
];
// Then following will work
{components.map((Component, key) => (
// Remember to make first letter capital (in this case "c")
<Component key={key}/>
}
The other way is to wrap the component in div like this
import React from 'react';
export default [
<ComponentOne/>
<ComponentTwo/>
];
// Then wrap in div
{components.map((component, key) => (
<div key={key}>
{component}
</div>
}
You can also do like that :
{components.map(component => component)}
It mappes your components to display them one by one.
Just to expand a bit more to the accepted answer. If you have an array of elements elementArray and you would like to pass props to it like callbacks etc. you would do something like this-
To create the component array somewhere-
elementArray.push(<MyComponent/>);
And then in render-
<div>
{
elementArray.map((element,i) => {
React.cloneElement(element, { key: i, onClick: () => myOnclick(i)})
})
}
</div>
The second argument to React.cloneElement is an object of all the props that you would pass to the component at the time of render.
if you are using react version above 16 you can create your list of component like that:
import React from 'react';
export const componentList = [ ComponentOne, ComponentTwo ];
and render them anywhere like that:
import React from 'react';
function SomeComponent() {
return (
<div>
{componentList.map((component, index) => (
<div key={index}> {component && component.render()}</div>
))}
</div>
);
}
If I put components inside an array, I usually do it like this:
const COMPONENTS = [
<Foo />,
<Bar />
]
[0, 1].map(item => {
return (
<React.Fragment key={item}>
{COMPONENTS[item]}
</React.Fragment>
)
})

Resources