Populate react select options array with key value pairs from firebase collection - arrays

I am trying to use an options array in my react app, that uses react-select for the form and where the options are stored in a firebase collection.
This all works fine when I define a const in the form with an array of options that I define with key value pairs, but I'm struggling to figure out how to replace that array with the collection stored in Firebase (Cloud Firestore).
In my form, I currently have:
const options = [
{ value: "neurosciences", label: "Neurosciences - ABS 1109" },
{ value: "oncologyCarcinogenesis", label: "Oncology and Carcinogenesis - ABS 1112" },
{ value: "opticalPhysics", label: "Optical Physics - ABS 0205" },
{ value: "fisheriesSciences", label: "Fisheries Sciences - ABS 0704" },
{ value: "genetics", label: "Genetics - ABS 0604" },
{ value: "urbanRegionalPlanning", label: "Urban and Regional Planning - ABS 1205" }
];
I want to replace this array, with a map over the document titles in the database collection.
The document name in my database has the key and each document has a single field called 'title'.
Thank in my form select I have:
<div className="form-group">
<label htmlFor="fieldOfResearch">
Select your field(s) of research
</label>
<Select
key={`my_unique_select_key__${fieldOfResearch}`}
name="fieldOfResearch"
isMulti
className={
"react-select-container" +
(errors.fieldOfResearch && touched.fieldOfResearch ? " is-invalid" : "")
}
classNamePrefix="react-select"
value={this.state.selectedValue1}
onChange={e => {
handleChange1(e);
this.handleSelectChange1(e);
}}
onBlur={setFieldTouched}
options={options}
/>
{errors.fieldOfResearch && touched.fieldOfResearch &&
<ErrorMessage
name="fieldOfResearch"
component="div"
className="invalid-feedback d-block"
/>}
</div>
I have read the firebase documents on using arrays, but I am missing something (probably obvious) that has led me down at least 20 different paths for how to do this.
I'm not sure if this is relevant, but my forms are built with Formik.
How do I replace the const options array with a map over key value pairs from the firebase database collection?
I have tried to define my options constant as:
const options = fsDB.collection("abs_for_codes")
but the page fills up with errors that I can't decipher. I have read this user guide, but don't understand the directions relating to indexes and I'm not even clear on whether they're what I need to know for this problem.
https://firebase.google.com/docs/firestore/query-data/queries
I have also tried:
const options = fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc))
}
but that's just guessing from trying to make sense of the documentation.
When I try the exact formulation shown in the firebase docs, as:
const options = fsDB.collection("abs_for_codes");
options.get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
console.log(doc.id, ' => ', doc.data());
});
});
I get a full page of indecipherable error messages, as follows:
TypeError: options.reduce is not a function
Select.buildMenuOptions
node_modules/react-select/dist/react-select.esm.js:4123
4120 | };
4121 | };
4122 |
> 4123 | return options.reduce(function (acc, item, itemIndex) {
| ^ 4124 | if (item.options) {
4125 | // TODO needs a tidier implementation
4126 | if (!_this3.hasGroups) _this3.hasGroups = true;
View compiled
new Select
node_modules/react-select/dist/react-select.esm.js:3593
3590 |
3591 | var _selectValue = cleanValue(value);
3592 |
> 3593 | var _menuOptions = _this.buildMenuOptions(_props, _selectValue);
| ^ 3594 |
3595 | _this.state.menuOptions = _menuOptions;
3596 | _this.state.selectValue = _selectValue;
View compiled
constructClassInstance
node_modules/react-dom/cjs/react-dom.development.js:11787
11784 | new ctor(props, context); // eslint-disable-line no-new
11785 | }
11786 | }
> 11787 | var instance = new ctor(props, context);
| ^ 11788 | var state = workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null;
11789 | adoptClassInstance(workInProgress, instance);
11790 | {
View compiled
updateClassComponent
node_modules/react-dom/cjs/react-dom.development.js:15265
15262 | } // In the initial pass we might need to construct the instance.
15263 |
15264 |
> 15265 | constructClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
| ^ 15266 | mountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
15267 | shouldUpdate = true;
15268 | } else if (current$$1 === null) {
View compiled
beginWork
node_modules/react-dom/cjs/react-dom.development.js:16265
16262 |
16263 | var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);
16264 |
> 16265 | return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
| ^ 16266 | }
16267 |
16268 | case HostRoot:
View compiled
performUnitOfWork
node_modules/react-dom/cjs/react-dom.development.js:20285
20282 | startProfilerTimer(workInProgress);
20283 | }
20284 |
> 20285 | next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
| ^ 20286 | workInProgress.memoizedProps = workInProgress.pendingProps;
20287 |
20288 | if (workInProgress.mode & ProfileMode) {
View compiled
workLoop
node_modules/react-dom/cjs/react-dom.development.js:20326
20323 | if (!isYieldy) {
20324 | // Flush work without yielding
20325 | while (nextUnitOfWork !== null) {
> 20326 | nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
| ^ 20327 | }
20328 | } else {
20329 | // Flush asynchronous work until there's a higher priority event
View compiled
HTMLUnknownElement.callCallback
node_modules/react-dom/cjs/react-dom.development.js:147
144 | window.event = windowEvent;
145 | }
146 |
> 147 | func.apply(context, funcArgs);
| ^ 148 | didError = false;
149 | } // Create a global error event handler. We use this to capture the value
150 | // that was thrown. It's possible that this error handler will fire more
View compiled
invokeGuardedCallbackDev
node_modules/react-dom/cjs/react-dom.development.js:196
193 | // errors, it will trigger our global error handler.
194 |
195 | evt.initEvent(evtType, false, false);
> 196 | fakeNode.dispatchEvent(evt);
| ^ 197 |
198 | if (windowEventDescriptor) {
199 | Object.defineProperty(window, 'event', windowEventDescriptor);
View compiled
invokeGuardedCallback
node_modules/react-dom/cjs/react-dom.development.js:250
247 | function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
248 | hasError = false;
249 | caughtError = null;
> 250 | invokeGuardedCallbackImpl$1.apply(reporter, arguments);
| ^ 251 | }
252 | /**
253 | * Same as invokeGuardedCallback, but instead of returning an error, it stores
View compiled
replayUnitOfWork
node_modules/react-dom/cjs/react-dom.development.js:19509
19506 |
19507 | isReplayingFailedUnitOfWork = true;
19508 | originalReplayError = thrownValue;
> 19509 | invokeGuardedCallback(null, workLoop, null, isYieldy);
| ^ 19510 | isReplayingFailedUnitOfWork = false;
19511 | originalReplayError = null;
19512 |
View compiled
renderRoot
node_modules/react-dom/cjs/react-dom.development.js:20439
20436 | if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
20437 | if (mayReplay) {
20438 | var failedUnitOfWork = nextUnitOfWork;
> 20439 | replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
| ^ 20440 | }
20441 | } // TODO: we already know this isn't true in some cases.
20442 | // At least this shows a nicer error message until we figure out the cause.
View compiled
performWorkOnRoot
node_modules/react-dom/cjs/react-dom.development.js:21363
21360 | cancelTimeout(timeoutHandle);
21361 | }
21362 |
> 21363 | renderRoot(root, isYieldy);
| ^ 21364 | finishedWork = root.finishedWork;
21365 |
21366 | if (finishedWork !== null) {
View compiled
Another attempt:
const options = abs_for_codes.map((title) => {
<option key={title}
value={id} />
}
This doesn't work either - I tried it because it looks similar to the react arrays instructions.
The attached image shows the data structure in firestore.
NEXT ATTEMPT
Using Murray's suggestion, I have tried
import Select from "react-select";
import { fsDB, firebase, settings } from "../../../firebase";
let options = [];
const initialValues = {
fieldOfResearch: null,
}
class ProjectForm extends React.Component {
state = {
selectedValue1: options,
}
handleSelectChange1 = selectedValue1 => {
this.setState({ selectedValue1 });
};
componentDidMount() {
fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
let newOptions = [];
querySnapshot.forEach(function (doc) {
console.log(doc.id, ' => ', doc.data());
newOptions.push({
value: doc.data().title.replace(/( )/g, ''),
label: doc.data().title + ' - ABS ' + doc.id
});
});
this.setState({options: newOptions});
});
}
handleSubmit = (formState, { resetForm }) => {
// Now, you're getting form state here!
console.log("SUCCESS!! :-)\n\n", formState);
fsDB
.collection("project")
.add(formState)
.then(docRef => {
console.log("docRef>>>", docRef);
this.setState({ selectedValue1: null });
this.setState({ selectedValue2: null });
this.setState({ selectedValue3: null });
this.setState({ selectedValue4: null });
this.setState({ selectedValue5: null });
this.setState({ selectedValue6: null });
resetForm(initialValues);
})
.catch(error => {
console.error("Error adding document: ", error);
});
};
onSubmit={this.handleSubmit}
render={({ errors, status, touched, setFieldTouched, handleSubmit, values }) => {
let fieldOfResearch;
const handleChange1 = optionsObject => {
fieldOfResearch = optionsObject;
return (values.fieldOfResearch = optionsObject.value);
};
<div className="form-group">
<label htmlFor="fieldOfResearch">
Select your field(s) of research
</label>
<Select
key=
{`my_unique_select_key__${fieldOfResearch}`}
name="fieldOfResearch"
isMulti
className={
"react-select-container" +
(errors.fieldOfResearch && touched.fieldOfResearch ? " is-invalid" : "")
}
classNamePrefix="react-select"
value={this.state.selectedValue1}
onChange={e => {
handleChange1(e);
this.handleSelectChange1(e);
}}
onBlur={setFieldTouched}
options={options}
/>
{errors.fieldOfResearch && touched.fieldOfResearch &&
<ErrorMessage
name="fieldOfResearch"
component="div"
className="invalid-feedback d-block"
/>}
</div>
So, stepping that through, options starts as an empty array, the ComponentDidMount function resets its state to NewOptions and that gets fed into the form select drop down.
That all makes sense to me, but it doesn't work - I just get an empty array.
When I try Avanthika's suggestion, i can render the form and multiple options can be selected from the right db collection, but nothing happens when I submit the form. The console debugger in react shows an unsmiling face (I've never seen that before. Pic below). This form submits fine when I remove the select field.
next attempt
when i try each of Murray R and Avinthika's updated suggestions below I can choose multiple fields. BUT i cannot submit the form. The form submits if i remove the select field. Is there a trick to submitting formik multi field forms?
My submit button is:
<div className="form-group">
<Button
variant="outline-primary"
type="submit"
style={style3}
id="ProjectId"
onClick={handleSubmit}
disabled={!dirty || isSubmitting}
>
Save
</Button>
</div>
My handle submit has:
handleSubmit = (formState, { resetForm }) => {
// Now, you're getting form state here!
console.log("SUCCESS!! :-)\n\n", formState);
fsDB
.collection("project")
.add({
...(formState),
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(docRef => {
console.log("docRef>>>", docRef);
this.setState({ selectedValue1: null, selectedValue2: null, selectedValue3: null, selectedValue4: null, selectedValue5: null, selectedValue6: null });
// this.setState({ selectedValue1: null });
// this.setState({ selectedValue2: null });
// this.setState({ selectedValue3: null });
// this.setState({ selectedValue4: null });
// this.setState({ selectedValue5: null });
// this.setState({ selectedValue6: null });
resetForm(initialValues);
})
.catch(error => {
console.error("Error adding document: ", error);
});
};
The console doesn't log anything.
next attempt
I removed and reinstalled the react chrome extension and that's working again.
The attached screen shot shows the form isn't validating and isn't submitting, but the state of each of the form values is in there - you can see the bottom of the shot shows one of the form field values as 's'.
further attempt
So - I split this form out into a form that only has one field- the select field that I have been trying to work on here.
That form, in its entirety, has:
import React from 'react';
import { Formik, Form, Field, ErrorMessage, withFormik } from "formik";
import * as Yup from "yup";
import Select from "react-select";
import { fsDB, firebase, settings } from "../../../firebase";
import {
Badge,
Button,
Col,
ComponentClass,
Feedback,
FormControl,
FormGroup,
FormLabel,
InputGroup,
Table,
Row,
Container
} from "react-bootstrap";
const initialValues = {
fieldOfResearch: null,
}
class ProjectForm extends React.Component {
state = {
options: [],
selectedValue1: [],
}
async componentDidMount() {
// const fsDB = firebase.firestore(); // Don't worry about this line if it comes from your config.
let options = [];
await fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, ' => ', doc.data());
options.push({
value: doc.data().title.replace(/( )/g, ''),
label: doc.data().title + ' - ABS ' + doc.id
});
});
});
this.setState({
options
});
}
handleSelectChange1 = selectedValue1 => {
this.setState({ selectedValue1 });
};
handleSubmit = (formState, { resetForm }) => {
// Now, you're getting form state here!
console.log("SUCCESS!! :-)\n\n", formState);
fsDB
.collection("project")
.add({
...(formState),
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(docRef => {
console.log("docRef>>>", docRef);
this.setState({ selectedValue1: null});
resetForm(initialValues);
})
.catch(error => {
console.error("Error adding document: ", error);
});
};
render() {
const { options } = this.state;
return (
<Formik
initialValues={initialValues}
validationSchema={Yup.object().shape({
// fieldOfResearch: Yup.array().required("What is your field of research?"),
})}
onSubmit={this.handleSubmit}
render={({ errors, status, touched, setFieldTouched, handleSubmit, isSubmitting, dirty, values }) => {
let fieldOfResearch;
const handleChange1 = optionsObject => {
fieldOfResearch = optionsObject;
return (values.fieldOfResearch = optionsObject.value);
};
return (
<div>
<Form>
<div className="form-group">
<label htmlFor="fieldOfResearch">
Select your field(s) of research
</label>
<Select
key={`my_unique_select_key__${fieldOfResearch}`}
name="fieldOfResearch"
isMulti
className={
"react-select-container" +
(errors.fieldOfResearch && touched.fieldOfResearch
? " is-invalid"
: "")
}
classNamePrefix="react-select"
value={this.state.selectedValue1}
onChange={e => {
handleChange1(e);
this.handleSelectChange1(e);
}}
onBlur={setFieldTouched}
options={options}
/>
{errors.fieldOfResearch && touched.fieldOfResearch &&
<ErrorMessage
name="fieldOfResearch"
component="div"
className="invalid-feedback d-block"
/>}
</div>
<div className="form-group">
<Button
variant="outline-primary"
type="submit"
id="ProjectId"
onClick={handleSubmit}
// disabled={!dirty || isSubmitting}
>
Save
</Button>
</div>
</Form>
</div>
);
}}
/>
);
}
}
export default ProjectForm;
This form allows the selection of a field of research in the form. The on submit function works in the console, to the extent that it logs success with a fieldOfResearch as 'undefined'. Nothing persists to the database.
The error message says: Unhandled Rejection (FirebaseError): Function
DocumentReference.set() called with invalid data. Unsupported field
value: undefined (found in field fieldOfResearch) ▶
When I try to enter a field value and inspect the react value, the error message says:
Uncaught TypeError: Cannot convert undefined or null to object

Another updated answer:
The error message says: Unhandled Rejection (FirebaseError): Function
DocumentReference.set() called with invalid data. Unsupported field
value: undefined (found in field fieldOfResearch
This error happened because your form values not valid. You are not maintaining proper formik state.
I just tried this and checked, form submit works great for me. You've written too much of excess code - we just need native formik methods and firebase. The change log is as follows:
The onChange of react-select should use setFieldValue from Formik like this:
onChange={selectedOptions => {
// Setting field value - name of the field and values chosen.
setFieldValue("fieldOfResearch", selectedOptions)}
}
The initial value should be an empty array. Since we have initialValues declared and the formvalues maintained via Formik, there's absolutely no need for internal state management. I.E, there's no need for this.state.selectedValue1, handleChange1 and handleSelectChange1. If you take a look at the render() of your Formik HOC, you'll notice values - This gives current value of the form after every change.
So,
value={this.state.selectedValue1}
should be changed to
value={values.fieldOfResearch}
I've written the handleSubmit like this - The exact replica of your code. But I'm only extracting values from the array of selected options:
handleSubmit = (formState, { resetForm }) => {
// Now, you're getting form state here!
const fdb = firebase.firestore();
const payload = {
...formState,
fieldOfResearch: formState.fieldOfResearch.map(t => t.value)
}
console.log("formvalues", payload);
fdb
.collection("project")
.add(payload)
.then(docRef => {
console.log("docRef>>>", docRef);
resetForm(initialValues);
})
.catch(error => {
console.error("Error adding document: ", error);
});
}
I'm able to see the form submission & the docRef in the console. The form also gets reset to initial state.
import React from "react";
import { Formik, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import Select from "react-select";
import firebase from "./firebase";
import {
Button,
Container
} from "react-bootstrap";
const initialValues = {
fieldOfResearch: []
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
options: []
};
}
async componentWillMount() {
const fdb = firebase.firestore();
let options = [];
await fdb
.collection("abs_codes")
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
options.push({
value: doc.data().title.replace(/( )/g, ""),
label: doc.data().title
});
});
});
this.setState({
options
});
}
handleSubmit = (formState, { resetForm }) => {
// Now, you're getting form state here!
const fdb = firebase.firestore();
const payload = {
...formState,
fieldOfResearch: formState.fieldOfResearch.map(t => t.value)
}
console.log("formvalues", payload);
fdb
.collection("project")
.add(payload)
.then(docRef => {
console.log("docRef>>>", docRef);
resetForm(initialValues);
})
.catch(error => {
console.error("Error adding document: ", error);
});
}
render() {
const { options } = this.state;
return (
<Container>
<Formik
initialValues={initialValues}
validationSchema={Yup.object().shape({
fieldOfResearch: Yup.array().required("What is your field of research?"),
})}
onSubmit={this.handleSubmit}
render={({
errors,
status,
touched,
setFieldValue,
setFieldTouched,
handleSubmit,
isSubmitting,
dirty,
values
}) => {
return (
<div>
<Form>
<div className="form-group">
<label htmlFor="fieldOfResearch">
Select your field(s) of research
</label>
<Select
key={`my_unique_select_keyfieldOfResearch`}
name="fieldOfResearch"
isMulti
className={
"react-select-container" +
(errors.fieldOfResearch && touched.fieldOfResearch
? " is-invalid"
: "")
}
classNamePrefix="react-select"
value={values.fieldOfResearch}
onChange={selectedOptions => {
setFieldValue("fieldOfResearch", selectedOptions)}
}
onBlur={setFieldTouched}
options={options}
/>
{errors.fieldOfResearch && touched.fieldOfResearch &&
<ErrorMessage
name="fieldOfResearch"
component="div"
className="invalid-feedback d-block"
/>
}
</div>
<div className="form-group">
<Button
variant="outline-primary"
type="submit"
id="ProjectId"
onClick={handleSubmit}
disabled={!dirty || isSubmitting}
>
Save
</Button>
</div>
</Form>
</div>
);
}}
/>
</Container>
);
}
}
export default App;
Just try copy pasting this first, and on top of this, try making your changes. I guess this should be helpful for you!
Updated Answer:
Hi Mel, I just set the whole thing in my system and tried doing it for you, although I cannot share the creds with you, I guess this should help.
Javascript is not synchronous. Your componentDidMount will not wait for the data you're trying to get from firebase. It will just set the state before your query returns response.
They key is to await the response. I've edited the code that way, and I'm able to see the options on my console in the render().
import React from 'react';
import firebase from "./firebase.js";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
options: []
}
}
async componentDidMount() {
const fsDB = firebase.firestore(); // Don't worry about this line if it comes from your config.
let options = [];
await fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, ' => ', doc.data());
options.push({
value: doc.data().title.replace(/( )/g, ''),
label: doc.data().title
});
});
});
this.setState({
options
});
}
render() {
console.log(this.state);
const { options } = this.state;
return (
<div className="form-group">
<label htmlFor="fieldOfResearch">
Select your field(s) of research
</label>
<Select
key={`my_unique_select_key__${fieldOfResearch}`}
name="fieldOfResearch"
isMulti
className={
"react-select-container" +
(errors.fieldOfResearch && touched.fieldOfResearch
? " is-invalid"
: "")
}
classNamePrefix="react-select"
value={this.state.selectedValue1}
onChange={e => {
handleChange1(e);
this.handleSelectChange1(e);
}}
onBlur={setFieldTouched}
options={options}
/>
</div>
);
}
}
export default App;
Let me know if this works for you!
And I couldn't help but notice, why so many setStates in handleSubmit? You're forcing your component to rerender that many times. Instead you can do:
handleSubmit = (formState, { resetForm }) => {
// Now, you're getting form state here!
console.log("SUCCESS!! :-)\n\n", formState);
fsDB
.collection("project")
.add(formState)
.then(docRef => {
console.log("docRef>>>", docRef);
this.setState({ selectedValue1: null, selectedValue2: null, selectedValue3: null, selectedValue4: null, selectedValue5: null, selectedValue6: null });
resetForm(initialValues);
})
.catch(error => {
console.error("Error adding document: ", error);
});
};

So would something like this help?
function SomeComponentName(props) {
const [options, setOptions] = React.useState([]);
React.useEffect(() => {
getOptions()
}, []}
async function getOptions() {
const tmpArr = [];
try {
// Perform get() request and loop through all docs
await fsDB
.collection("abs_codes")
.get()
.then(snapshot => {
snapshot.forEach(doc => {
const { title } = doc.data();
const label = `${title} - ABS ${doc.key}`;
tmpArr.push({ value: title, label });
});
setOptions(tmpArr);
});
} catch (err) {
console.log("Error getting documents", err);
}
}
return (
<div className="form-group">
<label>
<Select
// ...
options={options}
// ...
/>
</label>
</div>
);
}
This will get all documents within the 'abs_code' collection, loop through them, and push each entry as an object to the 'options' array.

For your React component to update after retrieving your research fields from Firestore, you'll need to declare your options in a way that React will pay attention to its value changes. You can do this by saving options within the class's state and update it later using setState():
// At the top of the class,
state = {
options: [],
// Any other state properties,
};
Then, within the componentDidMount() function, make your call to your Firestore collection and populate state.options with the results:
fsDB.collection("abs_for_codes").get().then(function (querySnapshot) {
let newOptions = [];
querySnapshot.forEach(function (doc) {
console.log(doc.id, ' => ', doc.data());
newOptions.push({
value: doc.data().title.replace(/( )/g, ''),
label: doc.data().title + ' - ABS ' + doc.id
});
});
setState({options: newOptions});
});
This should retrieve your 'abs_for_codes' documents from Firestore and assign them to your component's options property in a way that will allow your Select element to be populated with the new data.

Related

React .NET - Display data on page from a POST

I am working on a calculating application using ASP.NET Core 6 and React 18
in the .NET application, I am using some logic to return some data, the data is not stored in the database, only temporary/mock data and I am only using POST.
the code in the backend works, it returns what I want.
However, in my frontend application with React. I am using Formik for formhandeling, and it works when entering the data my backend wants in the post endpoint.
When adding a breakpoint to the endpoint it works just as I want.
But I also want the returned data to be visible in my React application.
Let me show you some code.
Backend
[HttpPost("Calculate")]
public ActionResult<TaxModel> PostCalculation(TaxModel model)
{
DateTime date;
if (model == null || !DateTime.TryParseExact(model.Date + " " + model.Time + ":00", "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
return BadRequest(BadRequestMsg);
}
try
{
var vehicle = model.Vehicle;
var calculatePrice = Calculator.GetTollFee(date, vehicle);
return Ok(new TaxModel
{
Date = model.Date,
Time = model.Time,
Vehicle = model.Vehicle,
Price = calculatePrice
});
}
catch
{
return BadRequest(BadRequestMsg);
}
}
Frontend
export const TaxForm = (props: taxFormProps) => {
return(
<Formik initialValues={props.model}
onSubmit={props.onSubmit}
validationSchema={yup.object({
date: yup.string().required("Date is required"),
time: yup.string().required("Time is required"),
vehicle: yup.string().required("Vehicle is required"),
})}>
{(FormikProps) => (
<Form>
<InputField field="date" displayName="Date" />
<InputField field="time" displayName="Time" />
<InputField field="vehicle" displayName="Vehicle" />
<Button className="" type="submit"
text="Calculate" disabled={FormikProps.isSubmitting} />
</Form>
)}
</Formik>
)
}
interface taxFormProps {
model: taxModel;
onSubmit: (values: taxModel, actions: FormikHelpers<taxModel>) => void;
}
export const PostTax = () => {
const [errors, setErrors] = useState<string[]>([]);
const [tax, setTax] = useState<taxModel>();
const create = async (tax: taxModel) => {
try{
await axios.post(`${UrlTax}/Calculate`, tax)
.then(response => {
if(response.status === 200){
setTax(response.data);
}
});
}
catch(error) {
if(error && error.response){
setErrors(error.response.data);
}
}
}
return(
<>
<h3>Calculate your toll Price</h3>
<DisplayErrors errors={errors} />
<TaxForm model={
{
date: "",
time: "",
vehicle: "",
price: 0
}}
onSubmit={async value => {
await create(value);
}} />
{/* {tax?.map(t =>
<p>{t.date} {t.time} {t.vehicle} {t.price}</p>
)} */}
<p>{tax}</p> // does not work
</>
)
}
in the React code, I am trying to add the posted data into the state, but it is not really working.
Any idea on how to fix this error?
Thanks!

