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

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

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

Related

How to write value to localStorage and display it in input on reload?

I have an input on the page, initially it is empty. I need to implement the following functionality: on page load, the component App fetches from localStorage a value of key appData and puts it in the input. That is, so that in the localStorage I write the value to the input and when reloading it is displayed in the input. How can i do this?
I need to use useEffect
import { useEffect, useState } from "react";
export default function App() {
const [userData, setUserData] = useState("");
useEffect(() => {
localStorage.setItem("Userdata", JSON.stringify(userData));
}, [userData]);
return (
<div>
<input value={userData} onChange={(e) => setUserData(e.target.value)}></input>
</div>
);
}
Use the change event to write to the localStorage, then use an init function in the useState hook.
import { useState } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const [ userData, setUserData ] = useState(loadUserData);
const handleUserDataUpdate = e => {
const userData = e.target.value;
setUserData(userData);
saveUserData(userData);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input id="testInput" value={ userData } onChange={ handleUserDataUpdate } />
</div>;
}
If you need an example using uncontrolled inputs, here is one using useEffect :
import { useEffect } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.value = loadUserData();
}, []); // initial load
const handleUpdateUserData = () => {
saveUserData(inputRef.current.value);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input ref={ inputRef } id="testInput" onChange={ handleUpdateUserData } />
</div>;
}
You can set a default value for the input inside state.
const [userData, setUserData] =
useState(JSON.parse(localStorage.getItem('Userdata')) || '');
So when the component mounts (after reload), the initial userData value is taken directly from the localStorage. If it's empty, the fallback value will be set ('').
Note: Make sure to add also the onChange handler to the input.

IonInput not allowing to conditionally prevent the onChange event

