Search code examples
javascriptreactjsecmascript-6notation

React dot notation component passing between components


How to pass Parent props boolean in to child component when using dot notation with functional component.

Component structure is

<Dropdown className="float-right">
  <Dropdown.Title>Open menu</Dropdown.Title>
  <Dropdown.Menu align="end">
    <Dropdown.Item className="hello mate" href="/dashboard">
      Dashboard
    </Dropdown.Item>
    <Dropdown.Item className="hello mate" href="/settings">
      Settings
    </Dropdown.Item>
    <Dropdown.Divider />
    <Dropdown.Item className="hello mate" onClick={signOut}>
      Sign out
    </Dropdown.Item>
  </Dropdown.Menu>
</Dropdown>

Child component <Dropdown.Title> has OnClick to open `<Dropdown.Menu>

const DropdownTitle = (props) => {
  const { children, className } = props;
  const [openDropdown, setOpenDropdown] = useState(false);
  const toggleDropdown = () => setOpenDropdown(!openDropdown);
  return (
    <button type="button" onClick={toggleDropdown}>{children}</button>
  );
};

How to pass the openDropdown prop to <Dropdown.Menu> from <Dropdown.Title>

Edit young-smoke-c9glwr

Looking for solution with managing with openDropdown.tsx which can avoid additional coding when using the component <Dropdown>


Solution

  • Modified code sandbox

    What you can do is create a context that wraps your parent Dropdown component using React.createContext. Then all the children of the Dropdown can receive the values that are passed through the context provider.

    Set the value prop of the Provider component to be the state of the dropdown, and the dropdown toggle function.

    import React, { useState, createContext, useContext } from "react";
    
    const DropdownContext = createContext();
    
    const Dropdown = (props) => {
      const { children, className } = props;
      const [openDropdown, setOpenDropdown] = useState(false);
      const toggleDropdown = () => setOpenDropdown(!openDropdown);
    
      return (
        <div
          className={`inline-flex flex-col relative ${className ? className : ""}`}
        >
          <DropdownContext.Provider value={{ openDropdown, toggleDropdown }}>
            {children}
          </DropdownContext.Provider>
        </div>
      );
    };
    

    Then, to consume your context values, use React's useContext hook in the components that need them.

    const DropdownMenu = (props) => {
      const { children, className, align } = props;
      const { openDropdown } = useContext(DropdownContext);
    
      return (
        <div
          {...props}
          className={`
          flex-col absolute mt-1 py-1 top-full bg-white text-base z-50 rounded shadow min-w-[180px] 
          ${align === "start" ? "left-0" : "right-0"}
          ${className}
          ${openDropdown ? "inline-flex" : "hidden"}
          `}
        >
          {children}
        </div>
      );
    };
    
    const DropdownTitle = (props) => {
      const { children, className } = props;
      const { toggleDropdown } = useContext(DropdownContext);
    
      return (
        <button
          onClick={toggleDropdown}
          type="button"
          className={`inline-flex items-center gap-x-1.5 rounded-md bg-white px-4 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 ${
            className ? className : ""
          }`}
        >
          {children}
          <svg
            className="w-4 h-4 ml-2"
            fill="none"
            stroke="currentColor"
            viewBox="0 0 24 24"
            xmlns="http://www.w3.org/2000/svg"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth="2"
              d="M19 9l-7 7-7-7"
            />
          </svg>
        </button>
      );
    };
    

    This solution will allow you to keep all the values incapsulated into one file. Similar to the bootstrap method you linked here: https://react-bootstrap.netlify.app/docs/components/dropdowns/