React select to render values from array - reactjs

Using https://github.com/JedWatson/react-select. I have an method that will loop through an array that I would like to input into the value for react-select picker. Any ideas why const value is invalid?
renderList (thelist, i) {
const { selectedOption } = this.state;
console.log(thelist);
// this throws me error
const value = {
value: {thelist.name},
label: {thelist.name}
}
return (
<div>
<Select
name="form-field-name"
onChange={this.handleChange}
value={value}
/>
</div>
);

You don't need the curly braces for the object values. Just do:
const value = {
value: thelist.name,
label: thelist.name
}
You only use curly braces when declaring an object or when you need to tell React to interpret something inside render() as plain JavaScript instead of a string.
However, you probably want to define an options prop too to your select component. value only gives the dropdown a selected value, but options is what actually defines... well, the options. The options can be multiple, so we define them in an array of objects.
So do:
options={[
{
value: thelist.name1,
label: thelist.name1
},
{
value: thelist.name2,
label: thelist.name2
}
]}

Related

How to make MUI's Autocomplete display the label from the matching value in props.options?

I have a multilingual website where there are certain Autocompletes whose options array need to have its items labels translated, which is done very easily.
However, it would be harder to update the current chosen value, stored elsewhere. Since Autocomplete uses the label from the value prop instead of using the item of same ID within options, it ends up like this:
const vehicles = [
{ id: 1, label: "Car" },
{ id: 2, label: "Bus" }
];
const currentVehicleValue = {
id: 2,
label: "Ônibus"
};
export default function ComboBox() {
return (
<Autocomplete
disablePortal
id="combo-box-demo"
options={vehicles}
value={currentVehicleValue}
renderInput={(params) => <TextField {...params} label="Vehicle" />}
/>
);
}
Is there a way to just tell Autocomplete to use the label inside the options prop instead of the one within the value
demo
EDIT: I mean to have the label within options being shown while I don't type anything. As in, the language changed, the options were translated, but the currentValue was not, so it would be nice to have the Autocomplete use the label from the matching item within options as long as I don't type anything.
Edit after clarification
You can change how the <TextField/> is rendered by tweaking the props it receives.
In the example below, I find the currentVehicle by its id and change the inputProps.value to be the vehicle label.
Also, to ensure MUI finds the currentValue correctly within the options, you will need to use the isOptionEqualToValue to compare the options ids instead of strict equality
const vehicles = [
{ id: 1, label: "Car" },
{ id: 2, label: "Bus" }
];
const currentVehicleValue = {
id: 2,
label: "Ônibus"
};
function compareValueToOption(option, value) {
return option.id === value.id;
}
function ComboBox() {
return (
<Autocomplete
disablePortal
id="combo-box-demo"
options={vehicles}
value={currentVehicleValue}
renderInput={ComboBoxInput}
isOptionEqualToValue={compareValueToOption}
/>
);
}
function ComboBoxInput(props) {
const currentVehicle = vehicles.find(
(vehicle) => vehicle.id === currentVehicleValue.id
);
props.inputProps.value = currentVehicle.label;
return <TextField {...props} label="Vehicle" />;
}
Here's a working demo

React Hook form with Dynamic key names creates huge array instead

I am working on creating form inputs, where the IDs of the inputs match an item id from the backend data. I need to keep track of the dynamic IDs, so I want the name of the fields to match also.
How can I create a form with an object of dynamic field names? It seems that RHF creates an array instead, so if my itemID is 7841 the array is 7841 in length?
interface DynamicCharges {
...other fields with static names
additionalCharges: {
[id: number]: { // where the `id` comes from the backend so i need the input to follow this
qty: number;
amt: string;
};
}
}
and then implementing the UI:
const chargesWithDynamicIds = (additionalCharges || []).reduce(
(acc, additionalCharge) => {
const keyName = `_${additionalCharge.id}`;
return {
...acc,
additionalCharges: {
...acc.additionalCharges,
[keyName]: {
qty: 0,
amt: '$0.00',
},
},
};
},
otherStaticChargesToInitObj
);
const methods = useForm<ReservationChargesShape>({
defaultValues: chargesWithDynamicIds,
});
<Controller
name={`additionalCharges.${additionalCharge?.id}.qty`}
control={control}
render={({ value, onChange }) => (
<Input
value={value}
onChange={e => {
onChange(e.target.value); // sets the actual QTY input
setValue( // sets the AMT field that is dependent on this QTY change
`additionalCharges.${additionalCharge?.id}.amt`,
formatMoney(
Number(e.target.value) *
centsToDollars(
additionalCharge?.maximum_unit_amount
)
)
);
}}
/>
)}
/>
the original /default form state looks OK
But it turns into an array once the form re-renders
What am I doing wrong with this?
EDIT: I think this is related to the small snippet in their docs:
can not start with a number or use number as key name
What I have done is to prefix the field with _ so the key is no longer numeric, but _7841. Is there any other solution to numeric keys?
The problem come from that you're calling onChange inside your onChange event.
To set the actual QTY input you should use a useState.

Bind multiple form inputs to vuex store array

So I have this array in Vuex Store, and I want to bind it's fields to multiple inputs in a form. The way I manged to get this working is like this:
template:
<b-form-input id="CustName2"
type="text"
v-model="CustName2" :maxlength="50"
placeholder="Nombre">
</b-form-input>
<b-form-input id="CustAddr"
type="text"
v-model="CustAddr" :maxlength="50"
placeholder="Dirección">
</b-form-input>
<b-form-input id="CustPostCode"
type="text"
v-model="CustPostCode" :maxlength="10"
placeholder="Cod. Postal">
</b-form-input>
Computed:
computed: {
CustName2: {
get () {
return this.$store.state.orderproperties.CustName2
},
set (value) {
this.$store.commit('SetCustName2', value)
}
},
CustAddr: {
get () {
return this.$store.state.orderproperties.CustAddr
},
set (value) {
this.$store.commit('SetCustAddr', value)
}
},
CustPostCode: {
get () {
return this.$store.state.orderproperties.CustPostCode
},
set (value) {
this.$store.commit('SetCustPostCode', value)
}
}
}
store.js:
orderproperties: {
CustName2: '',
CustAddr: '',
CustPostCode: ''
}
The thing is, now I need to add 5 more properties (5 more fields to the form), and I feel like I could be getting a single computed property as an array, and then bind this to each field in the form; instead of creating a single computed property for each field. The problem is that the setter will not bind each array element to each input. Any ideas on how to refactor this? Right now, for each field I need a computed property, a Store mutation and a Store getter for each field.
one of approaches:
In store.js add universal mutation
import Vue from 'vue'
export const mutations = {
updateProp: (state, payload) => {
const { prop, value } = payload
Vue.set(state.orderproperties, prop, value)
},
}
in methods add
methods {
onChange(prop, value) {
this.$store.commit('updateProp', {prop: prop, value: value})
},
getValue(prop) {
return this.$store.state.orderproperties[prop]
}
}
in template
<b-form-input id="CustName2"
type="text"
#change="onChange('CustName2', $event)"
:value="getValue('CustName2')"
:maxlength="50"
placeholder="Nombre">
<b-form-input id="CustAddr"
type="text"
#change="onChange('CustAddr', $event)"
:value="getValue('CustAddr')"
:maxlength="50"
placeholder="Dirección">
...

Changing an array inside an object using React Hooks

I have a form that looks like,
const virtual_form = {
name: 'virtual',
address_info: [
{
name: 'a',
address: '',
}
]
}
I use this as a default state of my hook
const [virtualForm, setVirtualForm] = useState(virtual_form)
I just providing the user to modify the address field.
<div className="input-text-wrapper">
<TextField
value={virtualForm.address_info.address}
name="address"
onChange={(e) => handleAccessInfoChange(e, 'virtual')} />
</div>
like above.
However, in my handleAccessInfoChange,
const handleAccessInfoChange = (e, type) => {
console.log(e.target.name, e.target.value, type)
switch (type) {
case 'virtual':
setVirtualForm({...virtualForm, address_info[0]: [...virtualForm.address_info, address: value] })
}
}
I am getting a syntax error when I try to change the virtualForm. It says 'address' is not defined no-undef.
How can I make this to only affect the address correctly?
You're treating the object with address and name as an array, but you cant assign an array with this syntax [address: value]. Its looking for a variable called address not using it as a key.
Instead, map over it and modify the object at the correct index. I have no way to know which index, so I'll assume 0 as in the question:
setVirtualForm({
...virtualForm,
address_info: virtualForm.address_info.map((info, i) => {
if (i == 0) {
return {
...info,
address: value
}
}
return info
}
})

Is there possible to determine parameter is one of the given collection types?

Imagine I have a Select component like this one. For simplicity, we ignore other props.
Now we Select component just receive two props:
set a set of string , Select component will render this set into a bunch of Option component.
defaultValue is initial selected option, it is one of set props's value.
I assume two case. The case one should work well without warning
<Select set={["peter", "tom", "jos"]} defaultValue={"tom"} />
Case two should be warning in editor, because 'ben' is not in set.
<Select set={["peter", "tom", "jos"]} defaultValue={'ben'} />
Can we do static check in case two instead of runtime check ?
You can define a type for such a component. It must be a generic component, that will infer the types of the string literals passed in as the set property:
function Select<T extends V[], V extends string>(p: { set: T, defaultValue: T[number] }) {
return <div></div>
}
let s = () => <Select set={["peter", "tom", "jos"]} defaultValue="tom" />
let s2 = () => <Select set={["peter", "tom", "jos"]} defaultValue="ben" /> // err
Without changing the definition of the original Select your only option would be to use a helper function to do the checking:
function Select(p: { set: string[], defaultValue: string }) {
return <div></div>
}
function SelectData<T extends V[], V extends string>(p: { set: T, defaultValue: T[number] }) : { set: string[], defaultValue: string } {
return p
}
let s = () => <Select {... SelectData({ set: ["peter", "tom", "jos"], defaultValue:"tom" })} />
let s2 = () => <Select {... SelectData({ set: ["peter", "tom", "jos"], defaultValue:"ben" })} /> // err

Resources