Higher Order Component in React [duplicate] - reactjs

i am learning react at the moment. this is the link with the code - http://redux.js.org/docs/basics/ExampleTodoList.html
I am having a bit of difficulty understanding what's going on in this part of the code
const Link = ({ active, children, onClick }) => {
if (active) {
return <span>{children}</span>
}
return (
<a
href="#"
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
I am having the most difficulty understand this snippet
return (
<a
href="#"
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
What does {children} mean here?
What does it do?
and what does this do?
children: PropTypes.node.isRequired,
what is meant by node in the above line?

When you use a Custom component, like
<MyComponent>Hello World</MyComponent>
Whatever you write between the tags (in above case Hello World) is passed to the component as a children prop.
So when write your component like
const Link = ({ active, children, onClick }) => {
You are destructuring the props and getting only active, children and onClick from the props passed to the component
Consider for example, you call the Link component like
<Link active="true" onClick={someFunc} style={{color: 'orange'}}>Hello</Link>
Then amongst all the props i.e active, onClick, style, children, you will only be accessing active, onClick,children in the component.
For your second question:
and what does this do?
children: PropTypes.node.isRequired,
So here PropTypes is a way of performing a typeCheck on the props that are passed to the component. It is being imported from the react-proptypes package.
So
children: PropTypes.node.isRequired
makes the prop children to be required. So if your render your component like
<Link />
It will not pass the type check and hence you need to do
<Link>Text</Link>

children: PropTypes.node.isRequired,
this is just the type checking of the react proptypes. Refer https://facebook.github.io/react/docs/typechecking-with-proptypes.html for more details how type checking works.
According to the example this says that the prop children is required and is of type node. This type node refers to anything that can be rendered. which is then included within the tag in your rendering.

If you care about the types of your props, you'd need to use React.PropsWithChildren, e.g.
type Props = React.PropsWithChildren<{
name: string; // strongly typed!
myProp: string;
}>;
export function MyComponent({ name, myProp, children }: Props) {
return (
<>
<div>{name}</div>
<div>{myProp}</div>
{children != null && children}
</>
)
}

Related

How to prevent a prop from being passed to the extended component?

My question is similar to this one, however I need to type a component. I have tried a multiple ways, but still getting errors.
I am not sure if the problem is in my understanding, or am I doing something wrong? Here is what I tried:
First approach works correctly but throws warning: React does not recognize the isActive prop on a DOM element
Second and Third throws TS error: No overload matches this call.
import * as React from "react";
import TextareaAutosize, { TextareaAutosizeProps } from "react-textarea-autosize";
import styled from "styled-components";
interface Props {
isActive?: boolean;
}
interface ExtendedProps extends Props, TextareaAutosizeProps {}
const FirstTry = styled(TextareaAutosize)<Props>`
color: ${({ isActive }) => (isActive ? "red" : "blue")};
`;
const SecondTry = styled(({ isActive, ...rest }: ExtendedProps) => (
<TextareaAutosize {...rest} />
))`
color: ${({ isActive }) => (isActive ? "red" : "blue")};
`;
const ThirdTry = ({ isActive, ...rest }: ExtendedProps) => {
const Foo = styled(TextareaAutosize)<TextareaAutosizeProps>`
color: ${isActive ? "red" : "blue"};
`;
return <Foo {...rest} />;
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<FirstTry isActive minRows={3} />
{/* FirstTry - Warning: React does not recognize the `isActive` prop on a DOM element */}
<SecondTry isActive minRows={3} />
<ThirdTry isActive minRows={3} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Link to sandbox:
https://codesandbox.io/s/zen-cdn-lp8pl?file=/src/App.tsx
Your second approach looks good except for one small thing that causes the error: ref prop of TextareaAutosizeProps collides with ref prop of your styled component.
ref (and key for that matter) is a tricky "prop" - you pass it to a component just like any other props yet it does not appear in your props (if you log them for example), it is handled differently.
If you look at your second example:
const SecondTry = styled(({ isActive, ...rest }: ExtendedProps) => (
<TextareaAutosize {...rest} />
))`
color: ${({ isActive }) => (isActive ? "red" : "blue")};
`;
You can see that you are not styling TextareaAutosize but the anonymous function component ({ isActive, ...rest }: ExtendedProps) => .... If you pass ref to your SecondTry component it will not appear in your props ({ isActive, ...rest }: ExtendedProps). Yet when extending TextareaAutosizeProps you are also saying that there will be such a prop and it will be of type HTMLTextAreaElement.
So I can think of two solutions depending on your needs:
If you don't need the ref prop on your SecondTry you can just omit it from your props:
interface ExtendedProps extends Props, Omit<TextareaAutosizeProps, 'ref'> {}
If you need it though you will need to use React.forwardRef function (see more about that here).

How to add optional React components inside another React component via props

What is the best approach for adding optional components inside a component?
I have one like this:
type Props = {
children: React.Node,
title?: string,
/**May add any component next to the header. Should be inside a fragment. */
headerComponents?: React.Node,
className?: string,
}
export const Content = ({ children, className, title, headerComponents }: Props) => (
<div className={`page-content ${className}`}>
<div className='page-content-header'>
{
title && (
<h2 className='content-title'>{title}</h2>
)
}
{
headerComponents && (
<div className='page-header-right'> {headerComponents} </div>
)
}
</div>
{children}
</div>
);
The headerComponent acts as a prop that can receive another component, like this:
<Page.Content
headerComponents={
<>
<Button>First</Button>
<Button>Second</Button>
</>
}
title='Example title'
>
<div>Example text</div>
</Page.Content>
And it works. But I'm wondering if there's a better approach.
This approach looks fine to me. It is readable and intuitive to pass props to the component, followed by conditionally rendering the optional components based on the values of the props.

Nesting a TypeScript React component inside another

So I'm trying to nest a TypeScript React component within another, but it complains about types. It would seem it wants me to add all of my props into the parent interface?
Is there a way of doing this without having to have all my types listed in the child component interface, but then also having to add the types to the parent component interface?
Note: I am using Styled Components in the example below
interface IField {
children: React.ReactNode
}
export function Field({ children, htmlFor, label, required, ...props }: IField) {
return (
<FormField {...props}>
<Label htmlFor={htmlFor} label={label} required={required}/>
{children}
</FormField>
)
}
interface ILabel {
htmlFor: string
label: string
required?: boolean
}
export function Label({ htmlFor, label, required }: ILabel) {
return (
<FormLabel htmlFor={htmlFor}>
{label}
{required && (
<Required />
)}
</FormLabel>
)
}
Error I get:
Type '{}' is missing the following properties from type 'ILabel': htmlFor, label
Thanks for any help in advance!
To avoid adding the properties from the child interface back to the parent component interface you can use extends (see TypeScript inheritance documentation).
After that, you can pass the props from the parent to the child component by using {...props} deconstructor on the child component.
And finally, if you are using TypeScript might as well use React.FunctionComponent typing to avoid having to manually type children.
You can check at this simplified working example:
https://stackblitz.com/edit/react-stackoverflow-60228406
I tried to adapt your snippet below...
import React from 'react';
interface IField extends ILabel {
}
export const Field: React.FunctionComponent<IField> = (props) => {
return (
<FormField>
<Label {...props} />
{props.children}
</FormField>
)
};
interface ILabel {
htmlFor: string
label: string
required?: boolean
}
export const Label: React.FunctionComponent<ILabel> = (props) => {
return (
<FormLabel htmlFor={props.htmlFor}>
{props.label}
{props.required && (<Required />)}
</FormLabel>
)
};

Infer type of props based on component passed also as prop

Is it possible to infer correct types for props from an unknown component passed also as a prop?
In case of known component (that exists in current file) I can get props:
type ButtonProps = React.ComponentProps<typeof Button>;
But if I want to create a generic component Box that accepts a component in as prop and the component's props in props prop. The component can add some default props, have some behavior, it doesn't matter. Basically its similar to higher-order components, but its dynamic.
import React from "react";
export interface BoxProps<TComponent> {
as?: TComponent;
props?: SomehowInfer<TComponent>; // is it possible?
}
export function Box({ as: Component, props }: BoxProps) {
// Note: it doesn't have to be typed within the Box (I can pass anything, I can control it)
return <Component className="box" title="This is Box!" {...props} />;
}
function MyButton(props: {onClick: () => void}) {
return <button className="my-button" {...props} />;
}
// usage:
function Example() {
// I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type.
return (
<div>
<Box
as={MyButton}
props={{
onClick: () => {
console.log("clicked");
}
}}
>
Click me.
</Box>
</div>
);
}
requirements:
must work without passing the generic type (is it possible?), because it would be used almost everywhere
must work with user-defined components (React.ComponentType<Props>)
would be great if it worked also with react html elements (a, button, link, ...they have different props), but not necessary
You can use the predefined react type ComponentProps to extract the prop types from a component type.
import React from "react";
export type BoxProps<TComponent extends React.ComponentType<any>> = {
as: TComponent;
props: React.ComponentProps<TComponent>;
}
export function Box<TComponent extends React.ComponentType<any>>({ as: Component, props }: BoxProps<TComponent>) {
return <div className="box" title="This is Box!">
<Component {...props} />;
</div>
}
function MyButton(props: {onClick: () => void}) {
return <button className="my-button" {...props} />;
}
// usage:
function Example() {
// I want here the props to be typed based on what I pass to as. Without using typeof or explicitly passing the generic type.
return (
<div>
<Box
as={MyButton}
props={{ onClick: () => { } }}
></Box>
</div>
);
}
Playground Link
Depending on you exact use case the solution might vary, but the basic idea is similar. You could for example turn the type around a little bit and take in the props as the type parameter for the BoxProps. That way you can constrain the component props to have some specific properties you can supply inside the Box component:
export type BoxProps<TProps extends {title: string}> = {
as: React.ComponentType<TProps>;
} & {
props: Omit<TProps, 'title'>;
}
export function Box<TProps extends {title: string}>({ as: Component, props }: BoxProps<TProps>) {
return <div className="box" title="This is Box!">
<Component title="Title from box" {...props as TProps} />;
</div>
}
Playground Link
If you want to take in intrinsic tags, you can also add keyof JSX.IntrinsicElements to the TComponent constraint:
export type BoxProps<TComponent extends React.ComponentType<any> | keyof JSX.IntrinsicElements> = {
as: TComponent;
props: React.ComponentProps<TComponent>;
}
export function Box<TComponent extends React.ComponentType<any>| keyof JSX.IntrinsicElements>({ as: Component, props }: BoxProps<TComponent>) {
return <div className="box" title="This is Box!">
<Component {...props} />;
</div>
}
Playground Link

In react how can I use flow to type check only certain props, but ignore ANY others?

I'm creating some button components and I have some custom props that I need and want them checked by flow. But as they are buttons I would also like any other props from the HTML button elements but don't want to type check them all.
Is there any way in react or maybe with an npm package to let me type check my new custom props and let the component receive any other ones? Or maybe just restricted to the HTML defined ones?
You should just be able to pass the rest of the props down without putting type annotations for it.
Example:
import React, { type Node } from 'react'
type Props = {
primary?: boolean,
children: Node
}
function Button({ primary, children, ...props }: Props) {
return (
<button
className={primary ? 'is-primary' : ''}
{...props}
>
{children}
</button>
)
}
export default Button
Usage:
function App() {
return (
<div>
<Button primary onClick={() => console.log('clicked!')}>
Click Me
</Button>
</div>
)
}
You can also check it out on flow.org/try.

Resources