On this StackBlitz project: https://stackblitz.com/edit/node-hxolmq?file=src%2Fmain.tsx
I have the following custom control...
/src/controls/IonInputMagic2.js
import { IonInput } from "#ionic/react";
import { useEffect, useState } from "react";
const IonInputMagic2 = props => {
const { value, onChange, validityFunc, ...others } = props
var isValidValue = validityFunc(value);
const initialValue = (typeof value !== 'undefined' && isValidValue) ? value : '';
const [ currentValue, setCurrentValue ] = useState(initialValue);
useEffect(() => {
setCurrentValue(initialValue);
}, [initialValue]);
const handleChange = (e) => {
var value = e.target.value;
if (!validityFunc(value)) {
e.preventDefault();
return false;
}
setCurrentValue(value);
if (onChange) {
onChange(e);
}
};
return (
<IonInput value={currentValue} onChange={handleChange} {...others} />
);
}
export default IonInputMagic2;
where you can see I use the Ionic control: IonInput.
My problem is: I have a validityFunc(...) that decides if what the user enters is acceptable or not. As per that function, only numeric and even digits are allowed. However, the user can enter whatever character with no restrictions.
I have a similar control: IonInputMagic1 which is very similar, but it uses the HTML built-in element: <input /> instead of the Ionic control: <IonInput />. On that control the user can only enter what is expected: only numeric and even digits.
Here is the difference between those 2 controls (left: works | right: doesn't work)
Here is how I use both controls:
What I need is: To make IonInputMagic2 (which uses: IonInput) work as: IonInputMagic1 where the user can only enter numeric and even digits. This is because the IonInput uses all the styling and scripting of Ionic and I don't want to break all that by using: <input />.
Note: I have detected through the DOM that the IonInput is a wrapper of: <input />.
Any idea on how to achieve that?
If possible, please fork the StackBlitz above and post the link here.
Thanks!
This change did the trick:
Here the full code for the component:
import { IonInput } from "#ionic/react";
import { useEffect, useState } from "react";
const IonInputMagic2 = props => {
const { value, onChange, validityFunc, ...others } = props
var isValidValue = validityFunc(value);
const initialValue = (typeof value !== 'undefined' && isValidValue) ? value : '';
const [currentValue, setCurrentValue] = useState(initialValue);
useEffect(() => {
setCurrentValue(initialValue);
}, [initialValue]);
const handleChange = (e) => {
var value = e.target.value;
if (!validityFunc(value)) {
e.preventDefault();
e.target.value = currentValue;
return false;
}
setCurrentValue(value);
if (onChange) {
onChange(e);
}
};
return (
<IonInput value={currentValue} onIonInput={handleChange} {...others} />
);
}
export default IonInputMagic2;

How to search field CARDS in React using React Hooks

I couldn't find how to make the field (input) work to filter the searches into cards:
can someone help me to operate the input field ?
**Notes.js**
import React, { useEffect, useState } from 'react'
import NoteCard from '../components/NoteCard'
import Masonry from 'react-masonry-css'
import Notess from '../components/CardList'
export default function Notes({details }) {
const [searchField, setSearchField] = useState("");
const [notes, setNotes] = useState([]);
const filteredNote = notes.filter(
note => {
return (
note.title.toLowerCase().includes(searchField.toLowerCase())
);});
const handleChange = e => {
setSearchField(e.target.value);
};
return (
<Container>
<input type='search' onChange = {handleChange} />
<Notess filter={filteredNote} />
</Container>
) }
someone can help me to operate the input field ?

useState not updating Grandparent state - data passes from Grandchild to child succesfully, but no further

I have an 'Autocomplete' grandchild React component which takes data from the child component, helps the user autocomplete a form field, and then passes the value right back up to the grandparent component which posts the whole form.
I am able to get data from grandchild to child, but not able to get the data from there up to the grandparent.
Grandparent - AddPost.jsx
import React, { useState } from 'react';
import GetBeerStyle from './GetBeerStyle';
export default function AddPost(props) {
const [parentBeerStyleData, setParentBeerStyleData] = useState("")
const handleSubmit = (e) => {
e.preventDefault();
// There's some code here that pulls the data together and posts to the backend API
}
return (
<div>
<GetBeerStyle
name="beerstyle"
beerStyleData={childData => setParentBeerStyleData(childData)}
onChange={console.log('Parent has changed')}
/>
// More data input fields are here...
</div>
);
}
Child - GetBeerStyle.jsx
import React, {useState, useEffect } from 'react';
import axios from 'axios';
import Autocomplete from '../Autocomplete';
export default function GetBeerStyle(props) {
const [beerStyleData, setBeerStyleData] = useState("")
const [beerStyles, setBeerStyles] = useState(null)
const apiURL = "http://localhost:8080/api/styles/"
// Code to fetch the beer styles from the API and push down to the grandchild
// component to enable autocomplete
const fetchData = async () => {
const response = await axios.get(apiURL)
const fetchedStyles = Object.values(response.data)
const listOfStyles = []
for (let i = 0; i < fetchedStyles.length; i++) {
(listOfStyles[i] = fetchedStyles[i].style_name)
}
setBeerStyles(listOfStyles)
};
// This snippet pulls data from the Posts table via the API when this function is called
useEffect(() => {
fetchData();
}, []);
return (
<div className="one-cols">
<Autocomplete
suggestions={ beerStyles } // sending the data down to gchild
parentUpdate={childData => setBeerStyleData(childData)}// passing data back to gparent
onChange={() => props.beerStyleData(beerStyleData)}
/>
</div>
);
}
Grandchild - Autocomplete.jsx
import React, { Component, Fragment, useState } from 'react';
export default function Autocomplete(props) {
const [activeSuggestion, setActiveSuggestion] = useState(0);
const [filteredSuggestions, setFilteredSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const [userInput, setUserInput] = useState("");
const [fieldId, setFieldId] = useState("");
const [parentUpdate, setParentUpdate] = useState("");
const onChange = e => {
const { suggestions } = props;
setActiveSuggestion(0);
setFilteredSuggestions(suggestions.filter(suggestion => suggestion.toLowerCase().indexOf(userInput.toLowerCase()) >-1));
setShowSuggestions(true);
setUserInput(e.currentTarget.value);
setParentUpdate(e.currentTarget.value);
(console.log(parentUpdate));
return props.parentUpdate(parentUpdate);
};
const onClick = e => {
setActiveSuggestion(0);
setFilteredSuggestions([]);
setShowSuggestions(false);
setUserInput(e.currentTarget.innerText);
setFieldId(props.fieldId);
setParentUpdate(e.currentTarget.innerText);
return props.parentUpdate(parentUpdate);
};
const onKeyDown = e => {
// User pressed the ENTER key
if (e.keyCode === 13) {
setActiveSuggestion(0);
setShowSuggestions(false);
setUserInput(filteredSuggestions[activeSuggestion]);
// User pressed the UP arrow
} else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
setActiveSuggestion(activeSuggestion - 1);
}
// User pressed the DOWN arrow
else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
setActiveSuggestion(activeSuggestion + 1);
}
};
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul class="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className;
// Flag the active suggestion with a class
if (index === activeSuggestion) {
className = "suggestion-active";
}
return (
<li className={className} key={suggestion} onClick={onClick}>
{suggestion}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div class="no-suggestions">
<em>No Suggestions Available.</em>
</div>
);
}
}
return (
<Fragment>
<input
type="text"
value={userInput}
onChange={onChange}
onKeyDown={onKeyDown}
id={fieldId}
/>
<div>
{suggestionsListComponent}
</div>
</Fragment>
);
}
While I certainly accept there may be other issues with the code, overriding problem that I seem to have spent an inordinate amount of time googling and researching, is that I can't get the data being entered in the form input, to pull through to the grandparent component!
What have I missed?

React useEffect hook does not call after recoil atom updated

