Giorgi Gvimradze
Giorgi Gvimradze

Reputation: 2129

Render React component with onClick and execute props function

What I try to acheive is NOT JUST render a component by onClick but also pass a dynamic function to the component.

Restrictions
In the project there is Dialog component from the FluentUI. This dialog has open property which is boolean and can be triggered by that. On one of the pages we lots of buttos which need to trigger the <Dialog/> this is why I can't just paste this sample code and change open state:

<Dialog
    cancelButton={()=>{setOpen(false)}}
    confirmButton={handleOnclick}
    header="Action confirmation"
    open={open}
/>

Requirements:

Simplified code
Here is the link to the sandbox. Here how it looks:

import React from "react";

export default function App() {
  const [visible, setVis] = React.useState(false)
  const Component = ({id,execute}:{id:number,execute?:any}) =>{
    //this part here should stayed untouched
    return visible ? <button onClick={execute(id)}></button> : <></>
  }

  const handleClick = (id: number,execute: any)=>{
    setVis(true)
    return <Component id={id} execute={execute}/>
  }

  return (
    <div className="App" >
      <button onClick={()=>handleClick(1,(id:number)=>{console.log(id)})}>First</button>
      <button onClick={()=>handleClick(2,(id:number)=>{console.log(id)})}>Second</button>
    </div>
  );
}

The code tries to replicate my situation but more simple way. I'm trying to render a component which has a button to execute some code passed to it.
Problem: Component never displays

Upvotes: 1

Views: 114

Answers (1)

asgerhallas
asgerhallas

Reputation: 17714

EDIT: React docs discourages storing components in state - not giving a reason though in the docs I found. So it's probably better just to store { id, execute } in state an pass them along to the component when rendering.

Like this:

import React from "react";

export default function App() {
  const [visible, setVis] = React.useState<any>()
  const Component = ({id,execute}:{id:number,execute?:any}) =>{
    //this part here should stayed untouched
    return <button onClick={() => execute(id)}></button>
  }

  const handleClick = (id: number, execute: any)=>{
    setVis({ id, execute })
  }

  return (
    <div className="App" >
      { visible ? <Component id={visible.id} execute={visible.execute} /> : <div /> }
      <button onClick={()=>handleClick(1,(id:number)=>{console.log(id)})}>First</button>
      <button onClick={()=>handleClick(2,(id:number)=>{console.log(id)})}>Second</button>
    </div>
  );
}

First revision of answer below:

When you call handleClick you return the component, you want rendered - but you don't actually render it. To render it you have to return the component as a part of the App-component's return value.

Like e.g.

  return (
    <div className="App" >
      <Component /* missing arguments here */ />
      <button onClick={()=>handleClick(1,(id:number)=>{console.log(id)})}>First</button>
      <button onClick={()=>handleClick(2,(id:number)=>{console.log(id)})}>Second</button>
    </div>
  );

Now you have two different "types of visible" components: Two different id's with different callbacks dependent on which button was clicked originally. And you have to store this information in state too. One way to do that is store these parameters as an object in state instead of just true/false.

But you can also just store the Component in state, like:

import React from "react";

export default function App() {
  const [visible, setVis] = React.useState<any>()
  const Component = ({id,execute}:{id:number,execute?:any}) =>{
    //this part here should stayed untouched
    return <button onClick={() => execute(id)}></button>
  }

  const handleClick = (id: number, execute: any)=>{
    setVis(<Component id={id} execute={execute} />)
  }

  return (
    <div className="App" >
      { visible ? visible : <div /> }
      <button onClick={()=>handleClick(1,(id:number)=>{console.log(id)})}>First</button>
      <button onClick={()=>handleClick(2,(id:number)=>{console.log(id)})}>Second</button>
    </div>
  );
}

Now I did have to change the part you wanted "untouched" for two reasons:

First of all, your onclick-callback was executed immediately upon rendering, so I wrapped it in a function. Secondly, the check for visible would always be true here, given that we just rendered the component - because it was truthy.

Upvotes: 2

Related Questions