What's wrong with my way to handle input in react? I want to detect keycode and prevent them to be entered into the input, but now below code doesn't seem working.
const Input = ({ placeholder }) => { const [inputValue, setInputValue] = useState("");
const handleKeyDown = e => {
console.log(e.target.value);
if ([188].includes(e.keyCode)) {
console.log("comma");
} else {
setInputValue(e.target.value);
} };
return (
<div>
<input
type="text"
value={inputValue}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
</div> ); };
https://codesandbox.io/s/ancient-waterfall-43not?file=/src/App.js
you need to call e.preventDefault(), but also you need to add onChange handler to input:
const handleKeyDown = e => {
console.log(e.key);
if ([188].includes(e.keyCode)) {
console.log("comma");
e.preventDefault();
}
};
const handleChange = e => setInputValue(e.target.value);
...
<input
type="text"
value={inputValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
When you update the value of input, you should use onChange().
But, If you want to catch some character and treat about that, you should use onKeyDown().
So, in your case, you should use both.
this is an example code about backspace.
const Input = (props) =>{
const [value, setValue] = React.useState('');
function handleChange(e){
setValue(e.target.value);
}
function handleBackSpace(e){
if(e.keyCode === 8){
//Do something.
}
}
return (
<div>
<input onChange={handleChange} onKeyDown={handleBackSpace} value={value} type="text" />
</div>
)
}
```
Pass the value from Parent to child. Please check the below code.
import React, { useState } from "react";
import "./styles.css";
const Input = ({ placeholder, inputValue, handleKeyDown }) => {
return (
<div>
<input
type="text"
value={inputValue}
onKeyDown={handleKeyDown}
placeholder={placeholder}
/>
</div>
);
};
export default function App() {
const [inputValue, setInputValue] = useState();
const handleKeyDown = e => {
console.log(e.keyCode);
console.log(e.target.value);
if ([40].includes(e.keyCode)) {
console.log("comma");
} else {
setInputValue(e.target.value);
}
};
return (
<div className="App">
<Input
placeholder="xx"
value={inputValue}
handleKeyDown={handleKeyDown}
/>
</div>
);
}
onKeyDown, onKeyUp, and onKeyPress contain the old value of the target element.
onInput event gets the new value of the target element.
check the below link I add some console log. which help you to understand which event contains the value
https://codesandbox.io/s/ecstatic-framework-c4hkw?file=/src/App.js
I think you should not use the onKeyDown event on this case to filter your input. The reason is that someone could simply copy and paste the content into the input. So it would not filter the comma character.
You should use the onChange event and add a Regex to test if the input is valid.
const Input = ({ placeholder }) => {
const [inputValue, setInputValue] = useState("");
const handleChange = e => {
const filteredInput = e.target.value.replace(/[^\w\s]/gi, "");
setInputValue(filteredInput);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder={placeholder}
/>
</div>
);
};
But how it works...
So the regex is currently allowing any word, digit (alphanumeric) and whitespaces. You could for example extend the whitelist to allow # by doing const filteredInput = e.target.value.replace(/[^\w\s#]/gi, ""); Any rule inside the [^] is allowed. You can do some regex testing here https://regexr.com/55rke
Also you can test my example at: https://codesandbox.io/s/nice-paper-40dou?file=/src/App.js
Related
I have multiple rows, each row contains two text inputs and a button. When user focuses on one of the inputs, the button should be shown. When elements lose focus, the button should become invisible once again. My best attempt:
const Input = ({inputRef}) => {
return (
<>
<h1>Input</h1>
<input type="text" ref={inputRef}/>
</>
)
}
export default () => {
const firstRef = useRef(null);
const secondRef = useRef(null);
const it = useRef(null);
const [editing, setEditing] = useState(false);
function handleClick(e) {
firstRef.current.focus();
}
function handleSave() {
console.log("saving!");
}
function checkFocus(e) {
if (!it.current.contains(document.activeElement)) {
setEditing(false);
} else {
setEditing(true);
}
}
useEffect(() => {
document.body.addEventListener("focus", checkFocus, true);
return () => {
document.body.removeEventListener("focus", checkFocus, true);
}
}, []);
return (
<div ref={it}>
<Input inputRef={firstRef}/>
<Input inputRef={secondRef}/>
<button type="button" onClick={handleSave} style={{visibility: editing ? "visible" : "hidden"}}>Save</button>
<button type="button" onClick={handleClick}>Edit</button>
</div>
)
}
Is there any better/more elegant and efficient way of achieving this?
Here's a solution to what you're attempting using only CSS, which in my opinion makes it more elegant (and ever so slightly more performant, but really this is negligible).
https://codepen.io/danny_does_stuff/pen/QWMprMJ
<div>
<input id="input1" />
<input id="input2" />
<button id="save-button">Save</button>
<button id="edit-button">Edit</button>
</div>
<style>
input#input2:focus + button#save-button {
visibility: hidden;
}
</style>
If you wanted to do it in a more React way, you could do what Marco B suggested in his answer
You can use onBlur and onFocus events.
This should work as expected, just adapt the logic on your component
EDIT
Edited the onBlur method.
const INITIAL_STATE = {
input: ''
}
export default function App() {
const [show, setShow] = useState(false);
const [value, setValue] = useState(INITIAL_STATE);
const handleChange = (e) => {
const { value, name } = e.target;
setValue(prevState => ({ ...prevState, [name]: value }))
}
const onBlur = () => {
if (!value.input) {
setShow(false)
}
}
return (
<>
<Input name="input" onChange={handleChange} value={value.input} onFocus={() => setShow(true)} onBlur={onBlur} />
{show && <button>TEST</button>}
</>
);
}
const Input = (props) => {
return (
<>
<h1>Input</h1>
<input {...props} type="text" />
</>
);
};
I am using a NumberFormat text field which allows integers only but I want to allow operators like(%, *,/,<,>,+,-,=,^).
import NumberFormat from "react-number-format";
state = { name: "" }
<NumberFormat
{...this.props}
placeholder=""Enter a number
customInput={OutlinedInput}
value={name}
onValueChange={(e) => handleFieldChange(e.value)}
/>
handleFieldChangeValue = (value) => {
this.setState({name, value})
}
How can I allow the operators in this textfield?
Here is how you achieve what you need with a simple input field with regex:
import React from "react";
const regex = new RegExp('^([0-9]|[-+=^<>/%*])+$');
function App() {
const [value, setValue] = React.useState('');
const onChange = e => {
if(regex.test(e.target.value) || e.target.value === "") {
setValue(e.target.value);
};
}
return (
<div>
<input value={value} onChange={onChange} />
</div>
);
}
You can also check it with this sandbox
Starting out with react, I have created an input field in the parent component, When the user submits the text, I am passing down the text to the child component and printing the text under the parent component.
Code of parent component
import React, { useState } from "react";
import Validate from "./Validate";
function CommandEntry() {
const [value, setValue] = useState("");
const handleSubmit = e => {
e.preventDefault();
// console.log(value);
return <Validate value={value} />;
};
return (
<div>
<form onSubmit={handleSubmit}>
enter text:~
<input
type="text"
autoFocus
required
onChange={e => setValue(e.target.value)}
/>
</form>
</div>
);
}
export default CommandEntry;
Code of child component
import React from "react";
export default function Validate(props) {
console.log("I am in child");
return (
<div>
<h1>{props.value}</h1>
</div>
);
}
You would need to render the child inside return and the set the value in handler.
Like so:
function CommandEntry() {
const [value, setValue] = useState("");
const [submit, setSubmitValue] = useState(false);
const handleChange = e => {
e.preventDefault();
setValue(e.target.value); //setting the value on change
};
const handleSubmit = e => {
e.preventDefault();
setSubmitValue(true); //setting the submit flag to true.
};
return (
<div>
value &&
<form onSubmit={handleSubmit}> // submit handler
enter text:~
<input
type="text"
autoFocus
required
onChange={handleChange} // change handler
/>
{submit &&
<Validate value={value} /> // rendering the child component
}
</form>
</div>
);
}
You can't return a JSX element from a handler function. Handler functions are different and render functions are different. So here you can change this in your parent component in which child will be shown only when submit is true.
import React, { useState } from "react";
import Validate from "./Validate";
function CommandEntry() {
const [value, setValue] = useState("");
const [submit, setSubmitValue] = useState(false);
const handleSubmit = e => {
e.preventDefault();
setSubmitValue(true);
};
return (
<div>
<form onSubmit={handleSubmit}>
enter text:~
<input
type="text"
autoFocus
required
onChange={e => setValue(e.target.value)}
/>
</form>
{submit && <Validate value={value} />}
</div>
);
}
export default CommandEntry;
I'd like to keep the two digits after a number ie 2.89 or 2.00. Google brought me to this answer to use .toFixed(2).
While that works great, it does not work well when entered input values:
const [ value, setValue] = useState()
const onChange = (value) => {
const float = parseFloat(value)
setValue(float.toFixed(2))
}
<input
type="number"
value={value}
onChange={({ target }) => onChange(target.value)}
/>
If I should type, say, "300", the input value stops at "3.00". I have to move the cursor before "3" to type "300". What's the best way to do this?
I expect the value to always show .33, .00 etc while having the ability to "free type". As I type this question, I feel I need to use onBlur to convert the value to .toFixed and not while typing?
You can use onBlur and add some checks in while setting value
export default function App() {
const [value, setValue] = useState('');
const onChange = (v) => {
if (!Number.isNaN(v)) {
setValue(v);
}
};
return (
<div className="App">
<input
type="number"
value={value}
step="1"
onChange={({ target }) => onChange(target.value)}
onBlur={e => setValue(Number(value).toFixed(2))}
/>
</div>
);
}
I would not try to set the decimal places on the number on the onChange but make an onBlur handler.
const TodoApp = ( ) => {
const [ value, setValue] = React.useState('');
const onBlur = (e) => {
const float = parseFloat(e.target.value)
setValue(float.toFixed(2))
}
return (
<input
type="number"
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={onBlur}
/>
);
}
Hi dude read this https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number
And try using step propety, i recommended
<input
type="number"
value={value}
step="1"
onChange={({ target }) => onChange(target.value)}
/>
and try if it works
export const InputElement = () => {
const [value, setValue] = React.useState(0);
const fixInt = (v) => {
setValue(Number(v).toFixed(2));
};
return (
<div className="App">
<input
type="number"
value={value}
step="1"
onChange={({ target }) => fixInt(target.value)}
/>
</div>
);
}
I'm providing some form functionality from a custom React hook. This hook has some functionality similar to Formik (but this is really basic stuff).
function useFormValidation(initialState, validate) {
const [values, setValues] = React.useState(initialState);
const [errors, setErrors] = React.useState({});
const [isSubmitting, setSubmitting] = React.useState(false);
React.useEffect(() => {
if (isSubmitting) {
const noErrors = Object.keys(errors).length === 0;
if (noErrors) {
console.log("authenticated!", values.email, values.password);
setSubmitting(false);
} else {
setSubmitting(false);
}
}
}, [errors]);
function handleChange(event) {
setValues({
...values,
[event.target.name]: event.target.value
});
}
function handleBlur() {
const validationErrors = validate(values);
setErrors(validationErrors);
}
function handleSubmit(event) {
event.preventDefault();
const validationErrors = validate(values);
setErrors(validationErrors);
setSubmitting(true);
}
return {
handleSubmit,
handleChange,
handleBlur,
values,
errors,
isSubmitting
};
}
The form is the following:
function Register() {
const {
handleSubmit,
handleChange,
handleBlur,
values,
errors,
isSubmitting
} = useFormValidation(INITIAL_STATE, validateAuth);
// const [email, setEmail] = React.useState("");
// const [password, setPassword] = React.useState("");
return (
<div className="container">
<h1>Register Here</h1>
<form onSubmit={handleSubmit}>
<Input
handleChange={handleChange}
handleBlur={handleBlur}
name="email"
value={values.email}
className={errors.email && "error-input"}
autoComplete="off"
placeholder="Your email address"
/>
{errors.email && <p className="error-text">{errors.email}</p>}
<Input
handleChange={handleChange}
handleBlur={handleBlur}
value={values.password}
className={errors.password && "error-input"}
name="password"
// type="password"
placeholder="Choose a safe password"
/>
{errors.password && <p className="error-text">{errors.password}</p>}
<div>
<button disabled={isSubmitting} type="submit">
Submit
</button>
</div>
</form>
</div>
);
}
And the memoized component is the next:
function Input({
handleChange,
handleBlur,
name,
value,
className,
autoComplete,
placeholder,
type
}) {
return (
<input
onChange={handleChange}
onBlur={handleBlur}
name={name}
value={value}
className={className}
autoComplete={autoComplete}
placeholder={placeholder}
type={type}
/>
);
}
function areEqual(prevProps, nextProps) {
console.log(`
prevProps: ${JSON.stringify(prevProps.value)}
nextProps: ${JSON.stringify(nextProps.value)}
`);
return prevProps.value === nextProps.value;
}
const useMemo = (component, propsAreEqual) => {
return memo(component, propsAreEqual);
};
export default useMemo(Input, areEqual);
I enter some text into the first input. Then, when I switch to the second Input and start typing, the first input loses the value. It's like the form is not rendering the LAST MEMOIZED input, but prior versions instead.
I'm a React beginner and can't figure out the solution. Any help please?
Try using the updater form of setState which takes a function:
function handleChange(event) {
// event.target wont be available when fn is run in setState
// so we save them in our own local variables here
const { name, value } = event.target;
setValues(prev => ({
...prev,
[name]: value
}));
}
Your areEqual method translates to
Re-render my Input ONLY when the value changes.
But in reality, your handleChange function from the hook is also changing. Also, you use the same handleChange for both the inputs. So, the Input "remembers" only the handleChange from the last time value had changed and since handleChange is tracking values via closure, it in-turn "remembers" the values when it was created.
Changing your areEqual method (or completely omitting it) to verify a change in handleChange, will solve your problem.
function areEqual(prevProps, nextProps) {
return (
prevProps.value === nextProps.value &&
prevProps.handleChange === nextProps.handleChange
);
}
A codesandbox of the solution here