skip to Main Content

Let’s say I have a component that accepts an element (likely to be <input />) and I want to programmatically update the value of it after 15 seconds. This is my first thought on how to do it:

const MyComponent = (myInput: JSX.Element) => {
    useEffect(() => {
        setTimeout(() => {
            myInput instanceof HTMLInputElement && myInput.value = "new value";
        }, 15000);
    });   

    return <></>;
};

Now, this doesn’t work because myInput is a React Node / JSX Element and not a raw HTMLInputElement. To get around this my 2nd thought is to use ref somehow but myInput isn’t rendered so I’m unable to ref={myRef}.

Is there any way I can access the underlying HTML element at runtime?

My reason for this is because I’m using a library that has rather limited functionality and I’m trying to make it work correctly with react-hook-form. As RHF relies on onChange, I need to trigger a "change" event (I used myInput.value = above to simplify the example but really I just want the raw element to trigger an event)

2

Answers


  1. I recommend you don’t try to modify the dom directly. React has no idea that you’re doing that so it can’t take your changes into account when it does its calculations.

    But there are a couple options. First, you could clone the input and give it an extra value prop. I adjusted your props to destructure the object, and now we’ve got a state to track the value:

    import { cloneElement } from 'react';
    
    const MyComponent = ({ myInput }: { myInput: JSX.Element }) => {
      const [value, setValue] = useState<string | null>(null);
      useEffect(() => {
        setTimeout(() => {
          setValue("new value");
        }, 15000);
      }, []);
    
      if (value === null) {
        return myInput;
      } else {
        return cloneElement(myInput, { value });
      }
    }
    

    This approach does mean though that you can only work with elements that accept a value prop. It’s also a bit of a pain in typescript.

    So a second option is to change the props you accept so that instead of passing in an element, you pass in a function. The function will accept the value and return an element. The term you’ll see for this pattern is a "render prop". For example:

    const MyComponent = ({ render }: {
      render: (value: string | null) => JSX.Element;
    }) => {
      const [value, setValue] = useState<string | null>(null);
      useEffect(() => {
        setTimeout(() => {
          setValue("new value");
        }, 15000);
      }, []);
    
      return render(value);
    };
    
    // Used like:
    <MyComponent render={(value) => <input value={value} />} />
    

    That will make it flexible to be used with other components that need to use different props or add custom logic. Eg:

    <MyComponent render={(value) => <Example title={value ? value.toUpperCase() : "NONE"} />} />
    
    Login or Signup to reply.
  2. Potential Solution:

    import React, { useEffect, useRef } from 'react';
    
    const MyComponent = () => {
      const myInputRef = useRef<HTMLInputElement>(null);
    
      useEffect(() => {
        const timeoutId = setTimeout(() => {
          if (myInputRef.current) {
            myInputRef.current.value = 'new value';
            myInputRef.current.dispatchEvent(new Event('change', { bubbles: true }));
          }
        }, 15000);
    
        return () => clearTimeout(timeoutId);
      }, [myInputRef]);
    
      return (
        <>
          <input ref={myInputRef} />
        </>
      );
    };
    
    export default MyComponent;
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search