I need to be able to send two values when handling an onChange event in my select box however I am not sure a way of doing this as they are currently sent as a string. I am using react with react-bootstrap.
const DropDown = () => {
const handleOnchange = (event) => {
console.log(event.target.value)
const filter = event.target.value[0];
const initialState = [...initialValues]
let result = [];
console.log(event)
setDefaultSelect(event.target.name)
if(event.target.value === 'Show All') {
setValues(initialState);
}
else {
initialState.forEach(item => {
let found = item.store_category.filter(item => item == filter);
if (found.length) {
result.push(item);
}
});
setValues(result);
}
}
return (
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Control onChange={(e) => handleOnchange(e)} as="select" custom>
<option className="pl-2" hidden>{defaultSelect}</option>
<option>Show All</option>
{storeCategorys.map((item, index) => (
<option value={{itemId: item.id, itemName: item.name}} key={index}>{item.name}</option>
))}
</Form.Control>
</Form.Group>
</Form>
);
}
How about to use useEffect instead of handleOnchange?
const DropDown = () => {
const [selectedItem, setSelectedItem] = useState({});
useEffect(() => {
console.log(selectedItem);
}, [selectedItem]); // will be fired when `selectedItem` is changes
...
{storeCategorys.map((item, index) => (
<option
value={{itemId: item.id, itemName: item.name}}
key={index}
onClick={() => {setSelectedItem(item)}} // will fire `useEffect`
// on `selectedItem`
>{item.name}</option>
))}
...
}
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"
/>
)}
</>
);
})}
</>
);
}}
/>
);
};
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 have user with two parameters (username, and user groups )
I have Page that's update my user by changing username and update groups
it looks like:
Problem is , I can't highlight groups , I need to choose to update .
const AddUser = props =>{
let editing = false;
let initialUsername = "";
const[initialGroups, setInitialGroups] = useState([])
useEffect(()=>{
retrieveGroups();
},[])
const retrieveGroups = () => {
BackendService.getAllGroups()
.then(response => {
setInitialGroups(response.data);
})
.catch(e => {
console.log(e);
});
}
const[username, setUsername] = useState(initialUsername);
const[groups, setGroups] = useState(initialGroups);
const[submitted, setSubmitted] = useState(false);
const onChangeUsername = e => {
const username = e.target.value;
setUsername(username);
}
const onChangeGroups = e => {
console.log(e);
setGroups(Array.from(e.currentTarget.selectedOptions, (v) => v.value));
}
const saveUser = () => {
var data = {
username: username,
groups: groups,
complited: false,
}
BackendService.editUser(
props.location.state.currentUser.id,
data)
.then(response=>{
setSubmitted(true);
console.log(response.data)
})
.catch(e=>{
console.log(e);
})
)
.catch(e=>{
console.log(e);
});
}
}
return(
<Container>
{submitted ? (
<div>
<h4>
User Edited Successfully
</h4>
<Link to={"/users/"}></Link>
Back to Users
</div>
):(
<Form>
<Form.Group className="mb-3">
<Form.Label>
"Edit" User
</Form.Label>
<Form.Control
type="text"
required
value={username}
placeholder="Enter username here"
onChange={onChangeUsername}
/>
<Form.Control
as="select"
multiple value={initialGroups}
onChange={onChangeGroups}
>
{initialGroups.map(group => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</Form.Control>
</Form.Group>
<Button variant="info" onClick={saveUser}>
"Edit" User
</Button>
</Form>
)}
</Container>
)
}
export default AddUser;
In this section I get all groups(initialGroups) I have in database:
const[initialGroups, setInitialGroups] = useState([])
useEffect(()=>{
retrieveGroups();
},[])
const retrieveGroups = () => {
BackendService.getAllGroups()
.then(response => {
setInitialGroups(response.data);
})
.catch(e => {
console.log(e);
});
}
After I put InitialGroups in :
<Form.Control
as="select"
multiple value={initialGroups}
onChange={onChangeGroups}
>
{initialGroups.map(group => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</Form.Control>
And process in :
const onChangeGroups = e => {
console.log(e);
setGroups(Array.from(e.currentTarget.selectedOptions, (v) => v.value));
}
What I do wrong ? I can't highlight group I need to proces and update user
I would like to make the following suggestions:
You don't need to use 'initialGroups' as a state variable. You already have a 'groups' variable which can receive an initial value.
const [groups, setGroups] = useState([]);
You can directly set the 'groups' variable once you retrieve the data.
BackendService.getAllGroups()
.then(response => {
setGroups(response.data);
})
You should pass 'groups' to the 'value' prop on the Form component, instead of 'initialGroups' (and then map over it).
<Form.Control
as="select"
multiple
value={groups}
onChange={onChangeGroups}
>
{groups.map(group => (
<option key={group.id} value={group.id}>
{group.name}
</option>
))}
</Form.Control>
Hope this helps!
I'm making a filter function by checkbox. I made a reproduce like below. I changed values in array but checkbox checked status not change, what I missed? I think I must re-render list but it's also refill checked array to initial state. What should I do? Thanks!
import * as React from "react";
import "./styles.css";
import { Checkbox } from "antd";
const arr = [
{
name: "haha"
},
{
name: "haha2"
},
{
name: "hahaha"
}
];
const App = () => {
let [viewAll, setViewAll] = React.useState(true);
let checked = new Array(3).fill(true);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "white" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
haha
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
checked = checked.map(() => viewAll);
};
const handleFilter = (value, index) => {
setViewAll(false);
checked.map((_value, _index) => {
if (_index === index) {
return (checked[_index] = !checked[_index]);
} else {
return checked[_index];
}
});
console.log(checked);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>Check all</Checkbox>
{list}
</div>
);
};
export default App;
Here is codesanboxlink
You should create checked state. Check the code below.
let [viewAll, setViewAll] = React.useState(true);
let [checked, setChecked] = React.useState([true, true, true]);
// render calendars checkbox
let list = arr.map((value, index) => {
return (
<Checkbox
style={{ color: "black" }}
checked={checked[index]}
onChange={() => handleFilter(value, index)}
className="check-box"
>
{value.name}
</Checkbox>
);
});
const handleViewAll = () => {
setViewAll(!viewAll);
setChecked(() => checked.map(item => !viewAll));
};
const handleFilter = (value, index) => {
setViewAll(false);
setChecked(() =>
checked.map((_value, _index) => {
if (_index === index) return !checked[_index];
return checked[_index];
})
);
};
return (
<div className="App">
<Checkbox checked={viewAll} onChange={() => handleViewAll()}>
{checked}
</Checkbox>
{list}
</div>
);
codesandbox demo
You have to define checked array as a state value.
Right now your code is not firing render function because checked array is not props but also not state.
please find below code which contains name id and am rendering initially using map
am replacing id value to input type in UI
with the updated input type am trying to update the value onchange
update is not capturing and unable to update the input field
any suggestion?
please refer below snippet
import React, { useState } from "react";
const CstmInput = (props) => {
return (
<input
name={props.name}
type="text"
value={props.value}
onChange={(event) => props.onInputChange(event)}
/>
);
};
export default CstmInput;
import React, { useState } from "react";
import CstmInput from "./CstmInput";
const HierarcyTest = () => {
let rowData = [
{ name: "first", id: 10 },
{ name: "second", id: 20 },
];
const [data, setData] = useState(rowData);
const [name, setName] = useState({ fn: "test" });
const onInputChange = (e) => {
console.log("---event---", e.target.value);
setName({ ...name, fn: e.target.value });
};
let updateValue = () => {
let newData = data.map(
(item, index) =>
(item.id = (
<CstmInput name={item.name} value={item.id} onInputChange={(e) => onInputChange(e)} />
))
);
setData([...data, newData]);
};
return (
<div>
<div>Testing</div>
{data.map((val) => (
<h6>
{" "}
{val.name} {val.id}
</h6>
))}
<button onClick={updateValue}> Click </button>
</div>
);
};
export default HierarcyTest;
A few things why your code isn't working as intended:
1.
let updateValue = () => {
let newData = data.map((item, index) => {
if (item.id === 10) {
return [
(item.id = (
<CstmInput
value={item.id}
onInputChange={(e) => onInputChange(e)}
/>
)),
];
}
});
setData([...data, newData]);
};
In the above function inside the callback of map, you're only returning when a condition satisfies. Are you trying to filter the array instead? If not then return something when the if condition fails.
And why are you returning an array?
return [
(item.id = (
<CstmInput
value={item.id}
onInputChange={(e) => onInputChange(e)}
/>
)),
];
the above code seems logically wrong.
2.
const onInputChange = (e) => {
console.log("---event---", e.target.value);
setName({ ...name, fn: e.target.value });
};
If you want to update state which depends on the previous state then this is how you do it:
setName((prevState) => ({ ...prevState, fn: e.target.value }));
but since you're not actually relying on the properties of the previous state you can just use:
setName({fn: e.target.value });
Note that since your state only has one property and you want to update that single property you can completely overwrite the state, you don't need to spread the previous state.
update
change the updateValue function as the following:
let updateValue = () => {
setData(prevData => {
return prevData.map(el => {
return { ...el, id: <CstmInput value={el.id} onInputChange={(e) => onInputChange(e)} /> };
})
});
};
A stackblitz example I've created that implements what you're trying to do.