Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement react-bootstrap custom dropdown example in Typescript

I'm trying to implement the custom toggle drop-down example from the react-bootstrap page, in Typescript, using react functional components.

Here's my component code:


    import React from 'react';
    import Dropdown from 'react-bootstrap/Dropdown';
    import FormControl from 'react-bootstrap/FormControl';


    export const DropdownSelector =() => (
      <Dropdown>
        <Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components">
          Custom toggle
        </Dropdown.Toggle>

        <Dropdown.Menu as={CustomMenu}>
          <Dropdown.Item eventKey="1">Red</Dropdown.Item>
          <Dropdown.Item eventKey="2">Blue</Dropdown.Item>
          <Dropdown.Item eventKey="3" active>
            Orange
          </Dropdown.Item>
          <Dropdown.Item eventKey="1">Red-Orange</Dropdown.Item>
        </Dropdown.Menu>
      </Dropdown>
    )

    // The forwardRef is important!!
    // Dropdown needs access to the DOM node in order to position the Menu
    const CustomToggle = React.forwardRef(({ children, onClick }, ref) => (
      <a
        href=""
        ref={ref}
        onClick={(e) => {
          e.preventDefault();
          onClick(e);
        }}
      >
        {children}
        &#x25bc;
      </a>
    ));

    // forwardRef again here!
    // Dropdown needs access to the DOM of the Menu to measure it
    const CustomMenu = React.forwardRef(
      ({ children, style, className, 'aria-labelledby': labeledBy }, ref) => {
        const [value, setValue] = useState('');

        return (
          <div
            ref={ref}
            style={style}
            className={className}
            aria-labelledby={labeledBy}
          >
            <FormControl
              autoFocus
              className="mx-3 my-2 w-auto"
              placeholder="Type to filter..."
              onChange={(e) => setValue(e.target.value)}
              value={value}
            />
            <ul className="list-unstyled">
              {React.Children.toArray(children).filter(
                (child) =>
                  !value || child.props.children.toLowerCase().startsWith(value),
              )}
            </ul>
          </div>
        );
      },
    );

This fails to compile:


./src/components/helpers/dropdown-selector.tsx
TypeScript error in ./src/components/helpers/dropdown-selector.tsx(25,52):
Property 'onClick' does not exist on type '{ children?: ReactNode; }'. TS2339


What am I doing wrong?

Stackblitz sandbox version here. Using that editor, I see a bunch of type errors (although it does run); but the IDE I'm using to develop the app won't let me run it with those errors...

like image 322
Ade Avatar asked Dec 11 '22 00:12

Ade


2 Answers

I Think I did resolve your problem


import React, { useState } from "react";
import Dropdown from "react-bootstrap/Dropdown";
import FormControl from "react-bootstrap/FormControl";

interface IFruity {
  id: number;
  fruit: string;
  prefix: string;
  suffix?: string;
}

const fruits: IFruity[] = [
  { id: 1, fruit: "Apples", prefix: "How's about them " },
  { id: 2, fruit: "Pear", prefix: "A cracking ", suffix: "!" },
  { id: 3, fruit: "Oranges", prefix: "What rhymes with ", suffix: "?" },
  { id: 4, fruit: "Banana", prefix: "Fruit flies like a " },
  { id: 5, fruit: "Coconuts", prefix: "Oh what a lovely bunch of " },
  { id: 6, fruit: "Avocado", prefix: "Is an ", suffix: " even a fruit?" }
];

type CustomToggleProps = {
  children?: React.ReactNode;
  onClick?: (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {};
};

// The forwardRef is important!!
// Dropdown needs access to the DOM node in order to position the Menu
const CustomToggle = React.forwardRef(
  (props: CustomToggleProps, ref: React.Ref<HTMLAnchorElement>) => (
    <a
      href=""
      ref={ref}
      onClick={e => {
        e.preventDefault();
        props.onClick(e);
      }}
    >
      {props.children}
      <span style={{ paddingLeft: "5px" }}>&#x25bc;</span>
    </a>
  )
);

type CustomMenuProps = {
  children?: React.ReactNode;
  style?: React.CSSProperties;
  className?: string;
  labeledBy?: string;
};

// forwardRef again here!
// Dropdown needs access to the DOM of the Menu to measure it
const CustomMenu = React.forwardRef(
  (props: CustomMenuProps, ref: React.Ref<HTMLDivElement>) => {
    const [value, setValue] = useState("");

    return (
      <div
        ref={ref}
        style={props.style}
        className={props.className}
        aria-labelledby={props.labeledBy}
      >
        <FormControl
          autoFocus
          className="mx-3 my-2 w-auto"
          placeholder="Type to filter..."
          onChange={e => setValue(e.target.value)}
          value={value}
        />
        <ul className="list-unstyled">
          {React.Children.toArray(props.children).filter(
            (child: any) =>
              !value || child.props.children.toLowerCase().startsWith(value)
          )}
        </ul>
      </div>
    );
  }
);

export const DropdownSelector = () => {
  const [selectedFruit, setSelectedFruit] = useState(0);

  const theChosenFruit = () => {
    const chosenFruit: IFruity = fruits.find(f => f.id === selectedFruit);
    return chosenFruit
      ? chosenFruit.prefix + chosenFruit.fruit + (chosenFruit.suffix || "")
      : "Select a fruit";
  };

  return (
    <Dropdown onSelect={(e: string) => setSelectedFruit(Number(e))}>
      <Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components">
        {theChosenFruit()}
      </Dropdown.Toggle>

      <Dropdown.Menu as={CustomMenu}>
        {fruits.map(fruit => {
          return (
            <Dropdown.Item key={fruit.id} eventKey={fruit.id.toString()}>
              {fruit.fruit}
            </Dropdown.Item>
          );
        })}
      </Dropdown.Menu>
    </Dropdown>
  );
};


like image 168
Whiso Avatar answered Dec 31 '22 02:12

Whiso


I'm facing the same issue when defining a custom dropdown with react-bootstrap,

What I did to workaround this problem was

const CustomToggle = React.forwardRef((props: any, ref) => (
const { children, onClick } = props 
like image 41
NiicooR Avatar answered Dec 31 '22 04:12

NiicooR