I want to be able to select a radio button onChange viakeydown and mouse click. But I am having trouble accessing both event parameters of the on keydown and on mouse click in the same function. So can't update my state to store selected value on keydown but it works on mouse click because I can access the input event parameter on mouse click.
Here is the code.
import { useState, useEffect, useRef } from "react";
import { useLocation } from "react-router-dom";
import { englishQuestions } from "../questions/englishQ";
const English = () => {
const theRef = useRef();
const { state } = useLocation();
const { user } = state;
const [input, setInputs] = useState<{ [key: string]: string }[]>([]);
const [answersSelected, setAnswersSelected] = useState({});
const [selected, setSelected] = useState<{ [key: string]: boolean }>({});
const [submitted, setSubmitted] = useState(false);
const [keyCode, setKeyCode] = useState("");
var index1 = 0;
const answers = englishQuestions.map((items, index) => items.answer);
//This is the `onChange` `function` from which I can't access `keydown` event
const handleChange = (e: any) => {
const numbers = /\d+/g;
switch (e.code) {
case "keyA":
setKeyCode("keyA");
const { value, name } = e.target;
const currentQuestionNumber = name.slice(name.indexOf("englishQ"), 10);
const currentQuestion = name.slice(name.indexOf(":") + 2);
const currentArrayIndex = name.match(numbers)[0] - 1;
if (answers[currentArrayIndex] === value) {
setInputs([
...input,
{
questionNumber: currentQuestionNumber.trim(),
question: currentQuestion,
answer: value,
status: "correct",
marks: "2",
},
]);
setAnswersSelected({ ...answersSelected, [name]: value });
} else {
setInputs([
...input,
{
questionNumber: currentQuestionNumber.trim(),
question: currentQuestion,
answer: value,
status: "wrong",
marks: "0",
},
]);
setAnswersSelected({ ...answersSelected, [name]: value });
}
//detect if an answer was selected
setSelected({ ...selected, [currentArrayIndex + 1]: true });
//convert the answered object keys into an array
const submittedKeys = Object.keys(selected);
//check if all answers were selected
const checkSelelction = submittedKeys.filter(
(key, index) => selected[key] === true
);
if (checkSelelction.length + 1 >= 10) {
setAllSelected(true);
} else {
setAllSelected(false);
}
break;
case "keyB":
setKeyCode("keyB");
break;
case "keyC":
setKeyCode("keyC");
break;
case "keyD":
setKeyCode("keyD");
break;
default:
setKeyCode("");
}
setSelectedValue([...selectedValue, { theSelectedValue: e.target.value }]);
localStorage.setItem("theSelectedValue", JSON.stringify(selectedValue));
console.log(localStorage.getItem("theSelectedValue") as string);
const { value, name } = e.target;
const currentQuestionNumber = name.slice(name.indexOf("englishQ"), 10);
const currentQuestion = name.slice(name.indexOf(":") + 2);
const currentArrayIndex = name.match(numbers)[0] - 1;
if (answers[currentArrayIndex] === value) {
setInputs([
...input,
{
questionNumber: currentQuestionNumber.trim(),
question: currentQuestion,
answer: value,
status: "correct",
marks: "2",
},
]);
setAnswersSelected({ ...answersSelected, [name]: value });
} else {
setInputs([
...input,
{
questionNumber: currentQuestionNumber.trim(),
question: currentQuestion,
answer: value,
status: "wrong",
marks: "0",
},
]);
setAnswersSelected({ ...answersSelected, [name]: value });
}
//detect if an answer was selected
setSelected({ ...selected, [currentArrayIndex + 1]: true });
//convert the answered object keys into an array
const submittedKeys = Object.keys(selected);
//check if all answers were selected
const checkSelelction = submittedKeys.filter(
(key, index) => selected[key] === true
);
if (checkSelelction.length + 1 >= 10) {
setAllSelected(true);
} else {
setAllSelected(false);
}
};
const submit = (e: any) => {
e.preventDefault();
setSubmitted(true);
localStorage.setItem("studentId", JSON.stringify(user.studentId));
localStorage.setItem("englishSubmitted", "true");
localStorage.setItem("englishAnswers", JSON.stringify(input));
};
//Here is the call to the event listener
useEffect(() => {
document.body.addEventListener("keydown", handleChange);
}, [input]);
return (
<>
<div className="bg-gray-200 mx-auto my-10 w-3/4 p-10 shadow-sm shadow-gray-600 rounded-lg">
<form onSubmit={submit} className="mb-10">
{englishQuestions.map((question) => {
index1 = index1 + 1;
return (
<div
key={index1}
className={index1 === theValue.first ? "" : "hidden"}
>
<h3>
<span className={submitted ? "text-gray-400" : "font-bold"}>
Q {index1}:
</span>{" "}
{question.start}{" "}
<span className="underline">{question.theWord}</span>{" "}
{question.end}
</h3>
{question.options.map((item, index) => {
return (
<div className="mb-3" key={index}>
<label htmlFor="">a: </label>
<input
id="a"
type="radio"
name={`englishQ${index1} : ${question.start} ${question.theWord} ${question.end}`}
value={item.a}
onChange={handleChange}
disabled={submitted ? true : false}
checked={keyCode === "KeyA" ? true : false}
/>
<label htmlFor=""> {item.a}</label>
<br />
<label htmlFor="">b: </label>
<input
id="b"
type="radio"
name={`englishQ${index1} : ${question.start} ${question.theWord} ${question.end}`}
value={item.b}
onChange={handleChange}
disabled={submitted ? true : false}
checked={keyCode === "KeyB" ? true : false}
/>
<label htmlFor=""> {item.b}</label>
<br />
<label htmlFor="">c: </label>
<input
id="c"
type="radio"
name={`englishQ${index1} : ${question.start} ${question.theWord} ${question.end}`}
value={item.c}
onChange={handleChange}
disabled={submitted ? true : false}
checked={keyCode === "KeyC" ? true : false}
/>
<label htmlFor=""> {item.c}</label>
<br />
<label htmlFor="">d: </label>
<input
id="d"
type="radio"
name={`englishQ${index1} : ${question.start} ${question.theWord} ${question.end}`}
value={item.d}
onChange={handleChange}
disabled={submitted ? true : false}
checked={keyCode === "KeyD" ? true : false}
/>
<label htmlFor=""> {item.d}</label>
</div>
);
})}
</div>
);
})}
<div className="flex justify-center">
<button
type="submit"
name="submit"
className="btnExam p-3 mx-auto cursor-pointer"
>
Submit
</button>
</div>
</form>
</div>
</>
);
};
export default English;
You should use the onKeyDown property on the input instead of the onChange to handle the key press.
E.g
<input onKeyDown={handleKeyPress} />
...
const handleKeyPress = (e) => { if (e.key === 'Backspace') ... }
Then you can use the onClick and onKeyDown to do whatever you want.
Related
I want to create an edit form so that I can be able to edit a contact, and update it using react-router. I passed in the state through the router link, then I receive it using the useLocation. But the problem I am facing right now is that I do not know how to use the received data to initialize my form when I click on any contact to edit. I meant, if I should click on edit, it should load the information on that contact I click on, then I will update it.
This is where I pass in the state to the edit button
import classes from './ContactItem.module.css'
import { Fragment, useState} from 'react'
import CustomButton from '../UI/CustomButton'
import {Link} from 'react-router-dom'
import {BiEdit} from 'react-icons/bi'
import {RiDeleteBinLine} from 'react-icons/ri'
import DeleteContact from '../UI/DeleteContact'
const ContactItem = (props) => {
const { id, firstName, lastName, phone, email} = props.contact;
const [show, setShow] = useState(false);
const handleShow = () => {
setShow(true);
}
return (
<Fragment >
<div className={classes.container}>
<div className={classes.name}>
<div>{firstName}</div>
<div>{lastName}</div>
</div>
<div className={classes.utilities}>
<div className={classes.contact}>
<div className={classes.email}>{email}</div>
<div className={classes.phone}>{phone}</div>
</div>
<div className={classes.utility}>
<Link to={`/edit/${id}`} state={props.contact }>
<div className={classes.edit}>
<BiEdit />
</div>
</Link>
<div className={classes.delete}>
<RiDeleteBinLine onClick={handleShow} />
</div>
</div>
</div>
<hr className={classes.hr}/>
</div>
<div className={classes.button}>
<Link to={`/${id}`}>
<CustomButton>view Details</CustomButton>
</Link>
</div>
<DeleteContact show={show} setShow={setShow} onDelete={id} />
</Fragment>
)
}
export default ContactItem
The below code is where I receive the data that is passed through the link. This is also the editForm components. I am using custom hook for my edit form component
import React from 'react'
import useInput from '../formHooks/hooks/use-input'
import './EditForm.css'
import LoadingSpinner from './LoadingSpinner'
import {useLocation} from 'react-router-dom'
const EditForm = (props) => {
const location = useLocation();
const data = location.state;
console.log(data)
const {
value: enteredFirstName,
isValid: enteredFirstNameIsValid,
hasError: firstNameInputHasError,
InputBlurHandler: firstNameBlurHandler,
valueChangeHandler: firstNameChangedHandler,
reset: resetFirstNameInput
} = useInput(value => value.trim() !== "")
const {
value: enteredLastName,
isValid: enteredLastNameIsValid,
hasError: lastNameInputHasError,
InputBlurHandler: lastNameBlurHandler,
valueChangeHandler: lastNameChangedHandler,
reset: resetLastNameInput
} = useInput(value => value.trim() !== "")
const {
value: enteredEmail,
isValid: enteredEmailIsValid,
hasError: emailInputHasError,
InputBlurHandler: emailBlurHandler,
valueChangeHandler: emailChangedHandler,
reset: resetEmailInput
} = useInput(value => value.includes('#'))
const {
value: enteredPhoneNumber,
isValid: enteredPhoneNumberIsValid,
hasError: phoneNumberInputHasError,
InputBlurHandler: phoneNumberBlurHandler,
valueChangeHandler: phoneNumberChangedHandler,
reset: resetPhoneNumberInput
} = useInput(value => value.trim() !== "")
const {
value: enteredAddress1,
isValid: enteredAddress1IsValid,
hasError: address1InputHasError,
InputBlurHandler: address1BlurHandler,
valueChangeHandler: address1ChangedHandler,
reset: resetAddress1Input
} = useInput(value => value.trim() !== "")
const {
value: enteredAddress2,
valueChangeHandler: address2ChangedHandler,
reset: resetAddress2Input
} = useInput(value => value.trim() == "")
const {
value: enteredState,
valueChangeHandler: stateChangedHandler,
reset: resetStateInput
} = useInput(value => value.trim() == "")
const {
value: enteredCountry,
valueChangeHandler: countryChangedHandler,
reset: resetCountryInput
} = useInput(value => value.trim() == "")
const {
value: enteredFile,
valueChangeHandler: fileChangedHandler,
reset: resetFileInput
} = useInput(value => value.trim() == "")
const {
value: enteredAbout,
isValid: enteredAboutIsValid,
hasError: aboutInputHasError,
InputBlurHandler: aboutBlurHandler,
valueChangeHandler: aboutChangedHandler,
reset: resetAboutInput
} = useInput(value => value.trim() !== "")
let formIsValid = false
if (enteredFirstNameIsValid
&& enteredEmailIsValid
&& enteredLastNameIsValid
&& enteredPhoneNumberIsValid
&& enteredAddress1IsValid
&& enteredAboutIsValid
) {
formIsValid = true
} else {
formIsValid = false
}
const formSubmitHandler = (e) => {
e.preventDefault();
if (!enteredFirstNameIsValid
&& !enteredLastNameIsValid
&& !enteredEmailIsValid
&& !enteredPhoneNumberIsValid
&& !enteredAddress1IsValid
&& !enteredAboutIsValid
) {
return;
}
resetFirstNameInput();
resetEmailInput();
resetLastNameInput();
resetPhoneNumberInput();
resetAddress1Input();
resetAddress2Input();
resetStateInput();
resetCountryInput();
resetFileInput();
resetAboutInput();
props.onSaveContact({
firstName: enteredFirstName,
lastName: enteredLastName,
email: enteredEmail,
phone: enteredPhoneNumber,
address1: enteredAddress1,
address2: enteredAddress2,
state: enteredState,
country: enteredCountry,
file: enteredFile,
about: enteredAbout
})
}
const firstNameInputClasses = firstNameInputHasError ? 'form-control invalid' : 'form-control'
const emailInputClasses = emailInputHasError ? 'form-control invalid' : 'form-control'
const lastNameInputClasses = lastNameInputHasError ? 'form-control invalid' : 'form-control'
const phoneNumberInputClasses = phoneNumberInputHasError ? 'form-control invalid' : 'form-control'
const address1InputClasses = address1InputHasError ? 'form-control invalid' : 'form-control'
const aboutInputClasses = aboutInputHasError ? 'form-control invalid' : 'form-control'
return (
<form onSubmit={formSubmitHandler}>
<div className='container'>
{props.isLoading && (
<div className='loading'>
<LoadingSpinner />
</div>
)}
<div className='control-group'>
<div className={firstNameInputClasses}>
<label htmlFor='firstName'>First Name</label>
<input
type='text'
id='firstName'
onChange={firstNameChangedHandler}
onBlur={firstNameBlurHandler}
value={enteredFirstName}
/>
{firstNameInputHasError &&
<p className="error-text">First Name must not be empty</p>
}
</div>
<div className={lastNameInputClasses}>
<label htmlFor="lastName">Last Name</label>
<input
type='text'
id="lastName"
onChange={lastNameChangedHandler}
onBlur={lastNameBlurHandler}
value={enteredLastName}
/>
{lastNameInputHasError &&
<p className="error-text">Enter a valid name</p>
}
</div>
</div>
<div className={emailInputClasses}>
<label htmlFor='email'>Email Address</label>
<input
type='email'
id='email'
onChange={emailChangedHandler}
onBlur={emailBlurHandler}
value={enteredEmail}
/>
{emailInputHasError &&
<p className="error-text">Enter a valid email</p>
}
</div>
<div className={phoneNumberInputClasses}>
<label htmlFor='phoneNumber'>Phone Number</label>
<input
type='text'
inputMode='numeric'
onChange={phoneNumberChangedHandler}
onBlur={phoneNumberBlurHandler}
value={enteredPhoneNumber}
/>
{phoneNumberInputHasError &&
<p className="error-text">Number must not be empty</p>
}
</div>
<div className={address1InputClasses}>
<label htmlFor='address1'>Address 1</label>
<input
type='text'
id='address1'
onChange={address1ChangedHandler}
onBlur={address1BlurHandler}
value={enteredAddress1}
/>
{address1InputHasError &&
<p className="error-text">Address must not be empty</p>
}
</div>
<div className='form-control'>
<label htmlFor='address2'>Address 2</label>
<input
type='text'
id='address2'
onChange={address2ChangedHandler}
value={enteredAddress2}
/>
</div>
<div className='country'>
<div className='form-control'>
<label htmlFor="country">Country</label>
<input
type='text'
id='country'
onChange={countryChangedHandler}
value={enteredCountry}
/>
</div>
<div className='form-control'>
<label htmlFor="state">State</label>
<input
type='text'
id='state'
onChange={stateChangedHandler}
value={enteredState}
/>
</div>
</div>
<div className='form-control'>
<label htmlFor="file">Select Image</label>
<input
type='file'
id="file"
onChange={fileChangedHandler}
value={enteredFile}
/>
</div>
<div className={aboutInputClasses}>
<label htmlFor='about'>About</label>
<textarea
type='text'
id='about'
rows='5'
cols='50'
onChange={aboutChangedHandler}
onBlur={aboutBlurHandler}
value={enteredAbout}
/>
{aboutInputHasError &&
<p className="error-text">Your input text must not below 100 characters</p>
}
</div>
<div className='form-actions'>
<button disabled={!formIsValid}>Update</button>
<button>Cancel</button>
</div>
</div>
</form>
)
}
export default EditForm
Below is the custom hook code I am using for the form
import { useState } from "react";
const useInput = (validateValue) => {
const [enteredValue, setEnteredValue] = useState('')
const [isTouched, setIsTouched] = useState(false)
const valueIsValid = validateValue(enteredValue);
const hasError = !valueIsValid && isTouched;
const valueChangeHandler = (e) => {
setEnteredValue(e.target.value)
// setEnteredNameIsValid(true)
}
const InputBlurHandler = (e) => {
setIsTouched(true)
}
const reset = () => {
setEnteredValue('');
setIsTouched(false)
}
return {
value: enteredValue,
isValid: valueIsValid,
hasError,
valueChangeHandler,
InputBlurHandler,
reset
}
};
export default useInput;
The basic gist is that you need to pass the passed route state value(s) to the form state. Ideally you'd want to initialize the field state to a value instead of an empty string. You could try the following refactor.
const useInput = ({
initialValue = '',
validateValue = () => true,
}) => {
const [enteredValue, setEnteredValue] = useState(initialValue || '');
const [isTouched, setIsTouched] = useState(false);
const valueIsValid = validateValue(enteredValue);
const hasError = !valueIsValid && isTouched;
const valueChangeHandler = (e) => {
setEnteredValue(e.target.value);
// setEnteredNameIsValid(true);
};
const InputBlurHandler = (e) => {
setIsTouched(true);
};
const reset = () => {
setEnteredValue(initialValue);
setIsTouched(false);
};
return {
value: enteredValue,
isValid: valueIsValid,
hasError,
valueChangeHandler,
InputBlurHandler,
reset
};
};
...
const location = useLocation();
const data = location.state;
const {
value: enteredFirstName,
isValid: enteredFirstNameIsValid,
hasError: firstNameInputHasError,
InputBlurHandler: firstNameBlurHandler,
valueChangeHandler: firstNameChangedHandler,
reset: resetFirstNameInput
} = useInput({
initialValue: data.firstName,
validateValue: value => value.trim() !== "",
});
...
I want add object inside of array items
I am trying to manage objects inside of an array with useState but is not working i have only in object but I want the object in interior of the array of items. When I click add items on the button i want add the element and if possible remove this element when i click remove items in button link with inputs (see the image)
Like :
company:"Apple",
companyAdress:"5 avenue triagle",
items: [
{
itemName: Computer,
itemQuantity: 20,
itemPrice: 209
},
{
itemName: Computer,
itemQuantity: 20,
itemPrice: 209
},
]
My code :
const [info, setInfo] = useState({});
const [itemForm, setItemForm] = useState({ num: 1 })
const handleRemoveItem = (e) => {
e.preventDefault();
setItemForm((itemForm) => ({ num: itemForm.num - 1 }))
}
const handleAddItem = (e) => {
e.preventDefault();
setItemForm((itemForm) => ({ num: itemForm.num + 1 }))
}
<label>Company</label>
<input onChange={(e) => { setInfo({ ...info, company: e.currentTarget.value}); }} placeholder="Company"></input>
<label>company Adress</label>
<input onChange={(e) => { setInfo({ ...info, companyAdress: e.currentTarget.value }); }} placeholder="Adresse"></input>
<ul className="space-y-3">
{[...Array(itemForm.num)].map((x, i) => {
return (
<li key={i}>
<div>
<input onChange={(e) => { setInfo({...info,itemName: e.currentTarget.value });}} name="itemName" placeholder="itemName:" ></input>
<input onChange={(e) => { setInfo({ ...info, itemQuantity: e.currentTarget.value }); }} type="number" name="itemQuantity" placeholder="Quantity:"></input>
<input onChange={(e) => { setInfo({ ...info, itemPrice: e.currentTarget.value }); }} type="number" name="itemPrice" placeholder="Price:"></input>
<button onClick={handleRemoveItem}>Enlever </button>
<button onClick={handleAddItem}>+ Add New Item</button>
</div>
</li>
)
}
)}
</ul>
i do something like this using an id to find the iteminfo.
i am currying the itemid here but you could put the itemId as part of the input id and find it that way if you like - then you could use one function. anyway hope it helps
also i am just using the id as the key for the object you might what to be more strict on this ¯_(ツ)_/¯
i would also put the factory and defaultInfo else where in your app
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
const defaultItemFactory = () => {
return { itemName: "", itemQuantity: "", itemPrice: "", id: uuidv4() };
};
const defaultInfo = {
company: "",
companyAdress: "",
items: [defaultItemFactory()],
};
function App() {
const [info, setInfo] = useState(defaultInfo);
const changeHanlder = (event) => {
const { id, value } = event.currentTarget;
setInfo((_info) => {
return { ..._info, [id]: value };
});
};
const itemHanlder = (itemId) => (event) => {
const { id, value } = event.currentTarget;
setInfo((_info) => {
if (id === "add")
return { ..._info, items: _info.items.concat(defaultItemFactory()) };
const items = _info.items
.map((item) => {
if (item.id !== itemId) return item;
if (id === "remove") return null;
return { ...item, [id]: value };
})
.filter((out) => out);
return { ..._info, items };
});
};
return (
<div className="App">
<label>Company</label>
<input
id={"company"}
value={info.company}
onChange={changeHanlder}
placeholder="Company"
></input>
<label>company Adress</label>
<input
id={"companyAdress"}
value={info.companyAdress}
onChange={changeHanlder}
placeholder="Adresse"
></input>
<ul className="space-y-3">
{info.items &&
info.items.map((item, i) => {
return (
<li key={`item-${item.id}`}>
<div>
<input
id={"itemName"}
value={item.itemName}
onChange={itemHanlder(item.id)}
name="itemName"
placeholder="itemName:"
></input>
<input
id={"itemQuantity"}
value={item.itemQuantity}
onChange={itemHanlder(item.id)}
type="number"
name="itemQuantity"
placeholder="Quantity:"
></input>
<input
id={"itemPrice"}
value={item.itemPrice}
onChange={itemHanlder(item.id)}
type="number"
name="itemPrice"
placeholder="Price:"
></input>
<button id={"remove"} onClick={itemHanlder(item.id)}>
Enlever{" "}
</button>
<button id={"add"} onClick={itemHanlder(item.id)}>
+ Add New Item
</button>
</div>
</li>
);
})}
</ul>
</div>
);
}
export default App;
On my site, I'm using TagsInput, which allows the user to enter data into an input field, hit the enter button, and see it displayed as tags.
But I have one problem: the user can enter data with the same value as many times as he wants. I would like to restrict this ability and not allow the same data to be entered.
I already have some validation that displays a message if the user has entered an invalid data format.
Thus, I would like to add the ability to not accept data if it is already in the tags and display the corresponding message.
export default function TagsInputRequestURL(props) {
const {tags, setTags} = props;
const [input, setInput] = useState("");
const [isValid, setIsValid] = useState(true);
const onChange = (e) => {
const { value } = e.target;
if (e.target.value) {
setIsValid(() => /^(ftp|https?):\/\/[^ "]+$/.test(e.target.value));
} else {
setIsValid(true);
}
setInput(value);
};
const onSubmit = (e) => {
e.preventDefault();
if (isValid) {
setTags((tags) => [...tags, input]);
setInput("");
}
};
const deleteTag = (index) => {
setTags((prevState) => prevState.filter((tag, i) => i !== index));
};
return (
<div className={classes.container}>
{tags.map((tag, index) =>
<div className={classes.tag}>
<ClearIcon
className={classes.del}
fontSize="big"
onClick={() => deleteTag(index)}
/>
{tag}
</div>
)}
<form onSubmit={onSubmit}>
<input
className={classes.input}
value={input}
placeholder={props.inputPlaceholder}
onChange={onChange}
/>
{!isValid && <small style={{ color: "red" }}>Invalid URL</small>}
</form>
</div>
);
}
export default function TagsInputRequestURL(props) {
const {tags, setTags} = props;
const [input, setInput] = useState("");
const [isValid, setIsValid] = useState(true);
const onChange = (e) => {
const { value } = e.target;
if (e.target.value) {
setIsValid(() => /^(ftp|https?):\/\/[^ "]+$/.test(e.target.value));
} else {
setIsValid(true);
}
setInput(value);
};
const containsString = (str) => {
if(!str || str === '') return false
const strLower = str.toLowerCase();
let isExist = false
for(let i=0; i<tags.length; i++){
let itemLower = tags[i].toLowerCase();
if(strLower === itemLower){
isExist = true;
break;
}
}
return isExist;
}
const onSubmit = (e) => {
e.preventDefault();
if (isValid && !containsString(input)) {
setTags((tags) => [...tags, input]);
setInput("");
}
else{
console.log("You already hame same value in the 'tags' array. Try with different string.")
}
};
const deleteTag = (index) => {
setTags((prevState) => prevState.filter((tag, i) => i !== index));
};
return (
<div className={classes.container}>
{tags.map((tag, index) =>
<div className={classes.tag}>
<ClearIcon
className={classes.del}
fontSize="big"
onClick={() => deleteTag(index)}
/>
{tag}
</div>
)}
<form onSubmit={onSubmit}>
<input
className={classes.input}
value={input}
placeholder={props.inputPlaceholder}
onChange={onChange}
/>
{!isValid && <small style={{ color: "red" }}>Invalid URL</small>}
</form>
</div>
);
}
i am trying to create a search form using React typescript props event.I have acheived half of it but now stuck on an checkbox multiSelector where i have no idea how we can implement it.i have googled a lot but got nothing in return.
here is my code.
I am using common typescript props event onChange for setting all the values inside my search Api Object.
can anyone help me out with code or docs how we can acheive multiSelector checkbox for React Typescript props event.
1.here is my search for structure=>
enter code here
let columns = useMemo(
() => [
{
Header: "Name", accessor: "username",
Cell: (props: any) => {
if (authoritiesList.includes("USR-U")) {
let path = "/users/" + props.value;
return createClickableLink(path, props.value);
} else {
return <>
<span>{props.row.original.username}</span>
</>
}
},
FilterOptions: {
FilterInput:
<input type="text" placeholder="Username..." />,
overrideFilterLabel: "Username",
overrideFilterAccessor: "username"
}
},
{
Header: "Role(s)", accessor: "roles", disableSortBy: true,
Cell: (props: any) => {
return <>
{props.row.original.roles.map((role: any) => {
return (
<div>
<span>{role}</span><br/>
</div>)
})}
</>
},
FilterOptions: {
FilterSelect:
roleData.items.map((curRole:any)=>{
return (
<input type="checkbox value=
{curRole.name} />
)
})} ,
overrideFilterLabel: "Roles",
overrideFilterAccessor: "roles"
}
},
},
], [customerData,roleData]
)
enter code here
const selector = (state: any) => state.users;
return (
<div className="m-0 p-0 ">
<section id="content-wrapper">
<div className="row">
<div className="col-lg-12 ml-auto">
<Breadcrumb crumbs={crumbs}/>
<div className="table_data mt-2">
{createSearchButton()}
{authoritiesList.includes("USR-C") && createAddButton("/users/create", "Add User")}
<DataTable columns={columns}
fetchAction={userActions.getAllData as Dispatch<Action>}
exportAction={userActions.exportData as Dispatch<Action>}
selector={selector}/>
</div>
</div>
</div>
</section>
</div>
);
}
I want to handle multi selected checkbox event for this form in
Typescript. all forms input tags are working currently but multiselected checkbox is not working for brining output to the query object.
here is my typescript code.
for (let column of tableColumns) {
if (!column.FilterOptions) {
column.FilterOptions = {};
}
if (column.FilterOptions?.FilterSelect) {
column.FilterOptions.FilterSelect.props.onKeyPress = (event: KeyboardEvent) => {
event.key === 'Enter' && setApplyFilter(true);
}
column.FilterOptions.FilterSelect.props.onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
updateFilterQuerySelect(column, filterQuery, setFilterQuery, event);
}
}
if (column.FilterOptions?.FilterInput) {
column.FilterOptions.FilterInput.props.onKeyPress = (event: KeyboardEvent) => {
event.key === 'Enter' && setApplyFilter(true);
}
column.FilterOptions.FilterInput.props.onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFilterQuery(column, filterQuery, setFilterQuery, event);
}
}
}
here is function updateFilterQuery
const updateFilterQuery = (column: DataTableColumn, filterQuery: any, setFilterQuery: Function, event: React.ChangeEvent) => {
let tempQuery: any = {...filterQuery};
let key: string = column.FilterOptions?.overrideFilterAccessor || column.accessor;
let value: any = event.target.value;
if (event.target.value == "on" && event.target.checked != undefined) {
value = event.target.checked;
}
if (event.target.value == undefined) {
delete tempQuery[key];
} else {
key === 'phone' ? tempQuery[key] = getUnformattedPhoneNumber(value)
:
tempQuery[key] = value;
}
setFilterQuery(tempQuery);
}
It is a search form and similary it is working same as for other forms as well th eonly part missing in this form is now multiselector which is not working.
You have to separate selection state into a custom hook. A state is an array of selected items.
CodeSandbox
hooks.ts
import React, { useState } from "react";
export const useMultiselect = (initialValue: string[]) => {
const [selected, setSelected] = useState<string[]>(initialValue);
const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
const index = selected.indexOf(value);
if (index > -1) {
setSelected([...selected.slice(0, index), ...selected.slice(index + 1)]);
} else {
setSelected([...selected, ...[value]]);
}
};
const isSelected = (value: string) => {
return selected.includes(value);
};
return { selected, isSelected, onChange };
};
App.tsx
import { useMultiselect } from "./hooks";
const data = ["Apple", "Orange", "Banana", "Pear", "Peach"];
export default function App() {
const { selected, isSelected, onChange } = useMultiselect([]);
return (
<div>
<div>Select your favorite fruites!</div>
<ul style={{ listStyleType: "none" }}>
{data.map((value) => (
<li key={value}>
<input
id={value}
type="checkbox"
value={value}
checked={isSelected(value)}
onChange={onChange}
/>
<label htmlFor={value}>{value}</label>
</li>
))}
</ul>
<div>Selected: {selected.join()}</div>
</div>
);
}
I have a bit of an issue that’s causing size and maxArrayElements on all checkboxes selected after the first checkbox to be undefined if the input boxes are untouched. They are all set to default to 1 if not touched.
So in the sandbox, pick a dataschema, selectorField, check one box, just choose the lengthType, and hit submit. The object will come back (in the console) with the default values for size and arrayElements of 1.
Now if I check another box, just choose the lengthType, and hit submit, the size and arrayElements values come back undefined if not touched. They should default to 1. I do have a placeholder value set to 1, so it might be misleading. Specifically, the value prop on the inputs value={this.state.size[lastCheckedFieldName] || 1} handle this by setting the default value to 1 if not changed. But for some reason this isn't happening
This sandbox is reproducing the issue.
import React from "react";
import ReactDOM from "react-dom";
import { Checkbox, CheckboxGroup } from "react-checkbox-group";
import axios from "axios";
import "./styles.css";
const schemas = [{ name: "Phone Data", id: "1" }];
const data = {
data: {
id: "2147483601",
name: "Phone Data",
fields: [
{
name: "Callee #"
},
{
name: "Caller #"
},
{
name: "Duration"
}
]
}
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
schemas: [],
fields: [],
size: {},
lengthType: {},
maxArrayElements: {},
fieldNames: [],
submitDisabled: true
};
this.onDataSchemaChange = this.onDataSchemaChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
componentDidMount() {
axios({
method: "get",
url: `/list/of/schemas`
})
.then(response => {
console.log(response);
this.setState({ schemas: schemas });
})
.catch(error => console.log(error.response));
}
onDataSchemaChange = event => {
const schema = this.state.schemas.find(
schema => schema.name === event.target.value
);
if (schema) {
axios({
method: "get",
url: ``
})
.then(response => {
console.log(response);
this.setState({
fields: data.data.fields,
selectedId: data.data.id
});
console.log(this.state.selectedId);
console.log(this.state.fields);
})
.catch(error => console.log(error.response));
}
};
onSizeChange = e => {
e.persist();
const { fieldNames } = this.state;
const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
this.setState(
prevState => {
return {
size: {
...prevState.size,
[lastCheckedFieldName]: e.target.value
}
};
},
() => {
console.log(this.state.size);
}
);
console.log([e.target.name]);
};
onChangeMaxArrayElements = e => {
e.persist();
const { fieldNames } = this.state;
const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
this.setState(
prevState => {
return {
maxArrayElements: {
...prevState.maxArrayElements,
[lastCheckedFieldName]: e.target.value
}
};
},
() => {
console.log(this.state.maxArrayElements);
}
);
console.log([e.target.name]);
};
handleSelectorFieldChange = event => {
this.setState({ selectorField: event.target.value });
};
handleCancel = event => {
event.target.reset();
};
fieldNamesChanged = newFieldNames => {
this.setState({
submitDisabled: !newFieldNames.length,
fieldNames: newFieldNames,
size: {
[newFieldNames]: 1,
...this.state.size
},
maxArrayElements: {
[newFieldNames]: 1,
...this.state.maxArrayElements
}
});
console.log(this.state.size);
console.log(this.state.maxArrayElements);
};
updateSelectorField = e => {
this.setState({ selectorField: e.target.value });
};
updateLengthType = e => {
e.persist();
const { fieldNames } = this.state;
const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
console.log("e", e);
this.setState(prevState => {
const lengthType = { ...prevState.lengthType };
lengthType[lastCheckedFieldName] = e.target.value;
return {
lengthType
};
});
};
handleSubmit = event => {
event.preventDefault();
const fields = this.state.fieldNames.map(fieldName => ({
name: fieldName,
lengthType: this.state.lengthType[fieldName],
size: this.state.size[fieldName],
maxArrayElements: this.state.maxArrayElements[fieldName]
}));
console.log(fields);
};
render() {
const { schemas, fields, fieldNames, selectorField } = this.state;
const lastCheckedFieldName = fieldNames[fieldNames.length - 1];
return (
<div>
<form onSubmit={this.handleSubmit}>
<fieldset>
<legend>
<h2>QuerySchema Information</h2>
</legend>
<div className="info-boxes">
<div>
<label> Pick the dataschema to describe your data file:</label>{" "}
<select
name="schemaName"
value={this.state.value}
onChange={this.onDataSchemaChange}
>
<option value="">Choose Dataschema ...</option>
{schemas &&
schemas.length > 0 &&
schemas.map(schema => {
return <option value={schema.name}>{schema.name}</option>;
})}
</select>
</div>
</div>
</fieldset>
<fieldset>
<legend>
<h2> DataSchema Fields </h2>
</legend>
<div className="selectorField-div">
<div>
<label>Selector Field:</label>{" "}
<select
value={this.state.selectorField}
onChange={this.updateSelectorField}
required
>
<option value="">Pick Selector Field...</option>
{fields &&
fields.map(field => {
return <option value={field.name}>{field.name}</option>;
})}
</select>
</div>
{selectorField && (
<fieldset>
<div>
<legend>Choose field names</legend>
<CheckboxGroup
checkboxDepth={5}
name="fieldNames"
value={this.state.fieldNames}
onChange={this.fieldNamesChanged}
required
>
{fields &&
fields.map(field => {
return (
<li>
<Checkbox value={field.name} />
{field.name}
</li>
);
})}
</CheckboxGroup>
</div>{" "}
</fieldset>
)}
</div>
<div className="input-boxes">
{lastCheckedFieldName && (
<div>
<label>Length Type:</label>
<select
value={this.state.lengthType[lastCheckedFieldName] || ""}
onChange={this.updateLengthType}
required
>
<option value="">Select Length Type...</option>
<option value="fixed">Fixed</option>
<option value="variable">Variable</option>
</select>
</div>
)}
{lastCheckedFieldName && (
<div>
<label>Size:</label>
<input
value={this.state.size[lastCheckedFieldName] || 1}
onChange={this.onSizeChange}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</div>
)}
{lastCheckedFieldName && (
<div>
<label>MaxArray Elements:</label>
<input
value={
this.state.maxArrayElements[lastCheckedFieldName] || 1
}
onChange={this.onChangeMaxArrayElements}
type="number"
name="maxArrayElements"
placeholder="1"
min="1"
max="100"
required
/>
</div>
)}
</div>
</fieldset>
<div className="btn-group">
<span className="input-group-btn">
<button
className="btnSubmit"
handleSubmit={this.handleSubmit}
type="submit"
disabled={this.state.submitDisabled}
>
Submit{" "}
</button>
<button
className="btnReset"
handleCancel={this.handleCancel}
type="reset"
onClick={() => {
alert("Clearing current field values.");
}}
>
Reset
</button>
</span>
</div>
</form>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I have checked the output of the state at the end of your handleSubmit function, and it looks like this:
lengthType: {
"Callee #": "fixed",
"Caller #": "variable"
},
maxArrayElements: {
"Callee #,Caller #": 1,
"Callee #": 1
}
fieldNames: [
0: "Callee #"
1: "Caller #"
]
The issue here is that, the keys in both lengthType and maxArrayElements are named incorrectly. You are setting these key-value pairs onChange of the CheckboxGroup. The root of the issue is in the parameters passed to fieldNamesChanged function. CheckboxGroup always pass the Array of checked checkboxes, not just the new one. I would suggest to get only the last record from newFieldNames and add it to state.
fieldNamesChanged = newFieldNames => {
this.setState({
submitDisabled: !newFieldNames.length,
fieldNames: newFieldNames,
size: {
[newFieldNames[newFieldNames.length - 1]]: 1,
...this.state.size
},
maxArrayElements: {
[newFieldNames[newFieldNames.length - 1]]: 1,
...this.state.maxArrayElements
}
});
};