Everytime I type, i lose the focus of the input text. I have other components which are working fine; but this one is losing focus. Any idea why would this happen?
I tried creating separate components and rendering them in my switch but still not working.
I have other components rendered with Switch statements and are working fine.
const FactionDetail = ({Faction}) => {
const [tab, setTab] = React.useState(0)
const [selectedMember, setSelectedMember] = React.useState(null)
const [receipt, setReceipt] = React.useState('')
const [selectedRank, setSelectedRank] = React.useState(null)
const [newRankDetails, setNewRankDetails] = React.useState({
rankName: '',
wage: 0,
hasPerm: false
})
const menuOption = (tabId) =>{
setTab(tabId)
}
const selectMember = (e) =>{
setSelectedMember(e)
}
const renderDropdownOptions = () =>{
let i = 0
for (const rank in Faction.ranks){
i++
return <option key = {i} value = {rank}>{rank}</option>
}
}
React.useEffect(() =>{
if(selectedMember != null && selectedRank != selectedMember.rank){
setSelectedRank(selectedMember.rank)
}
}, [selectedMember])
const RightBody = () =>{
switch(tab){
case 0: // Dashboard
return <>
</>
case 1: //Members
return <> </>
case 2: //Manage
return <>
<div className ='CreateRanks'>
<label>Rank Name</label>
<input style ={{width:'80px'}} onChange = {(e) => setNewRankDetails((prev) => {return {...prev, rankName: e.target.value}})} value = {newRankDetails.rankName}></input>
<label>Wage</label>
<input style ={{width:'80px'}} onChange = {(e) => setNewRankDetails((prev) => {return {...prev, wage: e.target.value}})} value = {newRankDetails.wage}></input>
<label>Has Perm</label>
<input type="checkbox" onChange = {(e) => setNewRankDetails((prev) => {return {...prev, hasPerm: e.target.checked}})} value = {newRankDetails.hasPerm} />
<button onClick = {() =>{ updateClient('factions:createNewRank', newRankDetails)}}>Create</button>
</div>
</>
default:
return<>{tab}</>
}
}
I was rendering two compoenents in the 'main' component; therefor the data from the useStates were mixing and re-rendering.
<input style ={{width:'80px'}} onChange = {(e) => saveDetails(e, 'Rank Name')} value = {newRankDetails.rankName}></input>
const saveDetails=(e, param:string)=>{
e.preventDefault();
if(param==='Rank Name'){
setNewRankDetails((prev) => {...prev, rankName: e.target.value})
}else if(param==='Wage'){
setNewRankDetails((prev) => {...prev, wage: e.target.value})
}
}
Related
I created a form component using react hook forms. The component is composed from a group of checkboxes and a text input. The text input appears when user click on the last checkbox custom. The idea of this one is: when the user will click on it appears a text input and the user can add a custom answer/option. Ex: if user type test within the input then when the user will save the form, there should appear in an array test value, but custom text should't be in the array. In my application i don't have access to const onSubmit = (data) => console.log(data, "submit");, so i need to change the values within Component component. Now when i click on submit i get in the final array the custom value. Question: how to fix the issue described above?
const ITEMS = [
{ id: "one", value: 1 },
{ id: "two", value: 2 },
{ id: "Custom Value", value: "custom" }
];
export default function App() {
const name = "group";
const methods = useForm();
const onSubmit = (data) => console.log(data, "submit");
return (
<div className="App">
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Component ITEMS={ITEMS} name={name} />
<input type="submit" />
</form>
</FormProvider>
</div>
);
}
export const Component = ({ name, ITEMS }) => {
const { control, getValues } = useFormContext();
const [state, setState] = useState(false);
const handleCheck = (val) => {
const { [name]: ids } = getValues();
const response = ids?.includes(val)
? ids?.filter((id) => id !== val)
: [...(ids ?? []), val];
return response;
};
return (
<Controller
name={name}
control={control}
render={({ field, formState }) => {
return (
<>
{ITEMS.map((item, index) => {
return (
<>
<label>
{item.id}
<input
type="checkbox"
name={`${name}[${index}]`}
onChange={(e) => {
field.onChange(handleCheck(e.target.value));
if (index === ITEMS.length - 1) {
setState(e.target.checked);
}
}}
value={item.value}
/>
</label>
{state && index === ITEMS.length - 1 && (
<input
{...control.register(`${name}[${index}]`)}
type="text"
/>
)}
</>
);
})}
</>
);
}}
/>
);
};
demo: https://codesandbox.io/s/winter-brook-sml0ww?file=/src/Component.js:151-1600
Assuming that the goal is to keep all the selections in the same group field, which must be an array that logs the selected values in provided order, with the custom input value as the last item if specified, perhaps ideally it would be easier to calculate the values in onSubmit before submitting.
But since the preference is not to add logic in onSubmit, maybe an alternative option could be hosting a local state, run the needed calculations when it changes, and call setValue manually to sync the calculated value to the group field.
Forked demo with modification: codesandbox
import "./styles.css";
import { Controller, useFormContext } from "react-hook-form";
import React, { useState, useEffect } from "react";
export const Component = ({ name, ITEMS }) => {
const { control, setValue } = useFormContext();
const [state, setState] = useState({});
useEffect(() => {
const { custom, ...items } = state;
const newItems = Object.entries(items).filter((item) => !!item[1]);
newItems.sort((a, b) => a[0] - b[0]);
const newValues = newItems.map((item) => item[1]);
if (custom) {
setValue(name, [...newValues, custom]);
return;
}
setValue(name, [...newValues]);
}, [name, state, setValue]);
const handleCheck = (val, idx) => {
setState((prev) =>
prev[idx] ? { ...prev, [idx]: null } : { ...prev, [idx]: val }
);
};
const handleCheckCustom = (checked) =>
setState((prev) =>
checked ? { ...prev, custom: "" } : { ...prev, custom: null }
);
const handleInputChange = (e) => {
setState((prev) => ({ ...prev, custom: e.target.value }));
};
return (
<Controller
name={name}
control={control}
render={({ field, formState }) => {
return (
<>
{ITEMS.map((item, index) => {
const isCustomField = index === ITEMS.length - 1;
return (
<React.Fragment key={index}>
<label>
{item.id}
<input
type="checkbox"
name={name}
onChange={(e) =>
isCustomField
? handleCheckCustom(e.target.checked)
: handleCheck(e.target.value, index)
}
value={item.value}
/>
</label>
{typeof state["custom"] === "string" && isCustomField && (
<input onChange={handleInputChange} type="text" />
)}
</React.Fragment>
);
})}
</>
);
}}
/>
);
};
Ok, so after a while I got the solution. I forked your sandbox and did little changes, check it out here: Save Form values in ReactJS using checkboxes
Basically, you should have an internal checkbox state and also don't register the input in the form, because this would add the input value to the end of the array no matter if that value is "".
Here is the code:
import "./styles.css";
import { Controller, useFormContext } from "react-hook-form";
import { useEffect, useState } from "react";
export const Component = ({ name, ITEMS }) => {
const { control, setValue } = useFormContext();
const [state, setState] = useState(false);
const [checkboxes, setCheckboxes] = useState(
ITEMS.filter(
(item, index) => index !== ITEMS.length - 1
).map(({ value }, index) => ({ value, checked: false }))
);
useEffect(() => {
setValue(name, []); //To initialize the array as empty
}, []);
const [inputValue, setInputValue] = useState("");
const handleChangeField = (val) => {
const newCheckboxes = checkboxes.map(({ value, checked }) =>
value == val ? { value, checked: !checked } : { value, checked }
);
setCheckboxes(newCheckboxes);
const response = newCheckboxes
.filter(({ checked }) => checked)
.map(({ value }) => value);
return state && !!inputValue ? [...response, inputValue] : response;
};
const handleChangeInput = (newInputValue) => {
const response = checkboxes
.filter(({ checked }) => checked)
.map(({ value }) => value);
if (state) if (!!newInputValue) return [...response, newInputValue];
return response;
};
return (
<Controller
name={name}
control={control}
render={({ field, formState }) => {
return (
<>
{ITEMS.map((item, index) => {
return (
<>
<label>
{item.id}
<input
type="checkbox"
name={`${name}[${index}]`}
onChange={(e) => {
if (index === ITEMS.length - 1) {
setState(e.target.checked);
return;
}
field.onChange(handleChangeField(e.target.value));
}}
value={item.value}
/>
</label>
{state && index === ITEMS.length - 1 && (
<input
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
field.onChange(handleChangeInput(e.target.value));
}}
type="text"
/>
)}
</>
);
})}
</>
);
}}
/>
);
};
The input loses its focus when I start typing a character. I saw many StackOverflow answers but none of them is working. I have added unique keys also. What is the reason the code is not working? Without the state, it is working fine. But after adding the state, the input loses the focus.
import React, { useState } from "react";
const Footer = ({ formData }) => {
const [colorsArray, setColors] = useState(["Red", "Green", "Blue", "Yellow"]);
const [sizeArray, setSizes] = useState(["S", "M", "L", "XL"]);
const [sizeInput, setsizeInput] = useState("");
const colorElementRemoveHandler = (indexToRemove) => {
const filteredValue = colorsArray.filter((data, index) => {
return indexToRemove !== index;
});
setColors(filteredValue);
};
const sizeElementRemoveHandler = (indexToRemove) => {
const filteredValue = sizeArray.filter((data, index) => {
return indexToRemove !== index;
});
setSizes(filteredValue);
};
const addColorHandler = (e) => {
let input = e.target.value.toLowerCase();
if (input.length > 2) {
let temp = colorsArray;
temp.push(input);
setColors(temp);
}
};
const addSizeHandler = (e) => {
let input = e.target.value.toUpperCase();
if (input.length > 0) {
let temp = sizeArray;
temp.push(input);
setSizes(temp);
console.log(sizeArray);
}
};
const Test = () => {
return (
<input
type="text"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
);
};
const VariantUI = () => {
return (
<div>
<label>Size</label>
<input
id="optionName"
type="text"
placeholder="e.g S, M, L, XL"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
</div>
<ul>
{sizeArray.map((data, index) => {
return (
<li key={index}>
{data}
<i onClick={() => {sizeElementRemoveHandler(index);}}></i>
</li>
);
})}
</ul
);
};
return (
<VariantUI formData={formData} />
);
};
export default Footer;
Thanks in advance.
const Footer = ({ formData }) => {
// ..
const VariantUI = () => {
// ...
return (<VariantUI formData={formData} />)
}
You are creating a brand new type of component (VariantUI), in the middle of rendering Footer. This will happen on ever render. Each VariantUi function might have the same text as the previous one, but it's a different function, and thus to react it's a different type of component. Since it's a different type of component, the old one unmounts, and the new one mounts. A newly-mounted <input> does not have focus.
Component types must be defined only once, not on ever render. So VariantUI needs to be moved outside of footer. Since you're currently relying on closure variables, you will need to changes those to props:
const VariantUI = ({
sizeArray, setSizes, sizeInput, setSizeInput, // I might have missed a couple props
}) => {
// ...
}
const Footer = ({ formData }) => {
// ...
return (
<VariantUI
sizeArray={sizeArray}
setSizes={setSizes}
sizeInput={sizeInput}
setSizeInput={setSizeInput}
/>
);
}
So Im having problems with a form in which takes in text inputs to be set on an object. This object will then be updated in a hashmap of <key, object>.
So far, I can type in the input areas, but if I add another item in the hashmap which generates another div with the input elements it will contain the same value of the previous inputs.
So I need a way to reset those labels within the form and a way to update the hashmap.
I got an updateMap function, but I don't know where to place it for it to await the changes.
Edit: I need to reset the state of link, but when I do a function for it. It says something about preventing infinite loops.
export default function Admin() {
const [link, setLink] = useState({ name: "", link: "" });
const [links, setLinks] = useState(new Map());
const clickAddLink = () => addToMap(links.size + 1, link);
const deleteOnMap = (key) => {
setLinks((prev) => {
const newState = new Map(prev);
newState.delete(key);
return newState;
});
};
const getName = (key) =>
{
let linkFromKey = links.get(key)
return linkFromKey.name
}
const getLink = (key) =>
{
let linkFromKey = links.get(key)
return linkFromKey.link
}
const addToMap = (key, value) => {
setLinks((prev) => new Map([...prev, [key, value]]));
};
const updateMap = (key, value) => {
setLinks((prev) => new Map([...prev, [key, value]]));
};
const clear = () => {
setLinks((prev) => new Map(prev.clear()));
};
.... skipping code ....
<div>
{console.log(link.name)}
{console.log(link.link)}
{[...links.keys()].map((key) => (
<div key={key}>
{links.size > 0 && (
<div>
<form>
<span>Name</span>
<input
type="text"
placeholder={getName(key)}
required
value={link.name}
onChange={(e) =>
setLink({ name: e.target.value })
}
/>
<span>Link</span>
<input
type="text"
placeholder={getLink(key)}
required
value={link.link}
onChange={(e) =>
setLink({ link: e.target.value })
}
/>
</form>
</div>
)}
</div>
))}
</div>
{console.log(link.name)}
{console.log(link.link)}
</div>
```
I have an input with label "Ilosc osob". When I change it, I want to change the Select's options, depends of number in input. It happens, but with one step gap. What should I do?
matchedTables depends on props.tables, and it is filtered array from parent component.
const ReservationForm = (props) => {
const [enteredSize, setEnteredSize] = useState("");
const [enteredTable, setEnteredTable] = useState("");
const [enteredDate, setEnteredDate] = useState("");
const [enteredTime, setEnteredTime] = useState("");
const [sizeMatchedTables, setSizeMatchedTables] = useState([
{ id: 55, table_size: 1, isBusy: false },
{ id: 56, table_size: 2, isBusy: true },
]);
//some code
const matchingSizeTablesHandler = () => {
const newArray = props.tables.filter((tables) => {
if (tables.table_size >= enteredSize) {
return tables;
}
});
setSizeMatchedTables(newArray);
};
const sizeChangeHandler = (event) => {
setEnteredSize(event.target.value);
matchingSizeTablesHandler();
};
//some code
return(
<div>
<div className="new-reservation__control">
<label>Ilość osób</label>
<input
type="number"
min={1}
max={10}
value={enteredSize}
onChange={sizeChangeHandler}
/>
</div>
<select
className="new-reservation__control"
value={enteredTable}
onChange={tableChangeHandler}
>
<TablesInSelect passedOptions={sizeMatchedTables} />
</select>
</div>
)};
const TablesInSelect = (props) => {
return (
<>
{props.passedOptions.map((option, index) => {
return (
<option key={index} value={option.id}>
{option.id}
</option>
);
})}
</>
);
};
I found workaround this problem, but dont think its best way to do this. I changed matchingSizeTableHandler so it work with argument (and also change filter to reduce but it is not the case):
const matchingSizeTablesHandler = (size) => {
const newArray = props.tables.reduce((newTables, tables) => {
if (tables.table_size >= size) {
var newValue = tables;
newTables.push(newValue);
}
if (size === "") newTables = [];
return newTables;
}, []);
setSizeMatchedTables(newArray);
};
and then, in sizeChangeHandler I changed call out matchingSizeTableHandler with event.target.value parameter
const sizeChangeHandler = (event) => {
setEnteredSize(event.target.value);
matchingSizeTablesHandler(event.target.value);
};
. If someone can explain to me the other way to implement this, using the sizeMatchedTable state as a parameter in matchingSizeTablesHandler function, I would be thankful.
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>
);
}