I have a component that fetches data from an API and displays it in a select box. When the select box is changed, it updates a property inside the state. When this is done, I want to run a different useEffect which fetches more values from the API based on the selected item.
// State for dropdown options
const [categories, setCategories] = useState<DropdownItemProps[]>([]);
// State for form
const [category] = useState<CategoryInstance>({
id: "",
number: 1,
sponsor: "",
logo: "",
permit: "",
bumpSpot: 128,
raceEventId: "670d34d5-134b-4408-91bd-691f8c4b62d2",
categoryId: "",
configurationId: "",
category: null,
configuration: null,
raceEvent: null,
ladder: null,
entryList: [],
rounds: [],
});
// UseEffect 1, fetch dropdown items on load
useEffect(() => {
console.log("Use effect 1");
let c: DropdownItemProps[] = [];
categoryStore.loadCategories().then(() => {
categoryStore.categories.forEach((x) =>
c.push({
key: x.id,
value: x.id,
text: x.name,
})
);
setCategories(c);
});
}, [categoryStore]);
// UseEffect 2, fetch more items
useEffect(() => {
console.log("Use effect 2");
console.log(`ID: ${category.categoryId}`);
}, [category.categoryId]);
From my understanding, when the category.categoryId changes value, it should cause the UseEffect to print out the information. But this isn't happening.
<SelectInput
name='categoryId'
placeholder='Category'
label='Category'
options={categories}
search
/>
Select Input
import { useField } from "formik";
import { DropdownItemProps, Form, Label, Select } from "semantic-ui-react";
interface Props {
placeholder: string;
name: string;
type?: string;
label?: string;
options: DropdownItemProps[];
search: boolean;
}
export default function SelectInput(props: Props) {
const [field, meta, helpers] = useField(props.name);
return (
<Form.Field error={meta.touched && !!meta.error}>
<label>{props.label}</label>
<Select
clearable
options={props.options}
value={field.value || null}
onChange={(event, data) => helpers.setValue(data.value)}
onBlur={() => helpers.setTouched(true)}
placeholder={props.placeholder}
search={props.search}
/>
{meta.touched && meta.error ? (
<Label basic color='red'>
{meta.error}
</Label>
) : null}
</Form.Field>
);
}
Any help would great,
Thanks.
Related
I almost have a working solution, but the label aspect is giving an undefined error, and I also want to make sure my solution is elegant as its a component I will reuse a lot.
I have an API which returns a json response, and for the purposes of this, the important values are; (I will stub out the API and just provide its response).
const countries =
[
{ country_id: 1, name: 'France', iso: 'fr'},
{ country_id: 2, name: 'Germany', iso: 'de'},
{ country_id: 3, name: 'United Kingdom', iso: 'gb'},
{ country_id: 4, name: 'Spain', iso: 'es'}
];
It's the MUI example with some tweaks to almost make it work as desired.
I want the label in the AutoComplete to be the country name, I want the value returned to be the country_id and the text in the AutoComplete to be the name of the Country they selected. It's the label that's not being set.
const Select = ({ country, onUpdate }) => {
//country is the valuable passed in to preselect an option or the option chosen, and the onUpdate is a function passed in (its a setState in the parent component).
const [countries, setCountries] = useState([]);
const [value, setValue] = React.useState('');
useEffect(() => {
api.get(`/countries`).then((response) => {
if (response.data) {
setCountries(response.data);
}
});
}, []);
return (
<>
<Autocomplete
autoHighlight
fullWidth
value={value}
options={countries}
onChange={(event, newValue) => {
setValue(newValue.name);
}}
inputValue={country}
onInputChange={(event, newInputValue) => {
onUpdate(newInputValue);
}}
renderOption={(props, country) => (
<Box component="li" {...props}>
{`{country.name} (${country.iso})`}
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
/>
)}
/>
</>
);
};
Select.propTypes = {
country: PropTypes.string,
onUpdate: PropTypes.func.isRequired,
};
export default Select;
You need to add this line to your AutoComplete attributes:
getOptionLabel={(option) => option.name}
According to the documentation:
Options structure
By default, the component accepts the following options structures:
interface AutocompleteOption {
label: string;
}
// or
type AutocompleteOption = string;
for instance:
const options = [
{ label: 'The Godfather', id: 1 },
{ label: 'Pulp Fiction', id: 2 },
];
// or
const options = ['The Godfather', 'Pulp Fiction'];
However, you can use different structures by providing a getOptionLabel prop.
Another option is to change the name property in your response object into label and you will be set.
I have 2 react-select, one is for methodGroups, the other is methods.
methodGroups: [{value: 1, label: 'Group-1'}, {value: 2, label: 'Group-2'}, ...]
methods: [{value: 1, label: 'method-1', isActive: 1}, {value: 2, label: 'method-2', isActive: 0}, ...]
When I select from methodGroups, the methods select should automatically set all the isActive = 1 as selected.
What I did was,
methodGroups Select:
const handleGroupChange = () => {
// fetch from api
let { activeMethods, inactiveMethods } = apiData;
let temp1 = activeMethods.map(obj => ({value: obj.id, label: obj.method, isActive: 1}));
let temp2 = inactiveMethods.map(obj => ({value: obj.id, label: obj.method, isActive: 0}));
setMethods(temp1.concat(temp2));
}
<CustomSelect
options={methodGroups}
onChange={handleGroupChange}
/>
methods Select:
const handleMethodsChange = (values) => {
setMethods(values);
}
<CustomSelect
defaultValue={methods.filter(obj => obj.isActive == 1)}
options={methods}
isMulti
onChange={handleMethodsChange}
/>
At first, when I select from methodGroups it shows the defaultValues on methods select.
But when I select new from methodGroups, the props of methods select has changed, but the default value that was being displayed in the UI is still the defaultValue of the previous selected methodGroups
BTW, I made the Select as a separate component, like this:
CustomSelect.js
import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
import Select from 'react-select';
function CustomSelect({
defaultValue,
options,
isMulti,
onChange
}) {
// const [defVal, setDefVal] = useState(defaultValue);
// useEffect(() => {
// console.log('defaultValue ', defaultValue);
// setDefVal(defaultValue);
// }, [defaultValue]);
return (
<Select
defaultValue={defaultValue}
options={options}
isMulti={isMulti}
onChange={onChange}
/>
)
}
export default CustomSelect
I'm creating a shopping cart form that can be used to add/update/delete user info. I've used react-hook-form for form submission and validation. My initial state is empty array. When user is added, objects are appended in the state array like -
state = [
{ name: 'abc', age: '23' },
{ name: 'katy', age: '12' },
];
How can update the value in state if a div row has an edit button and it displays it in an existing input box and when i click update(another button), it updates the corresponding value.
Note- name can be same hence i can't use a state.find().
One approach
const UpdateComponent = ({ id, user, setState }) => {
const [userData, setUserData] = React.useState({
id: 0,
name: "",
age: 0
});
React.useEffect(() => {
setUserData({ id: id, name: user.name, age: user.age });
}, [user, id]);
const onChange = (e) => {
setUserData((currentData) => ({
...currentData,
[e.target.name]: e.target.value
}));
};
const onSubmit = () => {
setState((currentState) =>
currentState.map((u) => (u.id === id ? userData : u))
);
};
return (
<>
<input
onChange={onChange}
name="name"
value={userData.name}
placeholder="Name"
/>
<input
onChange={onChange}
name="age"
value={userData.age}
placeholder="Age"
/>
<button onClick={onSubmit} type="button">
Update
</button>
</>
);
};
const List = () => {
const [state, setState] = React.useState([]);
React.useEffect(() => {
setState(
[
{ name: "abc", age: "23" },
{ name: "katy", age: "12" }
].map((u, i) => ({ ...u, id: i }))
);
}, []);
React.useEffect(() => {
// debug
console.log(state);
}, [state]);
return (
<div>
{state.map((user) => (
<UpdateComponent
key={user.id}
id={user.id}
user={user}
setState={setState}
/>
))}
</div>
);
};
Take a look https://codesandbox.io/s/fragrant-surf-p5cxh?file=/src/App.js
You could use the UUID package to generate the IDs:
React.useEffect(() => {
// Generates IDs when loading the data as example
// but ideally IDs are created on user creation
setState(
[
{ name: "abc", age: "23" },
{ name: "katy", age: "12" }
].map((u) => ({ ...u, id: uuidv4() }))
);
}, []);
Sandbox: https://codesandbox.io/s/unruffled-hypatia-tjzcx
But that is not very different from the initial approach with the map index id(on the component mount), that is not ideal because we are generating IDs when the component mounts, but at least they don't keep changing on each render
My initial state is empty array. When user is added, objects are appended in the state array like
For your case you could just have an ID that increments each time a user is added, or use the uuid when its added, so your data already comes with the ID
I am trying to make a checkout form using Formik, however when I click the submit button and log the data to my console I don't see any changes until the second click.
onSubmit={(values) => {
addData(values);
console.log(data);
}}>
const addData = (billing) => {
setData((currentData) => {
const shipping = {
first_name: billing.first_name,
last_name: billing.last_name,
address_1: billing.address_1,
address_2: billing.address_2,
city: billing.city,
state: billing.state,
postcode: billing.postcode,
country: billing.country,
};
return {billing, shipping, ...currentData};
});
};
const [data, setData] = useState({
payment_method: 'bacs',
payment_method_title: 'Direct Bank Transfer',
set_paid: true,
line_items: [
{
product_id: 93,
quantity: 2,
},
{
product_id: 22,
variation_id: 23,
quantity: 1,
},
],
shipping_lines: [
{
method_id: 'flat_rate',
method_title: 'Flat Rate',
total: '10',
},
],
});
Additionally, when I go to change the data (my form doesn't reset on submission) I still see the old data and it doesn't update.
I just have just passed it and I could resolved it!
It seems like it renders just on next click and then updates.
In order to fix that, please try React useEffect Hook.THis is my example for React Native
import React, { useState, useEffect } from 'react';
import { View, TextInput, Button } from 'react-native';
import { Formik } from 'formik';
export const FormikPub = (props) => {
const [formikPub, setFormikPub] = useState(
{foo: '', bar:''}
)
useEffect(() => {
console.log(`After setFormikPub(): ${JSON.stringify(formikPub)}`)
}, [formikPub]);
return (
<Formik
initialValues={{ foo: '', bar: '' }}
onSubmit={(values) => {
setFormikPub(values)
}}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput
name='foo'
placeholder="Foo"
onChangeText={handleChange('foo')}
onBlur={handleBlur('foo')}
value={values.foo}
/>
<TextInput
name='bar'
placeholder="Bar"
onChangeText={handleChange('bar')}
onBlur={handleBlur('bar')}
value={values.bar}
/>
<Button color="#004686" onPress={handleSubmit} title="Publish Formik" />
</View>
)}
</Formik>
);
}
It may able to you press the button and it will run only when you change the text input.
This is register form, I want to send the value orgTypes>name to orgType of data of same object using onChange of select.
https://codesandbox.io/s/react-typescript-zm1ov?fontsize=14&fbclid=IwAR06ZifrKrDT_JFb0A-d_iu5YaSyuQ9qvLRgqS20JgAcSwLtAyaOFOoj5IQ
When I use onChange of Select, it erases all other data of the inputs.
import React from "react";
import { Select } from "antd";
const { Option } = Select;
const RegisterFinal = () => {
const data = {
orgName: "",
orgRegNo: "",
orgType: "",
orgTypes: [
{ id: "1", name: "Vendor" },
{ id: "2", name: "Supplier" },
{ id: "3", name: "Vendor and Supplier" }
],
errors: {}
};
const [values, setValues] = React.useState(data);
const handleChange = (e: any) => {
e.persist();
setValues((values: any) => ({
...values,
[e.target.name]: e.target.value
}));
};
const handleSubmit = () => {
console.log(values);
};
return (
<React.Fragment>
<input
name="orgName"
onChange={handleChange}
placeholder="Organization Name"
value={values.orgName}
/>
<input
name="orgRegNo"
onChange={handleChange}
placeholder="Registration Number"
value={values.orgRegNo}
/>
<Select //imported from antd
id="select"
defaultValue="Choose"
onChange={(select: any) => {
setValues(select);
console.log(select); //shows Vender/Supplier, I want this value to be sent to orgType above
}}
>
{data.orgTypes.map((option: any) => (
<Option key={option.id} value={option.name}>
{option.name}
</Option>
))}
</Select>
</React.Fragment>
);
};
When I use onChange of Select, it erases all other data of the inputs.
Thank you for your help
setValues function expects to take the values object as an argument. Instead you are passing the select value to it.
Instead, you can do it like this:
const handleSelectChange = (selectValue: any) => {
setValues((values: any) => ({
...values,
orgType: selectValue
}));
}
and use handleSelectChange in the onChange of your select.
Check the solution here: https://codesandbox.io/s/react-typescript-p9bjz