Home > Software engineering >  Possible to dynamically set onClick function of an element inside a ReactNode?
Possible to dynamically set onClick function of an element inside a ReactNode?

Time:01-01

So I have a function addAlert that will add a message to an array for display as a React Bootstrap alert. Most of these alerts are static text, but one of them includes an "undo the last action" link. I would like the function called by this "undo" link to clear the alert in addition to undoing the action. I have a clearAlert function available, but it takes an alert id that I do not have access to at the point in code where I am creating the alert content.

App.tsx

function App(props: AppProps){
[...]
    const [currentAlerts, setCurrentAlerts] = useState<Array<AppAlert>>([]);
[...]
function addAlert(msg: React.ReactNode, style: string, callback?: (id: string) => {}) {
      console.log("add alert triggered", currentAlerts);
      let id = uuidv4();
      let newTimeout = setTimeout(clearAlert, timeoutMilliseconds, id);
      let newAlert = {
          id: id,
          msg: msg,
          style: style,
          callback: callback,
          timeout: newTimeout
      } as AppAlert;
      let test = [...currentAlerts, newAlert];
      console.log("after add alert", test);
      setCurrentAlerts(test);
    }
    function clearAlert(id: string){
      console.log("clear alert triggered", currentAlerts);
      let timeout = currentAlerts.find(t => t.id === id)?.timeout;
      if(timeout){
          clearTimeout(timeout);
      }

      let newCurrent = currentAlerts.filter(t => t.id != id);
      console.log("after clear alert", newCurrent);
      setCurrentAlerts(newCurrent);
    }
[...]
    return (
      <>
          <div className={ "app-container "   (error !== undefined ? "err" : "") } >
              { selectedMode === "Current" &&
                <CurrentItems {...currentItemsProps} />
              }
              { selectedMode === "History" &&
                <History {...historyProps } />
              }
              { selectedMode === "Configure" &&
                <Configure {...globalProps} />
              }
          </div>
          <div className="footer-container">
              {
                currentAlerts.map(a => (
                  <Alert variant={a.style} dismissible transition={false} onClose={a.callback}>
                    {a.msg}
                  </Alert>
                ))
              }
          </div>
      </>
    );
}

export default App;

CurrentItems.tsx

[...]
} else {
                // marked successfully, give option to undo
                props.global.addAlert(<>
                    <span>You did it! &nbsp;</span>
                    <Alert.Link onClick={ () => { onUnMarkItem(itemId); } }>Undo</Alert.Link>
                </>, "success");
            }
[...]

I can pass a callback function to addAlert, but is it possible to dynamically find the Alert.Link inside the ReactNode and set the onClick function of it?

The most straightforward solution I can think of with my current knowledge is to generate the alert id outside of the addAlert and pass it in as a parameter, but I would prefer not to do that if there's another way.

CodePudding user response:

One way to handle this would be to modify your addAlert function so that the msg can be a function of the generated id.

In order to minimize rewriting your existing addAlert function calls, we'll keep it so that it can also accept a ReactNode.

Now our msg property is either a ReactNode or a function that creates a ReactNode, so we need to check if it's a function and handle that accordingly.

function addAlert(
>>  msg: React.ReactNode | ((id: string) => React.ReactNode),
    style: string,
    callback?: (id: string) => {}
) {
    console.log("add alert triggered", currentAlerts);
    let id = uuidv4();
    let newTimeout = setTimeout(clearAlert, timeoutMilliseconds, id);
>>  const message = typeof msg === 'function' ? msg(id) : msg;
    let newAlert = {
        id: id,
>>      msg: message,
        style: style,
        callback: callback,
        timeout: newTimeout
    } as AppAlert;
    let test = [...currentAlerts, newAlert];
    console.log("after add alert", test);
    setCurrentAlerts(test);
}

The three lines with >> are the ones that I changed.

In your CurrentItems.tsx file, you can now provide your msg as a function of id, like this:

>> props.global.addAlert((alertId) => (<>
    <span>You did it! &nbsp;</span>
    <Alert.Link
        onClick={() => {
            onUnMarkItem(itemId);
>>          props.global.clearAlert(alertId);
        }}
    >
        Undo
    </Alert.Link>
</>), "success");
  • Related