Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS TypeScript pure component with children

I have a TSX component that is a container wrapping its children like this:

import * as React from "react";

interface IProps extends React.Props<Box> {
}

interface IState {
}

export class Box extends React.Component<IProps, IState> {
    render() {
        return (
            <div>
                <h3>Check this out:</h3>
                {this.props.children}
            </div>);
    }
}

I also have some pure components, like this one:

// ReSharper disable once InconsistentNaming - React components must start with a capital letter.
export function LabelTest(props: { label: string }) {
    return (
        <div style={{ display: "flex" }}>
            <label style={{ flex: "1 0 0" }}>{props.label}</label>
            <div style={{ width: "auto" }}>
                This is a pure component.
            </div>
        </div>);
}

I can't for the life of me figure out how to make a pure container component though. The declaration looks alright and shows no errors:

// ReSharper disable once InconsistentNaming - React components must start with a capital letter.
export function LabelBox(props: { label: string, children: React.ReactNode }) {
  return (
    <div style={{ display: "flex" }}>
      <label style={{ flex: "1 0 0" }}>{props.label}</label>
      <div style={{ width: "auto" }}>
        {props.children}
      </div>
    </div>);
}

children is React.ReactNode because that's what it is in React.Props<X>.

When used I get TS2324 Property 'children' is missing in type 'IntrinsicAttributes & { label: string; children: ReactElement | string | number | {} | (Reac....

// ReSharper disable once InconsistentNaming - React components must start with a capital letter.
export function LabelNumberInput(props: { label: string, value: number }) {
  return (
    <LabelBox label={props.label}>
      <input type="number" value={props.value} />
    </LabelBox>);
}

I can't say something like the snippet down below because it will say Cannot find symbol 'LabelBox' in external module _.tsx (it's the file in which all of this code is). Doesn't matter if I put the function or the interface first, but it feels wrong overall anyway.

interface ILabelBoxProps extends React.Props<LabelBox> { label: string }
export function LabelBox(props: ILabelBoxProps) {
  return (
    <div style={{ display: "flex" }}>
      <label style={{ flex: "1 0 0" }}>{props.label}</label>
      <div style={{ width: "auto" }}>
        {props.children}
      </div>
    </div>);
}

What's the right way to make a pure TypeScript React component capable of taking children just like a full class component would? Is this possible? I don't think it should not be, it's still a stateless component and children are just a special kind of props, really the real problem seems to be the tooling here (Visual Studio) which doesn't map the TSX content node to the children prop.

like image 614
Tomáš Hübelbauer Avatar asked Jun 14 '16 21:06

Tomáš Hübelbauer


People also ask

How do you pass children in React typescript?

By invoking them between the opening and closing tags of a JSX element, you can use React children for entering data into a component. The React children prop is an important concept for creating reusable components because it allows components to be constructed together.

Can React components have children?

React Components and Children In React, a component can have one, many, or no children.

Can we use shouldComponentUpdate in pure component?

PureComponent implements shouldComponentUpdate() method to determine re-rendering with a shallow prop and state comparison for rendering performance optimization. Although overridden shouldComponentUpdate() method will work as intended, it is recommended to extend React. Component instead because React.


2 Answers

What's the right way to make a pure TypeScript React component capable of taking children just like a full class component would

Sample 🌹:

interface PrimitiveProps extends React.HTMLProps<HTMLDivElement>{
   myAdditionalProp:any;
};

export const Content = (allProps: PrimitiveProps) => {
    const {myAdditionalProp, ...props} = allProps;
    return (
        <div data-comment="Content" {...props}/>;
    );
};

Of course you are free to do whatever with myAdditionalProp and put in more props into PrimitiveProps if you want to. Just be sure to remove them from allProps before passing it throught. Note that children get passed as its a part of allProps and thus props.

like image 194
basarat Avatar answered Oct 25 '22 03:10

basarat


Here is the complete example:

interface PaginationItemProps extends React.HTMLProps<HTMLDivElement> {
}

const PaginationItem = (props: PaginationItemProps) => {
   return <div>{props.children}</div>;
}

class Pagination extends React.Component<PaginationProps> {
  render() {
    return <div>          
        <PaginationItem>CHILDREN RENDER</PaginationItem>
    </div>
  }
}
like image 41
Xalisys Avatar answered Oct 25 '22 03:10

Xalisys