javascriptreactjsreact-contextreact-simple-keyboard

Initial value for useState isn't updating when using Virtual Keyboard


Beginner here. Trying to get react-simple-keyboard working with Gatsby & React.

I initialise my form with some state (firstName: "Johnn"). This should be the initial state. I want the user to be able to modify this name, and save the modified version in state.

I initialise my state here:

 const [inputs, setInputs] = useState({
     firstName: "Johnn"
  })

When I click on the field and press a button on the virtual keyboard (a letter, say), it deletes the content of the whole field and puts the letter there, instead of adding the letter to whats already in there. Also: Clicking on the field and pressing backspace (on the react-simple-keyboard) does not do anything. Why is this?

import React, { useRef, useState, useContext, useEffect } from "react"
import styled from "styled-components"
import ReactDOM from "react-dom"
import Keyboard from "react-simple-keyboard"
import "react-simple-keyboard/build/css/index.css"
import Layout from "@components/layout"
import { useForm } from "react-hook-form"
import { Flex, Box } from "rebass/styled-components"
import Input from "@atoms/Input"

import {
  GlobalDispatchContext,
  GlobalStateContext,
} from "../context/GlobalContextProvider"

function App() {
  const dispatch = useContext(GlobalDispatchContext)
  const state = useContext(GlobalStateContext)

  const [inputs, setInputs] = useState({
    firstName: "Johnn",
    // firstName: state.customers[state.currentCustomer].firstName,
  })
  const [layoutName, setLayoutName] = useState("default")
  const [inputName, setInputName] = useState("default")

  const [isShiftPressed, setShiftPressed] = useState(false)
  const [isCaps, setCaps] = useState(false)
  const [isKeyboardVisible, setKeyboardVisible] = useState(false)

  const { register, handleSubmit, errors } = useForm()
  const keyboard = useRef()

  const onChangeAll = newInputs => {
    /**
     * Here we spread the inputs into a new object
     * If we modify the same object, react will not trigger a re-render
     */
    setInputs({ ...newInputs })
  }

  const handleShift = () => {
    const newLayoutName = layoutName === "default" ? "shift" : "default"
    setLayoutName(newLayoutName)
  }

  const onKeyPress = button => {
    if (isShiftPressed === true && !isCaps) {
      setShiftPressed(false)
      handleShift()
    }
    if (button === "{lock}") {
      setCaps(true)
    }
    if (button === "{shift}" || button === "{lock}") {
      setShiftPressed(true)
      handleShift()
    }
  }

  const onChangeInput = event => {
    const inputVal = event.target.value

    setInputs({
      ...inputs,
      [inputName]: inputVal,
    })

    keyboard.current.setInput(inputVal)
  }

  const getInputValue = inputName => {
    return inputs[inputName] || ""
  }

  return (
    <Layout>
      <Flex flexDirection="column" style={{ height: "100%" }}>
        <form onSubmit={handleSubmit(onSubmit)}>
          <Input
            id="firstName"
            name="firstName"
            value={getInputValue("firstName")}
            onFocus={() => {
              setInputName("firstName")
            }}
            placeholder={"First Name"}
            onChange={onChangeInput}
          />
        </form>

        <Keyboard
          keyboardRef={r => (keyboard.current = r)}
          inputName={inputName}
          layoutName={layoutName}
          onChangeAll={onChangeAll}
          onKeyPress={onKeyPress}
        />
      </Flex>
    </Layout>
  )
}

export default App

Solution

  • You might need to use useEffect hook set the initial keyboard value, and on subsequent changes and also remove keyboard.current.setInput(inputVal).

    const {firstName} = input;
    useEffect(() => {
      keyboard.current.setInput(firstName);
    }, [firstName]);
    

    This will make sure that the initial and subsequent changes of firstName is set in keyboard instance.

    Code Sandbox: https://codesandbox.io/s/distracted-aryabhata-j3whs?file=/src/index.js