IonInput not allowing to conditionally prevent the onChange event - javascript

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;

Related

Unable to type into input field

I'm trying to store user input in local storage and had it functioning. But because I will need to test it, I have changed my code and made a function that will work as a custom hook, that I will call in my tests also.
Now the page is rendering but I am not able to type into the input box?
When hovered over the box the mouse cursor doesn't even respond as if it isn't an input field.
I believe the problem lies in my useStateWithLocalStorage function:
import { useState, useEffect } from 'react';
const useStateWithLocalStorage = (defaultValue, key) => {
const [value, setValue] = useState(() => {
const storedValues = localStorage.getItem(key);
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
export default useStateWithLocalStorage;
In particular this line
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
The value before anything is parsed into the localstorage should be name: ''
Here is my component:
import React from 'react';
import { Container, Title } from '#mantine/core';
import useStateWithLocalStorage from './Handlers';
const UserForm = () => {
const [inputValue, setInputValue] = useStateWithLocalStorage('', 'form');
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
}
function handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setInputValue((previousValues) => ({
...previousValues,
[event.target.name]: event.target.value,
}));
}
return (
<Container>
<Title order={2}>Welcome {inputValue.name}</Title>
<form onSubmit={handleSubmit}>
<label htmlFor="name">
Name
<input
type="text"
name="name"
id="name"
placeholder="enter your name"
onChange={handleChange}
value={inputValue.name}
/>
</label>
</form>
</Container>
);
};
export default UserForm;
I hope I've explained myself well enough and haven't wasted anyone's time. I'd be thankful for any help.

How to round up number inside HTMLInputElement

I have an Input field to which if I enter a decimal value I want to round it off to its next highest number, but the problem is that whenever I type a dot (".") the onChnage gets triggered, rounds it off and then updates the value, so basically I'm not able to type anything after the dot ("."). I can't use onBlur here, can someone tell me what I can do
import React, { useState, ChangeEvent } from 'react';
import { TextInputHelperCurrency } from '#honeycomb-npm/honeycomb-react';
export type LoanAmountProps = {
label: string;
errorMessage: string;
defaultLoanAmount: string;
minLoanAmount: string;
maxLoanAmount: string;
};
const LoanAmount: React.VFC = () => {
const [enteredLoanAmount, setEnteredLoanAmount] = useState<string>('10000');
const handleAmountChange = (value: string) => {
const roundedUpValue = Math.ceil(Number(value));
setEnteredLoanAmount(String(roundedUpValue));
};
return (
<div className="loan-amount">
<TextInputHelperCurrency
data-testid="textInputHelperCurrency"
id="loanAmount"
value={enteredLoanAmount}
onChange={handleAmountChange}
/>
</div>
);
};
export default LoanAmount;
I haven't worked on ReactTS, but I will answer the solution in ReactJS. Feel free to edit the solution or suggest changes for TS.
Firstly, create a useRef hook which will be passed in the TextInputHelperCurrency as innerRef prop. Next, in useEffect hook, check if the useRef hook created above has loaded and if there is a click, and useRef hook does not contains the target event Node, then round up the value.
In the TextInputHelperCurrency component, accept innerRef as prop and attach the innerRef as ref to the parent tag of return statement. Here is the code:
const LoanAmount: React.VFC = () => {
const [enteredLoanAmount, setEnteredLoanAmount] = useState<string>('10000');
const textInputRef = useRef(null);
useEffect(() => {
const handleOutsideClickEvent = (event) => {
if (
textInputRef.current &&
!textInputRef.current.contains(event.target)
) {
const roundedUpValue = Math.ceil(Number(textInputRef.current.value));
setEnteredLoanAmount(String(roundedUpValue));
}
};
document.addEventListener('click', handleOutsideClickEvent);
return () => document.removeEventListener('click', handleOutsideClickEvent);
}, [textInputRef]);
return (
<div className='loan-amount'>
<TextInputHelperCurrency
data-testid='textInputHelperCurrency'
id='loanAmount'
value={enteredLoanAmount}
innerRef={textInputRef}
/>
</div>
);
};
Inside TextInputHelperCurrency component,
const TextInputHelperCurrency = ({innerRef}) => {
// some code
return (
<div ref={innerRef}>
{/* some more code */}
</div>
)
};

How to implement two methodes from validation on a form in the react?

I have a project that is only allowed me to change this file (file below). Other files are complete, for this reason, I do not place these in here (also do not lengthen the code). My question is only about the implementation of two methods from validation in this file.
command:
An error is not displayed during the first focus on input until taken focus from it. If there is an error, it displays. and next times, during of the changing if encounter with an error, the same time displays.
I implemented the first section from the above command, how do I the second section in the same file? With 'useEffect'? How? Thanks for any helping.
input.js
import {useState} from "react";
import {validate} from './validators';
const INPUT_STATES = { /*this is not used!*/
UNTOUCHED: 'UNTOUCHED',
VALID: 'VALID',
INVALID: 'INVALID'
};
const myValidate= validate; /*validate is the my validation function*/
const Input = props => {
const [value,setValue] = useState('');
const [validate,setValidate] = useState(true);
const handleChange = (e) => {
setValue(e.target.value);
}
const handleValidate = () => {
const Validate = myValidate(value,props.validators);
setValidate(Validate);
}
return (
<div className={`form-input ${validate ? '' : 'form-input--invalid'}`} data-testid="form-input">
<label htmlFor={props.id}>{props.label}</label>
<input value={value} type={props.type} id={props.id} onChange={handleChange} onBlur={handleValidate} />
<p>{validate ? '' : props.errorText}</p>
</div>
)
};
export default Input;
I solved the above problem, by adding two If inside handleChange and handleValidate. Also, I defined another useState.
Also, I changed first parameter from myValidate, from value to e.target.value.
input.js:
import React from "react";
import {useState} from "react";
import {validate} from './validators';/*This file is cantains validation rulles.*/
const myValidate = validate; /*validate is the my validation function*/
const Input = props => {
const [value,setValue] = useState('');
const [validate,setValidate] = useState(true);
const [valid,setValid] = useState('VALID');
const handleChange = (e) => {
setValue(e.target.value);
if(valid === 'INVALID'){
handleValidate(e);
}
}
const handleValidate = (e) => {
const Validate = myValidate(e.target.value,props.validators);
setValidate(Validate);
if(!Validate){
setValid('INVALID');
}
}
return (
<div className={`form-input ${validate ? '' : 'form-input--invalid'}`} data-testid="form-input">
<label htmlFor={props.id}>{props.label}</label>
<input value={value} type={props.type} id={props.id} onChange={handleChange} onBlur={handleValidate} />
<p>{validate ? '' : props.errorText}</p>
</div>
)
};
export default Input;
Now:
An error is not displayed during the first focus on input until taken focus from it. If there is an error, it displays. and next times, during of the changing if encounter with an error, the same time displays.

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
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

React 16.13: Cannot update a component from inside the function body of a different component in a child's event handler

I'm using React 16.13 and I don't understand the workaround for resolving this warning. I am calling a setState function from a child component's event handler, which means the setState isn't being run on render. I believe this should be allowed according to the documents.
Anyone know how I can achieve this what I am trying to do in a way that plays nice with what React was intending to prevent here?
Codesandbox Link: https://codesandbox.io/s/silly-pike-zwvpb?fontsize=14&hidenavigation=1&theme=dark
At the request of one comment, I'm also putting code here. Note the version of ant is 3.24.1 (but this is true of all version of Antd < 4). as you type the word cat, each successive letter is triggering the warning for me. Please excuse the ugly formatting as I didn't include the CSS.
import React, { useState } from "react";
import { Select } from "antd";
const { Option } = Select;
export const getSelectOptions = options => {
if (!Array.isArray(options) || options.length === 0) {
return undefined;
}
return options.map(opt => {
return <Option key={opt}>{opt}</Option>;
});
};
const SelectWithSearchFilter = props => {
const { value, data } = props;
const [filteredList, setFilteredList] = useState([]);
const handleSearch = searchTerm => {
console.log(searchTerm);
if (searchTerm.length < 1) {
setFilteredList([]);
} else {
const newFilteredList = data.filter(item =>
item.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredList(newFilteredList);
}
};
return (
<Select
value={value}
showSearch={true}
size="default"
// loading={loading}
// onChange={onChange}
placeholder="enter something"
onSearch={handleSearch}
// mode={mode}
// allowClear={allowClear}
>
{getSelectOptions(filteredList)}
</Select>
);
};
const App = () => {
return (
<div>
<SelectWithSearchFilter
value="alligator"
data={[
"alligator",
"bird",
"cat",
"cat2",
"cat3",
"dog",
"cow",
"monkey"
]}
/>
</div>
);
};
export default App;

Categories

Resources