Using composition to split component into base and child in React - reactjs

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>

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

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.

Adding additional props to a component passed as a prop

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} />}

Pattern for react components that require specific contained components

What's the idiomatic React way to write a component that nests specific child components? I know how to write a component that simply wraps props.children, like this example from React's own docs:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
But what if I wanted to create a pair of components that can be used like this:
<TabSet>
<Tab name="Mammals">
content for <b>warm blooded</b> creatures here
</Tab>
<Tab name="Fish">
content for <b>cold blooded</b> creatures here
</Tab>
</TabSet>
Here's my initial implementation of TabSet (using reactstrap), simplified to remove styling, selected-tab management, and other stuff not related to this question.
import React, {Fragment, Component} from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
export default class TabSet extends Component {
render(props) {
return (
<Fragment>
<Nav tabs>
{props.children.map((tab,i) =>
<NavItem key={i}>
<NavLink>
{ tab.name }
</NavLink>
</NavItem>
)}
</Nav>
<TabContent>
{props.children.map((tab,i) =>
<TabPane key={i} tabId={i}>
{ tab.children }
</TabPane>
)}
</TabContent>
</Fragment>
);
}
}
Where I'm stuck is how to implement the Tab component. Logically, it's part of the TabSet component-- it should never stand alone. And its implementation should be painfully simple because it doesn't actually do anything-- it's just a container for a name attribute and child elements.
So, here's a few questions:
Should I create a separate JS file for the Tab component, or is it so simple that I should just export it as part of the implementation of the TabSet component? If the latter, how?
In classes that use TabSet, will I need two import statements, or is there a way I can import both TabSet and Tab with one import, kinda like import React, {Fragment} from 'react' works ? If the latter, then how would the export statement look in TabSet.js?
Apologies for what's probably an obvious question-- I'm a newbie to both React and ES6.
If the component is only used in context of another component it is logical to put them both in same module and many libraries do that. The way to achieve this is use multiple export statements without default. You are allowed to use one export default and as many export statements as you need. Like this
export default class TabSet
...
export class Tab
and to import
import TabSet, {Tab} from './Tab'
The general syntax being
import defaultExport, { namedExport1, namedExport2 } from "module"
The syntax might seem a bit confusing here is the reference
Where you are using export default class ... you can actually export your own object here. With that in mind, you are able to something like:
const TabSet = props => (
<Your Tabset markup here>
)
const Tab = props => (
<Your Tab markup here>
)
export {
Tabset,
Tab
}
Doing it like this will allow you to import both components in the one line by doing:
import { Tabset, Tab } from 'wherever'
Now while this is one way to do it, and although you think Tab is quite simple, I still believe they belong in their own files. So just create the two class files for Tabset and Tab, but then make a third file called tabs.js or something. It should contain the link to both, like:
import Tabset from './tabset'
import Tab from './tab'
export {
Tabset,
Tab
}
This way you have designated files for each component, and you can import them as a single import.
Also for bonus, if you use the PropTypes ability of react, you can restrict the children of the Tabset to actually be your Tabs. Here is the overview, but as an example you can do something like:
// untested
static propTypes = {
children: PropTypes.arrayOf(PropTypes.instanceOf(Tab))
}
You will have to import the Tab component into the set component to do this.

How react-static-boilerplate make use of react's ref for mounting and umounting?

I quite new to react. What is ref={node => (this.root = node)} for in:
import React from 'react';
import Navigation from './Navigation';
import Link from '../Link';
import s from './Header.css';
class Header extends React.Component {
componentDidMount() {
window.componentHandler.upgradeElement(this.root);
}
componentWillUnmount() {
window.componentHandler.downgradeElements(this.root);
}
render() {
return (
<header className={`mdl-layout__header ${s.header}`} ref={node => (this.root = node)}>
<div className={`mdl-layout__header-row ${s.row}`}>
<Link className={`mdl-layout-title ${s.title}`} to="/">
Igene Logo Here
</Link>
<div className="mdl-layout-spacer"></div>
<Navigation />
</div>
</header>
);
}
}
export default Header;
( from react-static-boilerplate (RSB) )
and it's relationship to mount and unmounting?
I get the concept of ref based on reading this, but still find it quite hard to understand when it is used in RSB. Can someone enlighten me how it works and also it's relationship to the componentDidMount() and componentWillMount()
The following:
ref={node => (this.root = node)}
stores a reference to the DOM element that it is contained within. Your example sees it inside a header tag, which makes it possible to then manipulate the actual DOM element of the header by referencing this.root.
One of the main use cases for storing refs is that the element becomes available to other members of the same component class.
Examples include:
focusing on a particular form input within componentDidMount
or disabling input into a text field after another one has been filled (these are examples are quite rough, but hopefully you get the idea).

Resources