On React Carbon Select component I want to have a default selected option, which can be changed, of course.
In general my goal is to implement edit functionality. It means user should be able to change already selected option.
I tried with adding the value property directly in Select like value={defaultSelected} or defaultValue={defaultSelected} but it didn't work.
Code example.
import React, { useState } from 'react';
import { Select, SelectItem } from '#carbon/react';
const options = [
{
value: 'apple',
text: 'Apple 🍏',
},
{
value: 'banana',
text: 'Banana 🍌',
},
{
value: 'kiwi',
text: 'Kiwi 🥝',
},
];
const SelectCarbon = ({defaultSelected}) => {
const [formData, setFormData] = useState();
const onChange = e => {
setFormData({
...formData,
fruit: e.target.value,
});
};
console.log('formData', formData);
return (
<>
{defaultSelected}
<Select id="select-fruits" onChange={onChange} value={defaultSelected}>
<SelectItem text="select Option" value="" />
{options.map(option => (
<SelectItem
key={option.value}
text={option.value}
value={option.value}
/>
))}
</Select>
</>
);
};
export default SelectCarbon;
Any help will be appreciated
Try This
const [formData, setFormData] = useState({
fruit: options[0].value
});
And note that formDate should be an object not array.
I came up with this solution conditionally showing either default value if nothing is selected or selected value.
And in order show the default value initially, I wrapped in formData in useEffect.
React.useEffect(() => {
setFormData({
...formData,
fruit: defaultSelected,
});
}, [defaultSelected]);
const onChange = e => {
setFormData({
...formData,
fruit: e.target.value ?? defaultSelected,
});
};
console.log('formData', formData);
return (
<>
{defaultSelected}
<Select
id="select-fruits"
onChange={onChange}
defaultValue={defaultSelected}>
<SelectItem text="select Option" value="" />
{options.map(option => (
<SelectItem
key={option.value}
text={option.value}
value={option.value}
/>
))}
</Select>
</>
);
};
export default SelectCarbon;
Related
I have stomped on a problem that i don't know how to resolve.
I have a react-select input that passes a selected value to another select input.
When a user clicks on submit button then i have to display an array of every selector input that the user has selected as a list of items and then the form should reset all select values.
For this, i have tried submitting to an array but it only shows one item from all selectors and form doesn't reset its values.
Here is a sandbox link
https://codesandbox.io/s/awesome-carson-i99g8p?file=/src/App.js
How can I archive this I have tried everything but I could not figure out how can i archive this functionality.
Ok. First tricky thing of react-select is, the value you assign to the component must be an object with label and valueproperties, not just the value. Meaning, when handling change events, you should set the state using the full event object.
This is the code mostly fixed:
import React, { useState, useEffect } from "react";
import Select from "react-select";
const options = [
{ value: "0", label: "0" },
{ value: "1", label: "1" },
{ value: "2", label: "2" }
];
const options2 = [
{ value: "Before Due Date", label: "Before Due Date" },
{ value: "After Due Date", label: "After Due Date" }
];
const App = (props) => {
const [numOfDays, setNumOfDays] = useState('');
const [beforeDueDate, setBeforeDueDate] = useState('');
const [items, setItems] = useState([]);
const [reminders, setReminders] = useState([]);
const submit = (event) => {
event.preventDefault(); // <-- prevent form submit action
const obj = {};
obj.id = reminders.length + 1;
obj.numOfDays = numOfDays.value;
obj.beforeDueDate = beforeDueDate.value;
setReminders((items) => [...items, obj]); // <-- update arr state
setBeforeDueDate("");
setNumOfDays("");
};
function numOfDaysHandle(event) {
// const numOfDays = event.value;
setNumOfDays(event);
setItems((items) => [...items, items]);
}
function beforeDueDateHandle(event) {
// const value = event.value;
setBeforeDueDate(event);
}
const removeReminder = (id) => {
setReminders(reminders.filter((item) => item.id !== id));
};
return (
<>
<form>
<div>
{reminders.map((item, index) => (
<div key={index}>
<div>
<span>
{item.numOfDays} days {item.beforeDueDate}
</span>
<button onClick={() => removeReminder(item.id)}>
removeItem
</button>
</div>
</div>
))}
</div>
<div>
<Select
options={options}
value={numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
//onChange={numOfDaysHandle}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
/>
</div>
{items.map((item, index) => (
<div key={index}>
<Select
options={options}
value={item.numOfDays}
id="numOfDays"
placeholder="Days"
isSearchable={false}
onChange={numOfDaysHandle}
/>
<Select
options={options2}
value={item.beforeDueDate}
id="beforeDueDate"
placeholder="Before Due Date"
isSearchable={false}
onChange={beforeDueDateHandle}
// onClick={() => setItems((items) => [...items, items])}
/>
</div>
))}
<button
onClick={submit}
//disabled={!numOfDays}
>
Set Reminder
</button>
</form>
</>
);
};
export default App;
Please see if you can move forward now, I could not understand what you want exactly with the array of <Select /> elements.
here's my form
const AddSection = () => {
const { register, handleSubmit } = useForm();
const onSubmit = formData => {}
return <form onSubmit={() => { handleSubmit(onSubmit) }}>
<select {...register("id", { required: true })}>
{
options.map(o => (
<option key={`o-${o.id}`} value={o.id}>{m.name}</option>
))
}
</select>
</form>
}
In onSubmit I'm trying to get select value and text
I tried the following. but that's not perfect as for me + it doesn't work properly in all the cases.
const AddSection = () => {
const { register, handleSubmit, setValue } = useForm();
register("text")
...
return <form onSubmit={() => { handleSubmit(onSubmit) }}>
<select
{...register("id", { required: true })}
onChange={event => setValue("text", event.target[event.target.selectedIndex].text)}
>
...
</select>
</form>
}
Does anyone know the right way to do that ?
This works, it doesn't use the useForm hook but every time a new option is selected, it saves the value and text in the "user" state which is an object with the keys: value and text. Then console.log()s on submit.
import { useState } from "react";
const AddSection = () => {
const [user, setUser] = useState({});
let options = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Bob Builder" },
];
const handleSubmit = (e) => {
e.preventDefault();
console.log(user);
};
return (
<form onSubmit={handleSubmit}>
<select
required
onChange={(e) => {
setUser({
value: e.target.value,
text: e.target.options[e.target.selectedIndex].text,
});
}}
>
{options.map((o) => (
<option key={`o-${o.id}`} value={o.id}>
{o.name}
</option>
))}
</select>
<input type="submit" value="Submit" />
</form>
);
};
export default AddSection;
You did it the right way. Maybe
inside the onchange function, you can call another function onOptionSelect. The issue will probably be in passing values from the select component. So console it here and debug.
You can use react form Controller as below.
<Controller
control={control}
name="bank"
rules={{
required: 'Bank is required',
}}
render={({ onChange, name, value }) => (
<select
{...register("id", { required: true })}
onChange={(val: any) => {
onChange(val);
onOptionSelect(val);
}}
>
)}
/>
const onOptionSelect = (opt: any) => {
console.log(opt);
setValue('userId', opt._id);
setValue('username', opt.name);
};
You can also do it using a new useState variable as mentioned in #Benjamin Bialy's answer.
Refer https://react-hook-form.com/api/usecontroller/controller/
import React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { useForm } from "react-hook-form";
import Multiselect from "./Multiselect.js";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const App = () => {
const { handleSubmit, setValue, control, watch, reset } = useForm({
mode: "onBlur",
reValidateMode: "onChange",
shouldUnregister: true,
defaultValues: {
disciplines: []
}
});
useEffect(() => {
(async () => {
await sleep(50);
setValue("disciplines", ["bbb", "ccc"], { shouldValidate: true });
reset({ disciplines: ["bbb", "ccc"] });
})();
}, []);
const values = watch();
return (
<form onSubmit={handleSubmit(console.log)}>
<Multiselect
name={"disciplines"}
label={"Disciplines"}
control={control}
values={["aaa", "bbb", "ccc"]}
/>
<button>submit</button>
<pre>{JSON.stringify(values, null, 4)}</pre>
</form>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can get select value by:-
const AddSection = () => {
const { register, handleSubmit } = useForm();
const onSubmit = formData => {alert(JSON.stringify(formData.id));}
return <form onSubmit={() => { handleSubmit(onSubmit) }}>
<select {...register("id", { required: true })}>
{
options.map(o => (
<option key={`o-${o.id}`} value={o.id}>{o.name}</option>
))
}
</select>
</form>
}
After getting value if you can you can loop through options array to get the text also.
I need to update the state on main page, the problem is that I update values 3 levels down...
This component is where I get all the cities, putting the data on the State and map through cities.
CitiesPage.tsx
export const CitiesPage = () => {
const [cities, setCities] = useState<City[]>([]);
useEffect(() => {
getCities().then(setCities);
}, []);
return (
<>
<PageTitle title="Cities" />
<StyledCitySection>
<div className="headings">
<p>Name</p>
<p>Iso Code</p>
<p>Country</p>
<span style={{ width: "50px" }}></span>
</div>
<div className="cities">
{cities.map((city) => {
return <CityCard key={city.id} city={city} />;
})}
</div>
</StyledCitySection>
</>
);
};
On the next component, I show cities and options to show modals for delete and update.
CityCard.tsx.
export const CityCard = ({ city }: CityProps) => {
const [showModal, setShowModal] = useState(false);
const handleModal = () => {
setShowModal(true);
};
const handleClose = () => {
setShowModal(false);
};
return (
<>
{showModal && (
<ModalPortal onClose={handleClose}>
<EditCityForm city={city} closeModal={setShowModal} />
</ModalPortal>
)}
<StyledCityCard>
<p className="name">{city.name}</p>
<p className="isoCode">{city.isoCode}</p>
<p className="country">{city.country?.name}</p>
<div className="options">
<span className="edit">
<FiEdit size={18} onClick={handleModal} />
</span>
<span className="delete">
<AiOutlineDelete size={20} />
</span>
</div>
</StyledCityCard>
</>
);
};
and finally, third levels down, I have this component.
EditCityForm.tsx.
export const EditCityForm = ({ city, closeModal }: Props) => {
const [updateCity, setUpdateCity] = useState<UpdateCity>({
countryId: "",
isoCode: "",
name: "",
});
const handleChange = (
evt: ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
const { target } = evt;
setUpdateCity({ ...updateCity, [target.name]: target.value });
};
const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const { id: cityId } = city;
updateCityHelper(cityId, updateCity);
closeModal(false);
};
useEffect(() => {
setUpdateCity({
isoCode: city.isoCode,
name: city.name,
countryId: city.country?.id,
});
}, [city]);
return (
<form onSubmit={handleSubmit}>
<Input
label="Iso Code"
type="text"
placeholder="Type a IsoCode..."
onChange={handleChange}
name="isoCode"
value={updateCity.isoCode}
/>
<Input
label="Name"
type="text"
placeholder="Paste a Name.."
onChange={handleChange}
name="name"
value={updateCity.name}
/>
<CountrySelect
label="Country"
onChange={handleChange}
value={city.country?.name || ""}
name="countryId"
/>
<Button type="submit" color="green" text="Update" />
</form>
);
};
Edit form where retrieve data passed from CityCard.tsx and update State, passing data to a function that update Info, closed modal and... this is where I don't know what to do.
How can I show the info updated on CitiesPage.tsx when I submitted on EditCityForm.tsx
Any help will be appreciated.
Thanks!
You are storing the updated value in a different state, namely the updateCity state, but what you should be doing is update the origional cities state. While these two states are not related, and at the same time your UI is depend on cities state's data, so if you wish to update UI, what you need to do is update cities' state by using it's setter function setCities.
Just like passing down state, you pass it's setters as well, and use the setter function to update state's value:
// CitiesPage
{cities.map((city) => {
return <CityCard key={city.id} city={city} setCities={setCities} />;
})}
// CityCard
export const CityCard = ({ city, setCities }: CityProps) => {
// ...
return (
// ...
<ModalPortal onClose={handleClose}>
<EditCityForm city={city} closeModal={setShowModal} setCities={setCities} />
</ModalPortal>
// ...
)
}
// EditCityForm
export const EditCityForm = ({ city, closeModal, setCities }: Props) => {
// const [updateCity, setUpdateCity] = useState<UpdateCity>({ // no need for this
// countryId: "",
// isoCode: "",
// name: "",
// });
const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
const { id: cityId } = city;
setCities(); // your update logic
closeModal(false);
};
}
I'd advise you to use React-Redux or Context API whenever you have nested structures and want to access data throughout your app.
However in this case you can pass setCities and cities as a prop to CityCard and then pass this same prop in the EditCityForm component and you can do something like this in your handleSubmit.
const handleSubmit = (evt: FormEvent<HTMLFormElement>) => {
evt.preventDefault();
let updatedCities = [...cities];
updatedCities.forEach(el => {
if(el.id == updateCity.id) {
el.countryCode = updateCity.countryCode;
el.name = updateCity.name;
el.isoCode = updateCity.isoCode;
}
})
setCities(updatedCities);
closeModal(false);
};
//value={items.find(x => x.item === selectedValue)}import { Fragment, useState } from //"react";
import Select from 'react-select';
let items = [
{
item: 1,
name: "tv"
},
{
item: 2,
name: "PC"
}
]
const Home = () => {
const [selectedValue, setSelectedValue] = useState(6)
const handleChange = obj => {
setSelectedValue(obj.item)
}
return (
<Fragment>
<div>Home page</div>
<p>Test React Select...</p>
<Select
value={items.find(x => x.item === selectedValue)}
options={items.map(({item, name}) => ({value: name, label: item}))}
onChange={handleChange}
/>
<p>selected Value:...</p>
{selectedValue}
</Fragment>
)
}
export default Home;
It would be helpful if you could provide more information, like if you're getting an error.
However, I think your problem is twofold:
the value of your Select component is being set to the entire object instead of the value.
You've mixed up the value and label keys in the options prop.
Try this:
<Select
value={items.find(x => x.item === selectedValue).item}
options={items.map(({item, name}) => ({value: item, label: name}))}
onChange={handleChange}
/>
EDIT: You don't need to use find, you already have this value:
<Select
value={selectedValue}
options={items.map(({item, name}) => ({value: item, label: name}))}
onChange={handleChange}
/>
I have 3 components. Main Component named 'XYZ' which contains 2 other components named 'RadioButton' and 'Textbox'. I need to update state of Textbox component (Setting value to 500) on change of RadioButton component. Below is my code. I am new to React development. Any help would be surely appreciated!
Main Component - XYZ.tsx
const XYZ = (props) => {
let lossCapacityValues = [
{ value: "Average", text: "Average" },
{ value: "Medium", text: "Medium" }
];
const lossCapacityChange = (event) => {
let ele = $(event.target);
};
return (
<div className="mel-box">
<RadioButton groupName="radio-loss-type" selectedValue="2" items={lossCapacityValues} onChange={lossCapacityChange} />
<TextBox value="1000" />
</div>
)
};
export default XYZ;
Child Component - 1- RadioButton.tsx
const RadioButton = (props) => {
const onChange = (event) => {
props.onChange && props.onChange(event);
}
return (
<div>
{
props.items.map((i: { "text": string, "value": string }, index: number) => {
return (
<div key={index} className="radiobutton-option-div">
<input
className="radio-custom"
type="radio"
name={props.groupName}
value={i.value}
defaultChecked={i.value === props.selectedValue}
onChange={onChange}
/>
<label className="radio-custom-label">
<span className="radio-custom-text">{i.text}</span>
</label>
</div>
)
})
}
</div>
);
};
export default RadioButton;
Child Component - 2- TextBox.tsx
const TextBox = (props) => {
const [state, setState] = React.useState({
value: props.value
});
const handleChange = (evt) => {
setState({ value: evt.target.value });
};
return (
<React.Fragment>
<div className="input-text">
<input
type="text"
value={state.value}
onChange={handleChange}
/>
</div>
</React.Fragment>
)
};
export default TextBox;
This can be done by lifting the state up, out of the TextBox component into the XZY component.
Working demo
In XYZ, put the text state handlers:
const XYZ = (props) => {
const [textState, setTextState] = React.useState({
value: 1000
});
const handleTextChange = (evt) => {
setTextState({ value: evt.target.value });
};
....
....
};
Pass them as props:
<TextBox value={textState.value} onChange={handleTextChange} />
Change your input to use them:
<input
type="text"
value={props.value}
onChange={props.onChange}
/>
In your XYZ you can check the selected radio value and set the text state accordingly.
const lossCapacityChange = (event) => {
//let ele = $(event.target);
if(event.target.value == 'Average'){
setTextState({ value: 500 });
}
};
Side note: you should avoid the use of jQuery in React unless there is a good reason for it. React uses a Virtual DOM which can be affected by jQuery use.