Search code examples
javascriptreactjstypescriptreact-simple-keyboard

How to add value to the input field according to the active input with react-simple-keyboard?


I have 3 input fields on my page. To fill in these inputs, it is not by what the user types, but by what the user presses on the <Keyboard /> component of the react-simple-keyboard library.

I'm managing to save what the user presses on the <Keyboard /> component. The problem is that I'm not able to separate the fields. For example, what I type in the 1st input is also going in the 2nd and 3rd input.

In <Keyboard /> we have a prop called onInit. I believe the problem is in how I am manipulating this prop. But I don't know how to fix this. Can you tell me what I'm doing wrong?

It's easier to understand with the codes and images below: Here's my code I put into codesandbox

import React, { useState } from "react";
import { CustomKeyboard } from "./components";
import { KeyboardEnum } from "./components/CustomKeyboard/KeyboardEnum";
import "./styles.css";

interface InputValuesProps {
  field1: string;
  field2: string;
  field3: string;
}

export default function App() {
  const [inputValue, setInputValue] = useState<string>("");
  const [inputValues, setInputValues] = useState<InputValuesProps>({
    field1: "",
    field2: "",
    field3: "",
  });
  const [activeInput, setActiveInput] =
    useState<keyof InputValuesProps>("field1");

  const onChange = (input: string) => {
    setInputValues({ ...inputValues, [activeInput]: input });

    setInputValue(input);
  };

  console.log("inputValues: ", inputValues);

  const onInputFocus = (inputName: any) => {
    setActiveInput(inputName);
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <input
        type="text"
        value={inputValues.field1}
        onClick={() => onInputFocus("field1")}
        onChange={() => {}}
      />
      <input
        type="text"
        value={inputValues.field2}
        onClick={() => onInputFocus("field2")}
        onChange={() => {}}
      />
      <input
        type="text"
        value={inputValues.field3}
        onClick={() => onInputFocus("field3")}
        onChange={() => {}}
      />
      <CustomKeyboard
        startValue={inputValue}
        keyboardOnChange={onChange}
        layoutName={KeyboardEnum.Numbers}
        maxCharacters={16}
      />
    </div>
  );
}


// CustomKeyboard component
import React, { useEffect, useInsertionEffect, useRef, useState } from "react";
import Keyboard from "react-simple-keyboard";
import { KeyboardEnum } from "./KeyboardEnum";
import "./index.css";

export interface CustomKeyboardProps {
  startValue: string;
  keyboardOnChange: (inputValue: string) => void;
  layoutName:
    | KeyboardEnum.Numbers
    | KeyboardEnum.Alphabet
    | KeyboardEnum.Symbols
    | KeyboardEnum.Shift;
  maxCharacters: number;
}

const CustomKeyboard = (props: CustomKeyboardProps) => {
  const [layoutName, setLayoutName] = useState(props.layoutName);
  const [inputValue, setInputValue] = useState(props.startValue);
  const keyboardRef = useRef();
  const handleShift = (button: string): void => {
    switch (button) {
      case "{enter}":
        setInputValue("");
        props.keyboardOnChange(inputValue);
        break;
      case "{shift}":
        setLayoutName(
          layoutName === KeyboardEnum.Alphabet
            ? KeyboardEnum.Shift
            : KeyboardEnum.Alphabet
        );
        break;
      case "{numbers}":
        setLayoutName(KeyboardEnum.Numbers);
        break;
      case "{abc}":
        setLayoutName(KeyboardEnum.Alphabet);
        break;
      case "{symbols}":
        setLayoutName(KeyboardEnum.Symbols);
        break;
      default:
        break;
    }
  };

  const onKeyPress = (button: string): void => {
    if (button === "{backspace}" && inputValue && inputValue.length === 1) {
      setInputValue("");
    }

    if (
      button === "{numbers}" ||
      button === "{abc}" ||
      button === "{shift}" ||
      button === "{symbols}" ||
      button === "{enter}"
    )
      handleShift(button);
  };

  const onChange = (input: string) => {
    const _input = input.replace(/\D/g, "");
    if (/^\d*$/.test(_input) && _input.length <= props.maxCharacters) {
      setInputValue(_input);
      if (keyboardRef.current) {
        try {
          // @ts-ignore
          keyboardRef.current.setInput(_input);
        } catch {
          console.log("Keyboard error");
        }
      }
    }
  };

  const customLayout = {
    numbers: [
      "1 2 3 4 5 6 7 8 9 0 {backspace}",
      // eslint-disable-next-line quotes
      "@ # $ % * ( ) ' \" {enter}",
      // eslint-disable-next-line quotes
      "{symbols} % - + = / ; : , . {symbols}",
      "{abc} {space} {abc}",
    ],
    shift: [
      "Q W E R T Y U I O P {backspace}",
      "A S D F G H J K L {enter}",
      "{shift} Z X C V B N M ! ? {shift}",
      "{numbers} , {space} . {numbers}",
    ],
    symbols: [
      "[ ] { } # % ^ * + = {backspace}",
      "_ \\ | ~ < > € £ ¥ ·",
      // eslint-disable-next-line quotes
      "{numbers} . , ? ! ' {enter}",
      "{abc} {space} {abc}",
    ],
    abc: [
      "q w e r t y u i o p {backspace}",
      "a s d f g h j k l {enter}",
      "{shift} z x c v b n m ! ? {shift}",
      "{numbers} , {space} . {numbers}",
    ],
  };

  const customDisplay = {
    "{numbers}": "123",
    "{shift}": "⇧",
    "{space}": "space",
    "{backspace}": "⌫",
    "{abc}": "ABC",
    "{symbols}": "#+=",
  };

  useEffect(() => {
    props.keyboardOnChange && props.keyboardOnChange(inputValue);
  }, [inputValue]);

  return (
    <div className="bg-[#263238] flex h-[418px] justify-center items-center w-full shadow-2xl p-6 absolute bottom-0">
      <div className="w-[1094px]">
        <Keyboard
          onInit={(e) => e.setInput(inputValue)}
          keyboardRef={(r) => (keyboardRef.current = r)}
          theme={"hg-theme-default myTheme1"}
          onChange={onChange}
          onKeyPress={onKeyPress}
          layoutName={layoutName}
          layout={customLayout}
          display={customDisplay}
        />
      </div>
    </div>
  );
};

export default CustomKeyboard;

enter image description here enter image description here


Solution

  • When you change the focus, you need to update your input value:

    
      const onInputFocus = (inputName: any) => {
        setActiveInput(inputName);
        setInputValue(inputValues[inputName]);
      };
    

    Right now, the last input value is being carried over when the focus changes.


    Additionally, you will need to update the keyboardRef.current whenever props.startValue. However, you will also need to take steps to ensure that you don't have circular updates on change.

    I think that your conjecture concerning onInit is correct: the Keyboard component is almost certainly not re-rendered/initialized each time you change your startValue.

    Without a MRE, it is hard to suggest further fixes.