I want to detect which character base on user keyboard hence I use onKeyDown, but how do I stop ',' been inserted into the input element?
const [inputValue, setInputValue] = useState("");
const handleKeyDown = (e: any) => {
if (['188'].includes(e.keyCode)) {
console.log("do something");
}
};
const handleChange = (e: any) => {
setInputValue(e.target.value)
}
return (
<input
type="text"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
);
const handleKeyDown = (e: any) => {
if (['188'].includes(e.keyCode)) {
return;
}
};
Your includes doesn't work since '188' (string) isn't equal to 188 (number)
You can check the last inputed char in your handleChange function like so:
const handleChange = (e: any) => {
const value = e.target.value
if (![','].includes(value[value.length-1])) {
setInputValue(e.target.value)
}
}
You just need to check the input in handleChange to decide to update new value or not
Try the code below:
Or Codesandbox
import React, { useState } from "react";
import "./styles.css";
const App = () => {
const [inputValue, setInputValue] = useState("");
const handleChange = e => {
if (!e.target.value.includes(",")) {
setInputValue(e.target.value);
}
};
return (
<input
value={inputValue}
type="text"
onChange={handleChange}
/>
);
};
export default App;
Related
Whenever I place the cursor in the middle of numbers, let's say 3|3000 then press either backspace or delete, cursor jumps to the back. I believe this is happening because I have value set as displayValue and since displayValue is a state, whenever displayValue changes value, input re-renders and therefore cursor moves back. I know simple solution is to set value={value}, but for this particular input I need to use state displayValue instead so I need to use alternative solution. Is there a way to cursor to remain in its natural position even through re-rendering? https://codesandbox.io/s/number-field-experiments-iii-ts-nh95up?file=/src/App.tsx:0-1633
import React, { useState, useRef, useEffect } from "react";
import "./styles.css";
export interface NumberFieldInterface {
onChange?: (value: number) => void;
value: number;
}
const NumberField: React.FC<NumberFieldInterface> = ({ value, onChange }) => {
const inputRef = useRef(null);
const [displayValue, setDisplayValue] = useState("");
const onChangeValue = (value: any) => {
if (typeof onChange === "function") {
onChange(value);
}
};
useEffect(() => {
const newFormatValue = value.toString();
setDisplayValue(newFormatValue);
}, [value]);
return (
<div>
<input
type="text"
ref={inputRef}
value={displayValue}
onChange={(e: React.FormEvent<HTMLInputElement>) =>
onChangeValue(e.target.value)
}
/>
</div>
);
};
export default function App() {
const [inputValue, setInputValue] = useState<number>(0);
const handleChange = (val: number) => {
setInputValue(val);
};
return (
<div className="App">
<NumberField value={inputValue} onChange={handleChange} />
</div>
);
}
Instead of using useEffect to change your displayValue, how about doing it inside the onChange?
const NumberField: React.FC<NumberFieldInterface> = ({ value, onChange }) => {
const inputRef = useRef(null);
const [displayValue, setDisplayValue] = useState("");
const onChangeValue = (value: any) => {
if (typeof onChange === "function") {
onChange(value);
}
const newFormatValue = value.toString();
setDisplayValue(newFormatValue);
};
return (
<div>
<input
type="text"
ref={inputRef}
value={displayValue}
onChange={(e: React.FormEvent<HTMLInputElement>) =>
onChangeValue(e.target.value)
}
/>
</div>
);
};
It seems to fix the issue of jumping around but I’m not entirely sure it fixes everything. And I’m unsure why value is passed to the component but never used.
Container
import { InputField } from './InputField';
const sleep = (time: number) => new Promise((res) => setTimeout(res, time, ''));
export const Container = () => {
const [inputValue, setInputValue] = React.useState('');
React.useEffect(() => {
(async () => await sleep(1000))();
async function fetchMyAPI(time, value) {
await sleep(time);
setInputValue(value);
}
fetchMyAPI(1000, 'vbc1');
fetchMyAPI(2000, 'dgi1');
}, []);
const inputChange = (value) => {
setInputValue(value);
};
return <InputField inputValue={inputValue} inputChange={inputChange} />;
};
InputField
export const InputField = ({
inputValue,
inputChange,
}: {
inputValue: string;
inputChange: (value: string) => void;
}) => {
const [value, setValue] = React.useState('');
React.useEffect(() => {
setValue(inputValue.slice(0, -1));
}, [inputValue]);
const handleChange = (event) => {
setValue(event.target.value);
inputChange(event.target.value + '1');
};
return <input value={value} onChange={handleChange} />;
};
inputValue above can change multiple times.
also a local variable in input is used to display , and inputValue is slightly different from it. So when we keep track of InputValue , we pass the cleared data to the local variable. And vice versa, we modify the data to put in the inputValue.
React.useEffect(() => {
setValue(inputValue.slice(0, -1));
}, [inputValue]);
Every time we call handleChange : we do setValue and inputChange. Thus, we change the value variable and the inputValue variable. After the inputValue is changed, useEffect is called which observes the inputValue. And overwrites exactly the same value of the Value variable. This is problem!
What is the correct solution to this problem?
You can create a boolean state effectRan to track whether the effect already ran or not, and only invoke the effect's logic if effectRan == false, then set it to true.
When the effect runs again with it as true, have it set it back to false to prepare to run again in the next change.
I changed the code a bit to highlight the approach:
const {useState, useEffect } = React
const InputField = () => {
const [value, setValue] = React.useState('');
const [effectRan, setEffectRan] = useState(true);
React.useEffect(() => {
if (!effectRan) {
setValue(prev => prev + '-');
setEffectRan(true)
console.log('Effect just ran');
} else {
setEffectRan(false)
}
}, [value]);
const handleChange = (event) => {
setValue(event.target.value);
};
return <input onChange={handleChange} value={value} />;
};
ReactDOM.render(<InputField />, root)
<script crossorigin src="https://unpkg.com/react#18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I need to get each user's keystroke when he pressed a certain key("#") and stop getting his keystroke when he pressed other key(space(" ")). For example: a user enters the text "I wanna go to #shop", I need to save his input and the tag inside it. How can I do it? I wrote some code to do it but I don't know how to make it completely
onKeyDown = (e) => {
let value = e.target.value, tags = [], currentTag = "";
if (e.key == "Enter") {
this.setState((state) => {
const item = this.createNote(value, tags);
return { notes: [...state.notes, item] };
});
}
if (e.key == "#") {}
};
You can make use of regex /#[^\s]+/g
Live Demo
export default function App() {
const [value, setValue] = useState("");
const [tags, setTags] = useState([]);
function onInputChange(e) {
const value = e.target.value;
setValue(value);
const tags = value.match(/#[^\s]+/g) ?? [];
setTags(tags);
}
return (
<>
<input type="text" name="" value={value} onChange={onInputChange} />
<ul>
{tags.map((tag) => {
return <li key={tag}> {tag} </li>;
})}
</ul>
</>
);
}
EDITED: You can make use of useMemo hook as
Thanks to 3limin4t0r
Live Demo
export default function App() {
const [value, setValue] = useState("");
const tags = useMemo(() => value.match(/#\S+/g) || [], [value]);
function onInputChange(e) {
const value = e.target.value;
setValue(value);
}
return (
<>
<input type="text" name="" value={value} onChange={onInputChange} />
<ul>
{tags.map((tag) => {
return <li key={tag}> {tag} </li>;
})}
</ul>
</>
);
}
Instead of parsing individual key values, you can use a function like this to parse your input field on changes and return an array of hashtags (without the leading #):
TS Playground link
function parseTags (input: string): string[] {
return (input.match(/(?:^#|[\s]#)[^\s]+/gu) ?? []).map(s => s.trim().slice(1));
}
Here's a working example in a functional component which incorporates the function:
<script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.16.4/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="react">
const {useState} = React;
function parseTags (input) {
return (input.match(/(?:^#|[\s]#)[^\s]+/gu) ?? []).map(s => s.trim().slice(1));
}
function Example () {
const [value, setValue] = useState('');
const [tags, setTags] = useState([]);
const handleChange = (ev) => {
const {value} = ev.target;
setValue(value);
setTags(parseTags(value));
};
return (
<div>
<input
type="text"
onChange={handleChange}
placeholder="Type here"
value={value}
/>
<div>Parsed tags:</div>
<ol>
{tags.map((str, index) => <li key={`${index}.${str}`}>{str}</li>)}
</ol>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
Something like this should work for you; You can adapt if you don't have access to hooks:
const RecorderInput = ({ onChange }) => {
const [isRecording, setIsRecording] = useState(false);
const toggleRecording = (e) => {
const character = String.fromCharCode(e.charCode);
if (character === '#') {
setIsRecording(true);
}
if (character === ' ') {
setIsRecording(false);
}
}
const handleChange = (e) => {
if (isRecording) onChange(e);
toggleRecording(e);
}
<input type="text" onChange={handleChange} />
}
As other suggested your onChange can also use regex groups to capture hashes as the user types. Thinking about this now, it would probably be a lot cleaner to do it this way but regex is well documented so I won't go through the hassle
I want to pass a numbers array like [2,4,5,5,3,7,4,7,0,4] into submitNumbers(), I'm trying to update numbers using an onChange event. it works if I pass the whole array without using the useState(). Trying to console.log these constants while running returns not defined.
function App() {
const [userAccount, setUserAccount] = useState()
const [amount, setAmount] = useState()
const [numbers, setNumbersValue] = useState()
//const numbers = [2,4,5,5,3,7,4,7,0,4]
async function setNumbers() {
if (typeof window.ethereum !== 'undefined') {
await requestAccount()
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(Address, Contract.abi, signer);
const transaction = await contract.submitNumbers(numbers);
await transaction.wait()
//fetchNumbers()
}
}
return (
<button onClick={e => setNumbers()}>Set Numbers</button>
<input onChange={e => setNumbersValue(e.target.value)} placeholder="Numbers" />
);
)
export default App;
Try this for converting your string to array:
import { useEffect, useState } from "react";
export default function App() {
const [state, setState] = useState([]);
const onChange = (e) => {
let value = e.target.value;
setState(value.split(","));
};
useEffect(() => {
console.log(state);
console.log(state);
console.log(typeof state);
}, [state]);
return (
<div className="App">
<input onChange={onChange} />
</div>
);
}
Note: As you said, you should set the input values like: 1,2,3,4,5,6,...
I want to debounce Formik <Field/> but when I type in the field seems debounce does not work. Also I have tried lodash.debounce, throttle-debounce and the same result. How to solve this?
CodeSandbox - https://codesandbox.io/s/priceless-nobel-7p6nt
Snippet:
import ReactDOM from "react-dom";
import { withFormik, Field, Form } from "formik";
const App = ({ setFieldValue }) => {
let timeout;
const [text, setText] = useState("");
const onChange = text => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => setText(text), 750);
};
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={e => {
onChange(e.target.value);
setFieldValue("textField", e.target.value);
}}
style={{ width: "100%" }}
/>
<br />
<br />
<div>output: {text}</div>
</Form>
);
};
const Enhanced = withFormik({
mapPropsToValues: () => ({
textField: ""
}),
handleSubmit: (values, { setSubmitting }) => {
setSubmitting(false);
return false;
}
})(App);
ReactDOM.render(<Enhanced />, document.getElementById("root"));
const [text, setText] = useState("");
const [t, setT] = useState(null);
const onChange = text => {
if (t) clearTimeout(t);
setT(setTimeout(() => setText(text), 750));
};
I would like to suggest to move the call inside of timeout function.
const App = ({ setFieldValue }) => {
let timeout;
const [text, setText] = useState("");
const onChange = text => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
setText(text);
//changing value in container
setFieldValue("textField", text);
}, 750);
};
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={e => {
onChange(e.target.value);
}}
style={{ width: "100%" }}
/>
<br />
<br />
<div>output: {text}</div>
</Form>
);
};
Using Custom Hooks
This is abstracted from the answer provided by #Skyrocker
If you find yourself using this pattern a lot you can abstract it out to a custom hook.
hooks/useDebouncedInput.js
const useDebouncedInput = ({ defaultText = '', debounceTime = 750 }) => {
const [text, setText] = useState(defaultText)
const [t, setT] = useState(null)
const onChange = (text) => {
if (t) clearTimeout(t)
setT(setTimeout(() => setText(text), debounceTime))
}
return [text, onChange]
}
export default useDebouncedInput
components/my-component.js
const MyComponent = () => {
const [text, setTextDebounced] = useDebouncedInput({ debounceTime: 200 })
return (
<Form>
<Field
type="text"
name="textField"
placeholder="Type something..."
onChange={(e) => setTextDebounced(e.target.value)}
/>
<div>output: {text}</div>
</Form>
)
}
An Example Using Redux, Fetching, and Validation
Here's a partial example of using a custom hook for a debounced field validator.
Note: I did notice that Field validation seems to not validate onChange but you can expect it onBlur when you leave the field after your debounced update has executed (I did not try racing it or with a long debounce to see what happens). This is likely a bug that should be opened (I'm in the process of opening a ticket).
hooks/use-debounced-validate-access-code.js
const useDebouncedValidateAccessCode = () => {
const [accessCodeLookUpValidation, setAccessCodeLookUpValidation] = useState()
const [debounceAccessCodeLookup, setDebounceAccessCodeLookup] = useState()
const dispatch = useDispatch()
const debouncedValidateAccessCode = (accessCodeKey, debounceTime = 500) => {
if (debounceAccessCodeLookup) clearTimeout(debounceAccessCodeLookup)
setDebounceAccessCodeLookup(
setTimeout(
() =>
setAccessCodeLookUpValidation(
dispatch(getAccessCode(accessCodeKey)) // fetch
.then(() => undefined) // async validation requires undefined for no errors
.catch(() => 'Invalid Access Code'), // async validation expects a string for an error
),
debounceTime,
),
)
return accessCodeLookUpValidation || Promise.resolve(undefined)
}
return debouncedValidateAccessCode
}
some-component.js
const SomeComponent = () => {
const debouncedValidateAccessCode = useDebouncedValidateAccessCode()
return (
<Field
type="text"
name="accessCode"
validate={debouncedValidateAccessCode}
/>
)
}