How to write a useComponent custom hook in React?

Viewed 181

I want to create a custom hook useComponent which returns a JSX.Element that will be rendered elsewhere.

I have tried this:

import { useState} from 'react';

const useComponent = () => {

  const [value, setValue] = useState('');

  const c = () => {
    return <>
    <p>Component</p>
    <input value={value} onChane={(e) => setValue(e.target.value)} />
    </>
  }

  return {
    c,
    value,
  }

}


export default function App() {

  const {c: C} = useComponent();

  return (
    <div className="App">
      <C />
    </div>
  );
}

but it does not work. Once I try typing on input, nothing happens.

How can I achieve this ?

I know it might be a bad practice to do such a thing, but the reason I want this is to be able to open a global dialog and pass the c component as children to the <Dialog /> component so I can both render c inside the dialog's body and also have access to the [value, setValue] state. So my use case would be something like:

[EDIT]

I also add the whole logic with dialog:

import { createContext, useContext, useState } from "react";

const Test = ({ value, setValue }) => {
  return (
    <>
      <p>Component</p>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
    </>
  );
};

const useComponent = () => {
  const [value, setValue] = useState("");

  return {
    element: <Test value={value} setValue={setValue} />,
    value
  };
};

const DialogCTX = createContext({});

export function DialogProvider(props) {
  
  const [component, setComponent] = useState(null);

  const ctx = {
    component,
    setComponent
  };

  return (
    <DialogCTX.Provider value={ ctx }>
      {props.children}
    </DialogCTX.Provider>
  );
}

export const useDialog = () => {
  const {
    component,
    setComponent,
  } = useContext(DialogCTX);

  return {
    component,
    setComponent,
  }
};

const Dialog = () => {

  const { component } = useDialog();
  return <div>
    <p>Dialog</p>
    {component}
    </div>
}

const Setter = () => {
  const {element, value} = useComponent();
  const {setComponent} = useDialog();

  return <div>
    <p>Setter component</p>
    <p>{value}</p>
    <button onClick={() => setComponent(element)}>Set</button>
    </div>
}
export default function App() {
  
  return <div className="App">
    <DialogProvider>
      <Setter />
      <Dialog />
    </DialogProvider>
  </div>;
}
1 Answers

As you said you want to return a JSX.Element but you actually returning a new component (a new function) every time your hook runs. So you could achieve your goal if you actually declare your component outside your hook and return the rendered one. Here is a working example:

import { useState } from "react";

const Test = ({ value, setValue }) => {
  return (
    <>
      <p>Component</p>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
    </>
  );
};

const useComponent = () => {
  const [value, setValue] = useState("");

  return {
    element: <Test value={value} setValue={setValue} />,
    value
  };
};

export default function App() {
  const { element } = useComponent();

  return <div className="App">{element}</div>;
}
Related