How to display selected option in select dropdown in React JS edit form

I am using react-hook-form npm module for my edit data form. Here below is my API response sample:
{
UUID: "xxxxxx-bd10-473e-a765-xxxxxx"
address1: "addr1"
address2: "addr2"
address3: ""
city: "xxxxx"
contact: "uxxxxxb"
country: "xxxxxx"
email: "xxxxxx#email.com"
links: {Company: 'xxxxxx-4689-4689-8812-xxxxxxxxx'}
name: "xxxxx"
phone: "xxxxxxxx"
state: "xxxxxxxx"
zip: "11111"
}
Here below is the required code lines from the edit component
import axios from 'axios'
import { useForm } from 'react-hook-form'
// Now getting the default list of all the companies
Edit.getInitialProps = async (context) => {
try {
const companyResponse = await axios(process.env.BASE_URL + '/v1/companies',{
headers : {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + process.env.TOKEN
}
})
if(companyResponse.status == 200) {
return {
companies : companyResponse.data.results
}
} else {
return {companies: []}
}
} catch (error) {
return {companies: []}
}
}
export default function Edit({...props}) {
const [selectedCompany, setSelectedCompany] = useState(0)
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({mode: 'onBlur' });
useEffect(() => {
// Here I am getting location existing value from API response i.e. the above json response
setSelectedCompany(locationDetails.links.Company) // setting state of existing company of location
})
return (
<>
<form className="g-3" onSubmit={handleSubmit(editLocation)} data-testid="editLocationForm">
<select {...register('company', { required: true })} className="form-control">
{(props.companies || []).map((company, index) => (
<option key={index} value={company.UUID} defaultValue={company.UUID === selectedCompany}>{company.name}</option>
))}
</select>
.....
</form>
</>
}
I need to display the existing value of company name as selected in the drop down.
From the react-hook-form documentation you have to use the setValue or reset methods of the hook, the setValue will only set the value of the field you specify, and the reset will reset the whole form and set initial values that you specify
https://react-hook-form.com/api/useform/setvalue
https://react-hook-form.com/api/useform/reset
SetValue Approach
useEffect(() => {
// Here I am getting location existing value from API response i.e. the above json response
setSelectedCompany(locationDetails.links.Company) // setting state of existing company of location
setValue('company', locationDetails.links.Company);
})
Reset approach
useEffect(() => {
// Here I am getting location existing value from API response i.e. the above json response
setSelectedCompany(locationDetails.links.Company) // setting state of existing company of location
reset('company', locationDetails.links.Company);
})

How to load proper value in Autocomplete MUI from DB

I am new in MUI and try to replace my Select combo with Autocomplete. For now i maked to load proper value and save it in SQL Server DB but i don't know what and how to put in value property to visualize of component to load and select proper value. Data From this combo will save in insurance.autoUserId
interface IState {
insurance: InsuranceModel;
autoUsers: Array<AutoUserModel>;
autoUserDropdown: Array<DropdownModelCombo>;
autoUserSelectedValue:Array<DropdownModelCombo>;
}
constructor(props: IProps) {
super(props);
this.state = {
insurance: props.currentInsurance,
autoUsers: new Array<AutoUserModel>(),
autoUserDropdown: new Array<DropdownModelCombo>(),
autoUserSelectedValue: new Array<DropdownModelCombo>(),
};
}
ComponentDidMount
this.autoUserService.getAllActiveAutoUsers().then(
(data: any) => {
const autoUserDropdown1 : Array<DropdownModelCombo> = [];
data.map(autoUser => {
autoUserDropdown1.push({
value:autoUser.autoUserId,
label:autoUser.autoUserName
}
)
});
this.setState({
isLoading: false,
autoUsers: data,
autoUserDropdown: autoUserDropdown1,
});
})
.catch(error => {
this.snackbar.show({ severity: 'error', summary: error.message });
this.setState({
isLoading: false
});
});
Autocomplete Compopent
<Autocomplete
disablePortal
id="combo-box-demo"
value={?????}
options={autoUserDropdown}
sx={{ width: 300 }}
onChange={(e, value) => this.handleChangeSelectAutoComplete( e, value, 'autoUserId')}
renderInput={(params) => <TextField {...params} label="AutoUser" />}
/>
I found a solution. I must to return DropdownModelCombo object with proper data that i get from autoUsers where autoUserId is equal to this.state.insurance.autoUserId
const autoUserval: DropdownModelCombo ={value: 0, label: ""};
const id = this.state.insurance.autoUserId;
data.map(autoUser => {
if(autoUser.autoUserId===id)
{
autoUserval.value = autoUser.autoUserId;
autoUserval.label = autoUser.autoUserName;
}
});
this.setState({autoUserValue: autoUserval});

Antd Custom validator and validateFields are not working together as expected

I'm working on a form (Ant Design <Form>) where I want a custom validation of phone number (as it depends on country field and has some extra logic), and other fields validated by antd built-in features, so when user submits the form I want all fields to be validated with validateFields() passed as an array of field names showing validation errors as usual (red message underneath and an error message in console), but all I'm getting is just a warning message in the console.
Here's a minimal reproduction
Am I missing something about how validator function works?
make a if else check in your validatePhone function if value is not empty then run your code and if value is empty simply send a callback in else condition. like that
const validatePhone = (rule: any, value: any, callback: any) => {
if (value) {
const countryName = form.getFieldValue('country');
const country = CountryList.getCode(countryName);
const phoneNumber = parsePhoneNumberFromString(value, country as CountryCode);
console.log(form.getFieldValue('country'));
if (countryName && phoneNumber && phoneNumber.isValid()) {
updatePhonePrefix(prefix.concat(phoneNumber.countryCallingCode as string));
callback();
} else {
callback(`Phone number is not valid for ${countryName}`);
}
} else {
callback();
}
};
what the antd docs recommend is to use async or try-catch block
option 1:
const validatePhone = async (rule, value) => {
// check failing condition
throw new Error('Something wrong!');
}
option 2:
const validatePhone = (rule, value, callback) => {
try {
throw new Error('Something wrong!');
} catch (err) {
callback(err);
}
}
Here how I handled the error in antd form
import React, { useState } from 'react';
import { Form, Input } from 'antd';
function MyCustomForm(props: any) {
const [form] = Form.useForm();
const [validateFieldsName, setValidateFieldsName] = useState<string[]>([]);
const handleValidateFieldNames = (name: string) => {
const isFieldName = validateFieldsName.find(
(fieldName) => fieldName === name
);
if (isFieldName) return 'onChange';
return 'onBlur';
};
return (
<Form form={form} layout="vertical">
<Form.Item
name="contactNumber"
label="Contact Number"
validateTrigger={handleValidateFieldNames('contactNumber')}
rules={[
{
required: true,
message: 'Please enter contact number!',
},
{
validator(_, value) {
if (!value || value.length === 10) {
return Promise.resolve();
}
return Promise.reject('Please enter 10 digit Number!');
},
},
]}
>
<Input
type="number"
placeholder="Contact number"
onBlur={() =>
setValidateFieldsName([...validateFieldsName, 'contactNumber'])
}
/>
</Form.Item>
</Form>
);
}
export default MyCustomForm;

Optional field inside Options React Select

Hey guys im trying to create a autosuggestion in cooperation with redux-form. Im using the Creatable approach. I loading my options via an external API. The problem is, i need a extra field in every Option Object. {value: "test#gmx.de", label: "test#gmx.de", dn:"CN...." }. Is there a possibility to do so?
I typically add my own properties inside the callback for the API request, just before setting the options in the state. For example...
axios.get('/some/api/request')
.then(response => {
const options = response.data.map(item => {
// Add whatever custom properties you want here
return ({value: "test#gmx.de", label: "test#gmx.de", dn:"CN...." })
})
// set your options in the state to the new options constant from above
dispatch(change('formName', 'options', options))
Hope this helps!
//Handle change with either selectedOption
handleChange(selectedOption){
this.setState({ selectedOption })
if(this.props.onOptionSelect){
this.props.onOptionSelect(selectedOption.data)
}
}
loadOptions(input, callback) {
this.props.loadOptions(input).then(options => {
callback(null, {options: options})
})
}
render() {
const {selectedOption} = this.state
const selectClass = this.props.meta.touched && this.props.meta.error ? "has-error form-group" : "form-group"
return (
<div className={selectClass}>
<AsyncCreatable
value={selectedOption}
onChange={this.handleChange}
loadOptions={this.loadOptions}
isLoading={false}
placeholder={this.props.label}
promptTextCreator={(label) => this.props.promtLabel(label)}
onBlur={() => this.props.input.onBlur(selectedOption.value || "")}
/>
</div>
)
}
//Function to convert incomming users in usable options (React Select)
export const convertADUsersToOptions = users => {
return users.map(user => {
return {
value: normalizeDN(user.dn),
label: user.mail
}
})
}

Resources