JSX element attribute not changing with onChange - javascript

I have JSX element in my React code with value attribute which is not changing correctly with onChange method.
First I am using React Hooks:
const [isBusiness, setIsBusiness] = useState(false);
I am using this constant in my JSX:
<input
type="checkbox"
value={isBusiness} //here is the constant
id="isBusiness"
onChange={businessChange}
name="check"
/>
My onChange method is like this:
const businessChange = e => {
if (e.target.value == true) {
console.log(isBusiness);
setIsBusiness(false);
} else {
console.log(isBusiness);
setIsBusiness(true);
}
};
I am seeing what happens and it is stucking at true for some reason:
The idea is to be able to get value of this checkbox (true or false) if it is clicked or not. The initial value is false.

e.target.value == true will always be false, because e.target.value is a string. Your isBusiness will be converted to "true" or "false", and both "true" == true and "false" == true are false.
Typically, you rarely use value with checkboxes, and only use value with checkboxes if you're submitting a form somewhere; it provides the value to send (by default it's "on"). Instead, use checked:
<input
type="checkbox"
checked={isBusiness}
id="isBusiness"
onChange={businessChange}
name="check"
/>
Then
const businessChange = e => {
if (e.target.checked) {
console.log(isBusiness);
setIsBusiness(true);
} else {
console.log(isBusiness);
setIsBusiness(false);
}
};
Or simply:
const businessChange = e => {
console.log(isBusiness);
setIsBusiness(e.target.checked);
};
const {useState} = React;
const Example = () => {
const [isBusiness, setIsBusiness] = useState(false);
const businessChange = e => {
console.log(isBusiness);
setIsBusiness(e.target.checked);
};
return (
<input
type="checkbox"
checked={isBusiness}
id="isBusiness"
onChange={businessChange}
name="check"
/>
);
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
Note: It's slightly confusing with the logging, because you're always logging the old value. :-) Here's a version logging the new value:
const {useState} = React;
const Example = () => {
const [isBusiness, setIsBusiness] = useState(false);
const businessChange = e => {
console.log(e.target.checked);
setIsBusiness(e.target.checked);
};
return (
<input
type="checkbox"
checked={isBusiness}
id="isBusiness"
onChange={businessChange}
name="check"
/>
);
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
I would lean, however, toward not using the checkbox flag at all in the handler, as shown by keikai though they don't show correct setting of the checkbox. Here's a version logging the new value (not the old):
const {useState} = React;
const Example = () => {
const [isBusiness, setIsBusiness] = useState(false);
const businessChange = e => {
console.log(!isBusiness);
setIsBusiness(!isBusiness);
};
return (
<input
type="checkbox"
checked={isBusiness}
id="isBusiness"
onChange={businessChange}
name="check"
/>
);
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

You can set it proper and simple without using the event
const businessChange = () => {
setIsBusiness(!isBusiness);
};
const App = () => {
const [isBusiness, setIsBusiness] = React.useState(false);
const businessChange = () => {
console.log(!isBusiness);
setIsBusiness(!isBusiness);
};
return (
<div className="App">
<input
type="checkbox"
value={isBusiness} //here is the constant
id="isBusiness"
onChange={businessChange}
name="check"
/>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Related

How to debounce an input which is bound to a state value?

I am trying to debounce an input. I have memoized the debounce handler so that it does not change references on each render. I need the input to be bound to a state value as I need to set it elsewhere in my app. The issue is that the input value is never able to be updated as when inside the debounced changeHandler e.target.value always contains previous value, not a new value entered. How can i debounce the input that is bound to a state value?
xport function App() {
const [query, setQuery] = useState("a value");
const changeHandler = (event) => {
console.log(event.target.value); // wrong value
setQuery(event.target.value);
};
const debouncedChangeHandler = useCallback(debounce(changeHandler, 1000), []);
return (
<div>
<input
value={query}
onChange={debouncedChangeHandler}
type="text"
placeholder="Type a query..."
/>
</div>
);
}
sandbox: https://codesandbox.io/s/react-debounce-5pidiy?file=/src/index.js:130-596
I wouldn't try to debounce the keyboard input itself, I'd just wait to validate until the value hadn't changed for while, and then either directly validate it:
const { useState, useEffect, useCallback } = React;
function App() {
const [query, setQuery] = useState("a value");
const changeHandler = (event) => {
setQuery(event.target.value);
};
const validate = useCallback((query) => {
console.log(`Validating "${query}"...`);
});
useEffect(() => {
const timer = setTimeout(() => {
validate(query)
}, 1000);
return () => {
clearTimeout(timer);
}
}, [query]);
return (
<div>
<input
value={query}
onChange={changeHandler}
type="text"
placeholder="Type a query..."
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
...or use a separate state member for it:
const { useState, useEffect, useCallback } = React;
function App() {
const [rawQuery, setRawQuery] = useState("a valid value");
const [query, setQuery] = useState(rawQuery);
const changeHandler = (event) => {
console.log(`raw query: ${event.target.value}`);
setRawQuery(event.target.value);
};
const validate = useCallback((query) => {
console.log(`Validating "${query}"...`);
return query.includes("valid");
});
useEffect(() => {
const timer = setTimeout(() => {
console.log(`validate: ${rawQuery}`);
if (validate(rawQuery)) {
console.log("Valid!");
setQuery(rawQuery);
} else {
console.log("Invalid!");
setRawQuery(query);
}
}, 1000);
return () => {
clearTimeout(timer);
}
}, [rawQuery, query]);
return (
<div>
<input
value={rawQuery}
onChange={changeHandler}
type="text"
placeholder="Type a query..."
/>
<div>Value to validate: {query}</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
That makes it easier to do what you said in a comment you wanted, putting the input back to the previous valid value if an invalid one has provided.
(That updating can be wrapped in a hook for reuse.)

How to get each user's keystroke when he pressed a certain key?

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

Get the value from input when button is clicked

Having this code snippet:
const MyComponent = ({ e }) => {
const toggleButton = (e) => {
console.log('eee: ', e.target.value);
};
return (
<div>
<div>
<input className='input-search' type='text' placeholder='search' />
<Button onClick={(e) => toggleButton(e)}>reset</Button>
</div>
</div>
);
};
I was expecting to see in the log the value from input but it is undefined. Any thoughts?
e.target is the button which has no value attribute. You should be storing the input value in state with a onChange event. Make sure that the input's value attribute reflects the state, and use useEffect to log the changes to the console.
const {useState, useEffect} = React;
function Example() {
const [input, setInput] = useState('');
useEffect(() => {
console.log(`State: ${input}`);
}, [input])
function handleClick() {
setInput('');
}
function handleInput(e) {
const { target: { value } } = e;
setInput(value);
}
return (
<div>
<div>
<input onChange={handleInput} type="text" placeholder="search" value={input} />
<button onClick={handleClick}>reset</button>
</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

React checkbox state with id or other attributes instead of name

In react, is it possible to manage the state of multiple checkboxes with id attribute or other attributes such as data-*?
For the moment all I'm using is the name attribute, however in my project I need to use the id or preferably data-* due to the complexity of the project, but it seems that in react it's isn't possible.
Or am I not understanding in?
import React, { useState } from 'react';
import someData from './someData'
function Checkbox({ name, id, label, checked, onChange }) {
return (
<span>
<input
type="checkbox"
name={name}
id={id}
checked={checked}
onChange={onChange}
/>
<span>{label}</span>
</span>
);
}
function App() {
const [isChecked, setIsChecked] = useState()
const onCheckboxChange = event => {
const target = event.currentTarget;
const name = target.name
const id = target.id;
const checked = target.checked;
setIsChecked({
...isChecked,
[id]: checked // using "id" seems to not work here.
})
}
return (
someData.map(item => {
return (
<Checkbox
name={item.name}
id={item.id}
label={item.name}
onChange={onCheckboxChange}
checked={isChecked}
/>
);
}
);
}
There's no problem to do what you need in React whatsoever.
You may use dataset API to access data-* attributes values to update your state on checkbox change (e.g. if you assign data-chkboxname attribute to your checkbox):
onCheckboxChange = ({target:{checked, dataset:{chkboxname}}}) => {
setIsChecked({
...isChecked,
[chkboxname]: checked
})
}
Following is a quick proof-of-a-concept live-demo:
const { useState } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const App = () => {
const [isChecked, setIsChecked] = useState({}),
onCheckboxChange = ({target:{checked, dataset:{chkboxname}}}) => {
setIsChecked({
...isChecked,
[chkboxname]: checked
})
},
onFormSubmit = e => {
e.preventDefault()
console.log(isChecked)
}
return (
<form onSubmit={onFormSubmit}>
{
['chckbox1', 'chckbox2', 'chckbox3'].map(checkbox => (
<label key={checkbox}>
{checkbox}
<input
type="checkbox"
data-chkboxname={checkbox}
onChange={onCheckboxChange}
checked={isChecked[checkbox]}
/>
</label>
))
}
<input type="submit" value="submit" />
</form>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Try like this
return someData.map((item) => {
return (
<Checkbox
name={item.name}
id={item.id}
label={item.name}
checked={item.checked}
/>
);
});

React useImperativeHandle and forwardRef being set, the reference doesn't seem to be updated

I need to access the location of a child component. For what I understand, to access the child properties, I need to use useImperativeHandle to add the child API to its ref. Moreover, I need to use forwardRef to transmit the reference from the parent to the child. So I did this:
const Text = React.forwardRef(({ onClick }, ref) => {
const componentAPI = {};
componentAPI.getLocation = () => {
return ref.current.getBoundingClientRect ? ref.current.getBoundingClientRect() : 'nope'
};
React.useImperativeHandle(ref, () => componentAPI);
return (<button onClick={onClick} ref={ref}>Press Me</button>);
});
Text.displayName = "Text";
const App = () => {
const ref = React.createRef();
const [value, setValue] = React.useState(null)
return (<div>
<Text onClick={() => setValue(ref.current.getLocation())} ref={ref} />
<div>Value: {JSON.stringify(value)}</div>
</div>);
};
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
As you can see, the ref doesn't have the getBoundingClientRect property, but if I do this it will work as expected:
const App = () => {
const ref = React.createRef();
const [value, setValue] = React.useState(null)
return (<div>
<button ref={ref} onClick={() => setValue(ref.current.getBoundingClientRect()) } ref={ref}>Press Me</button>
<div>Value: {JSON.stringify(value)}</div>
</div>);
};
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
So what is wrong with my understanding of useImperativeHanedle and forwardRef?
To use useImperativeHandle you need to work with another ref instance like so:
const Text = React.forwardRef(({ onClick }, ref) => {
const buttonRef = React.useRef();
React.useImperativeHandle(
ref,
() => ({
getLocation: () => buttonRef.current.getBoundingClientRect()
}),
[buttonRef]
);
return (
<button onClick={onClick} ref={buttonRef}>
Press Me
</button>
);
});
If you want your logic to be valid (using the same forwarded ref), this will work:
const Text = React.forwardRef(({ onClick }, ref) => {
React.useEffect(() => {
ref.current.getLocation = ref.current.getBoundingClientRect;
}, [ref]);
return (
<button onClick={onClick} ref={ref}>
Press Me
</button>
);
});
Why your example doesn't work?
Because ref.current.getBoundingClientRect not available in a moment of assigning it in useImperativeHandle (try logging it) because you actually overridden the button's ref with useImperativeHandle (Check Text3 in sandbox, the ref.current value has getLocation assigned after the mount).
As shown in docs(maybe not understandable enough), the child component itself should have a different ref, and by useImperativeHandle you can define a function mapping forwardedRef to child ref:
import React from 'react'
import ReactDOM from 'react-dom'
const Text = React.forwardRef(({ onClick }, ref) => {
const buttonRef = React.useRef() // create a new ref for button
const componentAPI = {};
componentAPI.getLocation = () => {
return buttonRef.current.getBoundingClientRect ? buttonRef.current.getBoundingClientRect() : 'nope' // use buttonRef here
};
React.useImperativeHandle(ref, () => componentAPI); // this maps ref to buttonRef now
return (<button onClick={onClick} ref={buttonRef}>Press Me</button>); // set buttonRef
});
Text.displayName = "Text";
const App = () => {
const ref = React.useRef();
const [value, setValue] = React.useState(null)
return (<div>
<Text onClick={() => setValue(ref.current.getLocation())} ref={ref} />
<div>Value: {JSON.stringify(value)}</div>
</div>);
};
ReactDOM.render(<App />, document.querySelector("#app"))
I just wanted to add this answer to show how things can become easier when removing useless overcontrol...
const Text = React.forwardRef(({ onClick }, ref) => {
ref.getLocation = () => ref.current && ref.current.getBoundingClientRect()
return (<button onClick={onClick} ref={ref}>Press Me</button>);
});
Text.displayName = "Text";
function App() {
const ref = { current: null };
const [value, setValue] = React.useState(null)
return (<div>
<Text onClick={() => setValue(ref.getLocation())} ref={ref} />
<div>Value: {JSON.stringify(value)}</div>
</div>);
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>
In the code above, we just use forwardRef and attach the child API to it's ref, which seems very natural in the end, and very userfriendly.
The only thing that would prevent you using this is that React.createRef makes a call to Object.preventExtension() (thanks for making my life harder...), so the hack is to use { current: null } instead of Object.createRef() (which is basically the same).

Categories