Jonathan Tuzman
Jonathan Tuzman

Reputation: 13278

Proper ref handling with react-simple-keyboard

I am using react-simple-keyboard, and clearing my input by calling the useState hook's setter with an empty string clears the input but doesn't clear the keyboard's "memory", so the next time you type you are adding to the old string again.

react-simple-keyboard (RSK) has a clearInput function that does the trick, and (as I understand it) it has to be called on the keyboardRef. So I rigged that up:

my keyboard component (wrapper)

export default KeyboardElement = ({ onChange, keyboardRef }) => {
  return <KeyboardContainer>
    <Keyboard
      layout={ {
        'default': [
          '1 2 3 4 5 6 7 8 9 0',
          'Q W E R T Y U I O P',
          'A S D F G H J K L',
          'Z X C V B N M {bksp}',
        ],
      } }
      theme={ 'firmm-keyboard' }
      keyboardRef={ keyboardRef }
      onChange={ onChange }
    />
  </KeyboardContainer>;
};

consuming component

export default LicenseEntry = ({ onClose }) => {
  const [text, setText] = useState('');
  const keyboard = useRef();

  const onClear = () => {
    // @ts-ignore
    keyboard.current!.clearInput();
    setText('');
  };

  return (
    <FullScreenModalBackground zIndex={ 9 }>
      <OuterContainer>
        <InnerContainer>
          <ExitButton onClose={ onClose }/>
          <HeaderText>Add license</HeaderText>
          <Input
            readOnly
            data-testid="LICENSE_INPUT"
            value={ formatLicense(text) }
            placeholder={ 'type license here' }
          />
          <KeyboardElement
            keyboardRef={ r => keyboard.current = r }
            onChange={ setText }
          />
          <Button>Submit</Button>
          <Button onClick={ onClear }>Clear</Button>
        </InnerContainer>
      </OuterContainer>
    </FullScreenModalBackground>
  );
};

This works, but has a code smell: I'm creating the ref in the consuming component (I'm then passing it to my keyboard wrapper so that RSK can use it, and I can call clearInput on it), and using the RSK method in the consuming component instead of the keyboard wrapper.

It seems wrong that the component using the keyboard should be using and knowing about the RSK's ref. It also means that the type of keyboard.current! in the consuming component is never (or some other unmanageable type) and so I'm using the @ts-ignore as a band aid.

What is the proper correct way to go about this?

Update: I've tried the suggested solution but it's not working. The only change I actually make is that I don't used the passed ref in the inner component (KeyboardElement) and I define a clearInput function on KeyboardElement which just calls its ref's clearInput function. But the ref in the consuming component is always undefined.

I think the difference between my situation and the other one is that I'm not calling a function of the ref, I'm calling the function of the "nested ref". I'm passing the parent's ref to the child as a prop so that the child can assign its ref to that prop, and that seems wrong.

Upvotes: 6

Views: 3822

Answers (1)

tylerwalker
tylerwalker

Reputation: 101

the way i got this working with version 3.1.9 was to also import KeyboardReactInterface

import Keyboard, { KeyboardReactInterface } from 'react-simple-keyboard'

const keyboardRef = useRef<KeyboardReactInterface | null>(null)

<Keyboard
  keyboardRef={(r) => (keyboardRef.current = r)}
  onChange={onChange}
  onKeyPress={onKeyPress}
  layoutName={layoutName}
/>

Upvotes: 5

Related Questions