I'm trying to handle the onFocus and onBlur events for 2 elements - the input and the textarea elements. I even tried to implement it as one state with the object but it's just not possible, so I separated it into two states. Whenever I try to focus on the textarea, it'll expand, however, if I start focusing on the input element, it'll collapse instead of staying expanded. How do I implement this?
const [titleFocused, setTitleFocus] = useState(null);
const [contentFocused, setContentFocus] = useState(null);
function handleFocus(event) {
const {name} = event.target;
setTitleFocus(name === 'title' && true);
setContentFocus(name === 'content' && true);
}
function handleUnfocus(event) {
const {name} = event.target;
setTitleFocus(name === 'title' && false);
setContentFocus(name === 'content' && false);
}
return (
<div >
<form
className="create-note">
{titleFocused || contentFocused && (
<input
name="title"
onChange={handleChange}
value={note.title}
placeholder="Title"
onFocus={handleFocus}
onBlur={handleUnfocus}
/>)
}
<textarea
name="content"
onChange={handleChange}
value={note.content}
placeholder="Take a note..."
onFocus={handleFocus}
onBlur={handleUnfocus}
rows={titleFocused || contentFocused ? "3" : "1"}
/>
<Zoom in={titleFocused || contentFocused} appear={true}>
<Fab onClick={submitNote}>
<AddIcon fontSize="large" />
</Fab>
</Zoom>
</form>
</div>
);
Probably caused by event handles toggling both states even when the event is not concerning the component.
This should work
function handleFocus(event) {
const {name} = event.target;
name === 'title' && setTitleFocus(true);
name === 'content' && setContentFocus(true);
}
function handleUnfocus(event) {
const {name} = event.target;
name === 'title' && setTitleFocus(false);
name === 'content' && setContentFocus( false);
}
Related
I have a form. In the form I getting brandName, supplierName, and date of expiry from the user.
I am usinf TextField from mui library and submit button.
I want to disable submit button on empty form fields and enable it when user filled all the inputs
here is my code for declaring useStates
const[brandName, setBrandName] = useState("");
const[supplierName, setSupplierName] = useState("");
const[expiryDate, setExpiryDate] = useState(null);
const[brandNameError, setBrandNameError] = useState(false);
const[supplierNameError, setSupplierNameError] = useState(false);
const[expiryDateError, setExpiryDateError] = useState(false);
const[submitButton, setSubmitButton] = useState(true);
and here is all function which i used to validate my inputs
// checking brandName Error
const brandNameValidateOnBlur = ()=>{
if(brandName === ""){
setBrandNameError(true);
}
}
// checking supplier name error
const supplierNameValidateOnBlur = ()=>{
if(supplierName === ""){
setSupplierNameError(true);
}
}
// checking expiry date
const expiryDateValidateOnBlur = ()=>{
if(expiryDate === ""){
setExpiryDateError(true);
}
}
// now checking all inputs again if all inputs are good then
// button should be enabled
const checkAllInputs = ()=>{
if(brandName !== "" && supplierName !== "" && expiryDate !== ""){
setSubmitButton(false);
}else{
setSubmitButton(true);
}
}
and here is the rest of code
<TextField fullWidth id="productName" label="Product Name"
value={brandName.toLowerCase()} variant="outlined"
onChange={(data)=>{setBrandName(data.target.value.toUpperCase());checkAllInputs()}}
onBlur={brandNameValidateOnBlur}
onFocus={()=>setBrandNameError(false)}
error={brandNameError}
helperText = {brandNameError ? "Enter Brand Name" : ""}
/>
<TextField id="supplierName"
label="Supplier Name"
value={supplierName.toLowerCase()} variant="outlined"
onBlur={supplierNameValidateOnBlur}
onFocus={()=>setSupplierNameError(false)}
error={supplierNameError}
helperText={supplierNameError ? "Enter Supplier Name " : ""}
onChange={(data)=>{setSupplierName(data.target.value.toUpperCase());checkAllInputs()}} />
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DesktopDatePicker
label="Date Expiry"
inputFormat="MM/DD/YYYY"
value={expiryDate}
onBlur={expiryDateValidateOnBlur}
onFocus={()=>setExpiryDateError(false)}
error={expiryDateError}
helperText={expiryDateError ? "Enter Expiry Date ": ""}
onChange={(selectedDate)=>
{setExpiryDate(selectedDate.format("MM/DD/YYYY"));checkAllInputs()}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
<Button type='button' variant='contained' id="submitButton" disabled={submitButton}
style={{backgroundColor:'orangered'}} onClick={addData}>Add Data</Button>
now problem is when i enter a single word my useState is updated but my checkAllInputs not working as i want to be like if i enter a word my checkAllInputs method runs before setting of state as i enter second entry then it works like I want to be so i did not know what i am doing wrong
I TRY useEffect hook like that
useEffect(()=>{
checkAllInputs();
},[brandName, supplierName, expiryDate]);
its working fine as i accepted but I read that it should be a expensive to use useEffect
and an other approach which i use
const checkAllInputsWithDom = ()=>{
let brandNameEntry = document.getElementById("brandName").value;
let supplierNameEntry = document.getElementById("supplierName").value;
let expiryDateEntry = document.getElementById("expiryDate").value;
if(brandNameEntry !=="" && supplierNameEntry !== "" && expiryDateEntry !== ""){
setSubmitButton(false);
}else{
setSubmitButton(true);
}
}
it is working out of box but I thing in react this is against react principles to direct manipulate
dome elements can I do it with out using useeffect hook like only with my metohd and one more thing
this did not set expiryDate error in date field
As long as you're keeping the input values in state, you don't need another variable storing the button state as well. You can calculate it at render time. (Read more about avoiding redundant state here.)
You could do something like this:
const Component = () => {
const[brandName, setBrandName] = useState("");
const[supplierName, setSupplierName] = useState("");
const[expiryDate, setExpiryDate] = useState("");
const disabled = brandName.length === 0 && supplierName.length === 0 && expiryDate.length === 0
return (
<>
<input value={brandName} onChange={(e) => setBrandName(e.target.value)}/>
<input value={supplierName} onChange={(e) => setSupplierName(e.target.value)}/>
<input value={expiryDate} onChange={(e) => setExpiryDate(e.target.value)}/>
<button disabled={disabled}>Button</button>
</>
)
}
here is the link for codesandbox I have a suggestion for you to refactor this 6 use States with just one useState as an object having all the values like the following:
const initialValues = {
brandName: "",
supplierName: "",
expiryDate: "",
brandNameError: "",
supplierNameError: "",
expiryDateError: ""
};
and then update it with a generic handleInputChange like below:
const handleInputChange = (e) => {
//const name = e.target.name
//const value = e.target.value
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
let isEmpty = Object.values(values).some((x) => x === "");
console.log(isEmpty);
setIsDisabled(isEmpty);
};
FOR YOUR SOLUTION
You will have to just check all the values and just create a boolean with isDisabled so when all of those values are empty then it will only set to false.
I have created a code snippet for you, it needs some improvement but it will serve your purpose. Link already at the top and here as well
as suggested by Eduardo Motta de Moraes
I used this for making my button enabled after all inputs validate
const disabled= brandName.length === 0 || supplierName.length === 0 || expiryDate.length === 0;
this solve my problem
thanks for Eduardo Motta de Moraes for this
I have input feild which takes a input (interest) from user and after hitting the Enter key adds the interest to the interests array. Then the elements in this array are displayed on screen via the Domain component as the user goes on adding. The Domain component contains an icon X (cross) which on click should delete the selected/clicked element from the array. Right now the last element in the array is getting removed after clicking.
How can I resolve this? Here is the code:
function Demo() {
const [interest, setinterest] = useState("");
const [interests, setinterests] = useState([]);
const domainSelection = (e) => {
if (e.key === "Enter" && interest.length > 0) {
setinterests((interests) => [...interests, interest]);
setinterest("");
}
};
const RemoveDomain = (e) => {
var arr = [...interests];
var index = arr.indexOf(e.target.value);
arr.splice(index, 1);
setinterests(arr);
};
const Domain = ({ interest }) => {
return (
<span>
{interest}
<span>
<X onClick={RemoveDomain} />
</span>
</span>
);
};
return (
<div>
<Input
name="intersts"
type="text"
placeholder="eg Machine Learning .. "
value={interest}
required={true}
onChange={(e) => setinterest(e.target.value)}
className="interest-input inputs"
onKeyDown={domainSelection}
/>
{interests.map((interest, i) => (
<Domain
interest={interest}
// Prevent duplicate keys by appending index:
key={interest + i}
/>
))}
</div>
);
}
export default Demo;
I think e.target.value is undefined.
Use Filter, This might help
const RemoveDomain = (value) => {
var arr = interests.filter((item) => item !== value);
setinterests(arr);
};
const Domain = ({ interest }) => {
return (
<span>
{interest}
<span>
<X onClick={() => RemoveDomain(interest)} />
</span>
</span>
);
};
I tried to implement something like a multi-select, where the user can either select a value from a data list or can type in a new value. A chosen value should be added to an array if the user presses enter. For detecting changes in the input field I use onChange and a state variable that saves the current value typed in. For detecting the press of enter I use onKeyDown. The problem is that I'm no longer able to type something in the input field, however choosing values from the data list works. I figured out that when I comment out onKeyDown, I can type something in the input field and can also choose from values provided by the data list. However, in this case, adding values to an array on the press of enter doesn't work. I'm fairly new to React, is there something I miss?
My current code looks like follows:
const EditableMultiSelect = ({ field, helpers, metadataField, editMode, setEditMode }) => {
const { t } = useTranslation();
const [inputValue, setInputValue] = useState('');
const handleChange = e => {
const itemValue = e.target.value;
setInputValue(itemValue);
}
const handleKeyDown = event => {
event.preventDefault();
if (event.keyCode === 13) {
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
const removeItem = () => {
console.log('to be implemented');
}
return (
editMode ? (
<>
<div
onBlur={() => setEditMode(false)}
ref={childRef}>
<input name="inputValue"
value={inputValue}
type="text"
onKeyDown={e => handleKeyDown(e)}
onChange={e => handleChange(e)}
placeholder={t('EDITABLE.MULTI.PLACEHOLDER')}
list="data-list"
/>
<datalist id="data-list">
{metadataField.collection.map((item, key) => (
<option key={key}>{t(item.value)}</option>
))}
</datalist>
</div>
{(field.value instanceof Array && field.value.length !== 0) ? (field.value.map((item, key) => (
<span className="ng-multi-value"
key={key}>
{t(item)}
<a onClick={() => removeItem(key)}>
<i className="fa fa-times" />
</a>
</span>
))) : null}
</>
) : (
<div onClick={() => setEditMode(true)}>
{(field.value instanceof Array && field.value.length !== 0) ? (
<ul>
{field.value.map((item, key) => (
<li key={key}>
<span>{item}</span>
</li>
))}
</ul>
) : (
<span className="editable preserve-newlines">
{""}
</span>
)}
<i className="edit fa fa-pencil-square"/>
</div>
)
);
};
You're calling event.preventDefault() every time a key is pressed. You should move it inside the if statement:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
you can't type anything anymore in the input text because in the handleKeyDown event handler, you're calling event.preventDefault() in the early lines. So i think you just have to move it into the if case:
const handleKeyDown = event => {
if (event.keyCode === 13) {
event.preventDefault();
field.value[field.value.length] = inputValue;
helpers.setValue(field.value);
setInputValue("");
}
}
Remove e.preventDefault() or put it inside the if statements.
It is the one preventing the input from being editable.
I am making an application in React JS. It consists of list of the user for which book is available, taken or requested, but when the book is filtered from the store based on user the line of the invalid user still arrives.
return (
<div>
<h1>List of Books</h1>
{filterValues.map((books) => (
<Segment.Group key={books.id}>
{(books.name === user!.username || books.name === null) &&
(books.requestedBy === user!.username ||
books.requestedBy === null) ? (
<Segment>
<Item.Group>
<Item>
{console.log(books)}
<Item.Image size="tiny" circular src="/assets/books.jpg" />
<Item.Content>
<Item.Header as="a">{books.bookName}</Item.Header>
<Item.Description>
{books.isRequested ? (
<Button
name={books.bookName}
loading={target === books.bookName && submitting}
onClick={(e) => onRequestClick(e, "cancel", books.id)}
color="red"
type="button"
content="Cancel Request"
/>
) : books.isTaken ? (
<div>
<Label basic color="red">
This book is taken By you
</Label>
<Button
name={`return${books.bookName}`}
loading={
target === "return" + books.bookName && submitting
}
color="brown"
onClick={(e) => returnBook(e, books.id)}
type="button"
content="Return this Book"
/>
</div>
) : (
<Button
name={books.bookName}
loading={target === books.bookName && submitting}
onClick={(e) =>
onRequestClick(e, "request", books.id)
}
color="green"
type="button"
content="Request For Book"
/>
)}
</Item.Description>
</Item.Content>
</Item>
</Item.Group>
</Segment>
) : null}
</Segment.Group>
))}
<Segment clearing></Segment>
</div>
);
For example for the list of books i filtered 5 books in map and UI is something like :
How Can i remove those line
Your filtering logic is placed within the .map prototype method itself, so when you are returning null, it's still placed within an empty <Segment.Group> element. Therefore I guess that this element provides the styles which result in rendering those lines.
If you want to truly filter the results and omit any returns for the ones that do not match, it would be best to first call .filter() on your array and omit the null values returned by map:
{
filterValues
.filter(books =>
(books.name === user!.username || books.name === null)
&& (books.requestedBy === user!.username || books.requestedBy === null)
).map(books =>
<Segment.Group key={books.id}>
// Segment items here without the conditional rendering of elements
</Segment.Group>
)
}
I think this is because you are checking under <Segment.Group:
{filterValues.map(books => (
<Segment.Group key={books.id}>
{((books.name === user!.username || books.name === null) && (books.requestedBy === user!.username || books.requestedBy === null))
? /* CREATE THE ITEM */
: null
}
</Segment.Group>
))}
Thus, when it is evaluated to null, it still creates a <Segment.Group> which is shown as empty item in UI.
I'm trying to change the state of opening/closing times (and then post to an endpoint) on multiple days which are returned to me in an object like so:
I have rendered these in a React component:
{this.state.time.map(each => (
<Fragment key={each.code}>
<OpeningHours
code={each.code}
day={each.description}
name={each.code}
open={each.open !== null ? each.open : '00:00'}
close={each.close !== null ? each.close : '00:00'}
onChange={this.onTimeChange}
/>
</Fragment>
))}
The user will set these times by manually editing the time input. How would I get this open or close property of the day being edited and then store that in the time state? So far I've tried this, which works, but only if there was just an opening time or one field in general. The issue arises since I have 2 fields to edit:
onTimeChange(e) {
let times = this.state.time.slice();
for(let i in time){
if(times [i].name == event.target.name){
times [i].value = event.target.value;
this.setState ({time});
break;
}
}
}
EDIT: OpeningHours component
const OpeningHours = props => (
<div className={styles.block}>
<label htmlFor={props.code} className={styles.label}>{props.day}</label>
<div className={styles.container}>
<input
type="time"
name={props.name}
value={props.open !== null ? props.open : '00:00'}
onChange={props.onChange}
className={styles.timefield}
/>
<input
type="time"
name={props.name}
value={props.close !== null ? props.close : '00:00'}
onChange={props.onChange}
className={styles.timefield}
/>
</div>
</div>
);
One way to solve this could be to pass more information to the onChange function about what is actually changing.
onTimeChange(field, e) {
let times = this.state.time.slice();
for(let i in time){
if(times [i].name == event.target.name){
if(field === 'open') {
times [i].open = event.target.value;
this.setState ({time});
break;
} else if (field === 'closed') {
times [i].closed = event.target.value;
this.setState ({time});
break;
}
}
}
This would require a slight change to your input components:
<input
type="time"
name={props.name}
value={props.open !== null ? props.open : '00:00'}
onChange={ e => props.onChange('open', e) }
className={styles.timefield}
/>
You can bind identifying information to the onChange function and then use that to make the changes.
`<input onChange={this.handleChange.bind(this, props.code)}</input>`
Here is an example of how data is bound: https://jsfiddle.net/jphuangjr/aq7mtfgo/112/