I'm using recoiljs as my state manager for my react project, and one of my components doesn't call it's useEffect when a recoil atom changes from another file. Here is my main component that reads from an atom.
import React, {useState, useEffect} from 'react'
import '../css/MeadDeadline.css'
import {getNearestDate} from '../chromeAPI/retrieveDeadlineJSON'
import DeadlineList from '../atoms/deadlinelist'
import {useRecoilValue} from 'recoil'
export default function MainDeadline() {
// get the date and the stuff from chrome storage
const [school, setSchool] = useState("");
const [date, setDate] = useState("");
let [deadlinelist, setDeadlineList] = useRecoilValue(DeadlineList);
useEffect(() => {
const nearest = getNearestDate(deadlinelist);
const len = nearest.length;
if (len === 0){
setSchool("No schools registered");
setDate("");
} else if (len === 1){
setSchool(nearest[0].school);
setDate(nearest[0].date);
} else {
// we need to render a lot of stuff
console.log("error");
}
}, [deadlinelist]);
return (
<>
<div className="MainDeadline">
<div className='school'>{school}</div>
<div classNmae='date'>{date}</div>
</div>
</>
)
}
Here is my atom file
import {atom} from 'recoil'
const DeadlineList = atom({
key: "deadlinelist",
default: []
});
export default DeadlineList;
and here is the form that I'm submitting in
import React, {useState} from 'react'
import '../css/InputForm.css'
import checkList from '../utils/checkList'
import checkDate from '../utils/checkDate'
import {storeNewDeadline} from '../chromeAPI/storeNewDeadline'
import {useRecoilState} from 'recoil'
import DeadlineList from '../atoms/deadlinelist'
import SchoolList from '../atoms/schoollist'
export default function InputForm () {
const [inputschool, setInputSchool] = useState('');
const [inputdate, setInputDate] = useState('');
const [invalidschool, setInvalidSchool] = useState(false);
const [invaliddate, setInvalidDate] = useState(false);
const [badschool, setBadSchool] = useState('');
const [baddate, setBadDate] = useState('');
const [schoollist, setSchoolList] = useRecoilState(SchoolList);
const [deadlinelist, setDeadlineList] = useRecoilState(DeadlineList);
const validateForm = () => {
// check to make sure its not in the list
const valschool = checkList(schoollist, inputschool);
if (!valschool){
setInvalidSchool(true);
setBadSchool(inputschool);
} else {
setInvalidSchool(false);
setBadSchool("");
}
// check to make sure the date hasnt been reached yet
const valdate = checkDate(inputdate);
if (!valdate){ // add MSIN1DAY becauase the day value is 0 indexed so conflicts with Date() and date input
setInvalidDate(true);
setBadDate(inputdate);
}
else {
setInvalidDate(false);
setBadDate("");
}
return !invalidschool && !invaliddate; // want both to be valid
}
const handleSubmit = async(event) => {
event.preventDefault();
// validate the form
if (validateForm()){
storeNewDeadline(inputschool, inputdate);
// change schoollist state
let slist = schoollist;
slist.push(inputschool);
setSchoolList(slist);
// change deadlinelist state
let dlist = deadlinelist;
dlist.push({
"school": inputschool,
"date": inputdate
});
setDeadlineList(dlist);
console.log(deadlinelist, schoollist);
}
}
const handleChange = (event, fieldname) => {
switch (fieldname) {
case "inputschool":
setInputSchool(event.target.value);
break;
case "inputdate":
setInputDate(event.target.value);
break;
default:
break;
}
}
return (
<form className='InputForm' onSubmit={handleSubmit}>
<h3>Enter New School</h3>
<div id='inputname' className='Inputer'>
<p>School Name</p>
<input
type='text'
onChange={e => {handleChange(e, 'inputschool')}}
value={inputschool}
required
/>
{invalidschool ? <p>{badschool} is already registered</p> : null}
</div>
<div id='inputdate' className='Inputer'>
<p>Deadline Date</p>
<input
type='date'
onChange={e => {handleChange(e, 'inputdate')}}
value={inputdate}
required
/>
{invaliddate ? <p>{baddate} is invalid</p> : null}
</div>
<div id='inputsubmit' className='Inputer'>
<p>Submit</p>
<input type='submit' required></input>
</div>
</form>
)
}
If you want to just see file for file the github is here
The main component is src/components/MainDeadline.jsx , src/atoms/deadlinelist , src/components/InputForm.jsx
My main problem is when the user inputs something in the form, it's supposed to update the state, but the main component doesn't update.
Please tell me if I can improve my code in any way this is my first react project.
When dealing with arrays in state hooks, you need to clone the array as you do the set function.
Also, instead of this:
let [deadlinelist, setDeadlineList] = useRecoilValue(DeadlineList);
I would do this:
const [deadlinelist, setDeadlineList] = useRecoilState(DeadlineList);

Categories

Resources