Im developing a Soccer Betting system. I want to make a form dynamically and keep the objects in the state with redux-forms.
My basic entity is a Match: Who has a home_team, a away_team, and inputs with a home_result and a away_result.
matches = [
{
name: 1,
type: "group",
home_team: 1,
away_team: 2,
home_result: null,
away_result: null,
date: "2018-06-14T18:00:00+03:00",
stadium: 1,
channels: [],
finished: false
},
{
name: 2,
type: "group",
home_team: 3,
away_team: 4,
home_result: null,
away_result: null,
date: "2018-06-15T17:00:00+05:00",
stadium: 12,
channels: [],
finished: false
},
I want to fill it in a Redux-Form, but get stuck in the best way to make it. I want too that, every time a user changes the values on the input, this is reflected in the State-Json.
To dynamically create a form, you will need to build your data differently.
You need fields object that will look like this(with the matches id):
const fieldsObject = ['match1', 'match2', 'match3']
And a initialValues object that will look like this:
const resultsObject = {
match1_home: 1,
match1_away: 3
}
And so on. Then, the Form will initial its fields based on the initialValues and the names of the fields. The code:
const MyForm = (props) => {
const { handleSubmit, fields } = props;
return (
<form onSubmit={handleSubmit}>
{fields.map(match => (
<div key={match}>
<Field
name={`${match}_home`}
component="input"
/>
<Field
name={`${match}_away`}
component="input"
/>
</div>
))}
<button type="submit">Submit</button>
</form>
)
}
And the usage will look like this:
<MyForm initialValues={resultsObject} fields={fieldsObject} onSubmit={this.submitForm}/>
Related
My task is to do create a form that will get some fields from STRAPI api.
A form will be component, that will be passed to different pages.
So in each page there will be same form but with different fields based on Strapi.
Form structure will look like this:
const apiCall = {
dropdown: [
// there can be 0 objects or infinite objects, based on strapi api call
{
question: "how are you?",
answers: [
{name: "Good"},
{name: "Bad"}
// infinite number of answers
]
},
{
question: "how are you?",
answers: [
{name: "Good"},
{name: "Bad"}
// infinite number of answers
]
}
]
}
Then in Formik i need to do something like this:
// DROPDOWNS
{apiCall.dropdown?.map(({ answers, question, id}) => (
<div key={id} className="mb-5">
<h2 className="h3 mb-2">
{question}
</h2>
<FormSelect
key={id}
name={} //??? IDK
label={t('validation.oneOfOptions')}
options={
answers?.map(a => (
{value: a.name, label: a.name}
))
}
className={formFieldClasses}
/>
</div>
How should i initialize formik values and validate data? I am using Formik component.
Then i need to send data to another api call where we are going to send emails with answers. But IDK how can i send dynamic data when i dont know data that will come from apiCall?
Normally i will do something like this:
const initialValues: LostFormValues = {
category: CategoryValues.WALLET,
secCategory: SecCategoryValues.TRAIN,
priority: PriorityValues.HIGH,
cityFrom: null,
cityTo: null,
date: today,
ticketNumber: user?.user?.accountCode || '',
firstName: user?.user?.firstName || '',
surname: user?.user?.surname || '',
email: user?.user?.email || '',
phoneNumber: user?.user?.phoneNumber || '',
description: '',
files: [],
agreeWithTerms: false,
};
and then:
<Formik
enableReinitialize
initialValues={initialValues}
validationSchema={yup.object().shape({
category: yupFieldEnum(CategoryValues),
secCategory: yupFieldEnum(SecCategoryValues),
date: yupField.date,
ticketNumber: yup.string().nullable(), // not required
priority: yupField.string,
cityFrom: yupField.autoComplete,
cityTo: yupField.autoComplete,
firstName: yupField.string,
surname: yupField.string,
email: yupField.email,
description: yupField.string,
agreeWithTerms: yupField.checkbox,
})}
onSubmit={async (values) => {
const isSent = await sendToExternalService(values);
}}
>
But now i cant just write variables and then send them.. i need to write them dynamicaly
so this quite a big question, and a lot of unnecessary info, not sure I'm getting correct all the points however based on api you want to validate answers so if you have field answers you should have field answer, basically what i would do:
const apiCall = [{..},{..}]
const ParentComponent = () => {
const [answers, setAnswers] = useState({})
return ( <>
{apiCall.dropdown.map(item => (
<SelectComponent item={item} setAnswers={setAnswers} key={..}/>
)}
}
const SelectComponent = ({item}) => (
const formik = useFormik({
initialValues: {
answer: null,
}
validationSchema: Yup.object().shape({
answer: Yup.string().oneOf(item.answers.map(({name})=> name).nullable()}),
onSubmit: (values) => setAnswers(answers => (
{...answers, [item.question]:values.answer})
);
})
return (
<>...
<FormSelect name='answer'
value={formik.values.answer} options={
item.answers.map(a => (
{value: a.name, label: a.name}
))
}
/>
<Button onClick={() => formik.onSubmit()}>Ok</Button>
</>)
)
So now you keep your answers as key value pairs in parent component and you validate exact question, based on what in the api. Note there is many ways to go from here depending on how you would pass data to api...
Not sure if it would work out of the box if you dynamically change apiCall on fly but this should be also possible to handle
I want to allow the user to choose a teacher's name from a drop down and insert that teacher's "teacher_name" and "teacher_id" as 2 separate fields in Firestore database.
I have the following input field which creates a drop down by "teacher_name". Now I can either pass "teacher_name" OR "teacher_id" under optionValue to insert that field. Is there a way to insert both "teacher_name" AND "teacher_id" as 2 separate fields?
<ReferenceInput
label="Teacher"
source="teacher_name"
reference="teachers"
>
<AutocompleteInput
optionText="teacher_name"
optionValue="teacher_name"
defaultValue={null}
/>
</ReferenceInput>
My Firestore looks like this:
Collection name : teachers
Document Structure :
{
teacher_id : "XXX",
teacher_name : "XXX",
other_fields : "XXX",
}
As mentioned in the Answer which explains How to have the whole object act as optionValue in a SelctInput in react-admin as :
This is possible using parse and format. Format makes sure the
form options in the html are just the name strings. parse translates
the selected option into the format you db needs.
Example :
const choices = [
{ id: '1', name: 'Programming' },
{ id: '2', name: 'Lifestyle' },
{ id: '3', name: 'Photography' },
];
<SelectInput
source="category"
choices={choices}
format={(c) => c.name}
parse={(name) => choices.find((c) => c.name === name)}
/>
There is another answer which explains how to input and create two (or more than two) fields using React-Admin as:
allows users to select an existing record related
to the current one (e.g. choosing the author for a post). if that you
want to create a new record instead. You can do so via the onCreate
prop, as explained in the doc:
import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin';
const PostCreate = () => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
]; return (
<Create>
<SimpleForm>
<TextInput source="title" />
<AutocompleteInput
onCreate={(filter) => {
const newCategoryName = window.prompt('Enter a new category', filter);
const newCategory = { id: categories.length + 1, name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
source="category"
choices={categories}
/>
</SimpleForm>
</Create>
);
}
For more information, you can refer to the Official documentation which explains about the input components and common input props.
I want to add 4 empty inputText fields once button Add Person is clicked, which then needs to be filled and update values property. Those empty inputs supposed to be added to values.persons property which is an array of objects once addPerson()is called. I have some logic inside, but unfortunately nothing happens with this code
const data = [
{
luxmedType: {
package: "Indywidualny",
type: "Opieka standardowa",
cost: 0
},
companyCost: 91.6,
comment: null,
persons: [
{
name: "Marian",
lastName: "Kowalski",
type: "współpracownik",
cooperationForm: "B2B"
}
]
},
{
luxmedType: {
package: "Rodzinny",
type: "Opieka premium",
cost: 559.1
},
companyCost: 0,
comment: null,
persons: [
{
name: "Ewa",
lastName: "Kowalska",
type: "partner",
cooperationForm: null
},
{
name: "Maria",
lastName: "Kowalska",
type: "dziecko",
cooperationForm: null
}
]
},
{
luxmedType: {
package: "osobisty",
type: "Opieka premium",
cost: 1000
},
companyCost: 0,
comment: null,
persons: [
{
name: "Anna",
lastName: "Michalska",
type: "partner",
cooperationForm: null
},
{
name: "Maria",
lastName: "Michalska",
type: "dziecko",
cooperationForm: null
},
{
name: "Aleksander",
lastName: "Michalski",
type: "dziecko",
cooperationForm: null
}
]
}
];
const initialValues = data;
export default function App() {
const { values, handleChange, onSubmit, setValues } = useFormik({
initialValues,
onSubmit: (values) => console.log(values)
});
const addPerson = () => {
const persons = [...values.persons];
persons.push({
name: "",
lastName: "",
type: "",
cooperationForm: null,
id: Math.random()
});
setValues({ ...values,persons });
};
return (
<div>
<h1>Luxmed</h1>
<form onSubmit={onSubmit}>
{initialValues.map((object) => {
let luxmedType = object.luxmedType,
companyCost = object.companyCost,
comment = object.comment,
persons = object.persons;
return (
<div style={{ marginBottom: "20px" }}>
<InputText
name="package"
value={luxmedType.package}
onChange={handleChange}
/>
<InputText
name="type"
value={luxmedType.type}
onChange={handleChange}
/>
<InputText
name="cost"
value={luxmedType.cost}
onChange={handleChange}
/>
<InputText
name="companyCost"
value={companyCost}
onChange={handleChange}
/>
<InputText
name="companyCost"
value={comment || ""}
onChange={handleChange}
/>
{persons.map((person) => {
return (
<div>
<InputText
name="package"
value={person.name}
onChange={handleChange}
/>
<InputText
name="package"
value={person.lastName}
onChange={handleChange}
/>
<InputText
name="package"
value={person.type}
onChange={handleChange}
/>
<InputText
name="package"
value={person.cooperationForm}
onChange={handleChange}
/>
<button>Remove</button>
</div>
);
})}
</div>
);
})}
<button onClick={() => addPerson()}>Add Person</button>
<button type="submit">submit</button>
</form>
</div>
);
}
Here is also codesandbox for You if You wish
https://codesandbox.io/s/serene-bose-x6qk9y?file=/src/App.js:1552-1553&fbclid=IwAR3Q6bnkVyCtox5hTCHaFibtKJ92huwkSDVHKPOSiTdoA743lURBq76Abq0
Thanks
You should iterate (map) values instead of initialValues
Every mapped item should have unique key (every object in value array as well as every person in persons array). Consider adding unique keys to objects itself.
Also you can pass reference to addPerson function instead of anonymous function onClick={addPerson} and in addPerson you will receive event as an argument and you should add event.preventDefault() in your function body to prevent re-loading of page when you click you button
Instead of using setValue and passing updated values as an object, use setValue with callback function setValue(oldValues => {/*...Do you update here and return updated values */}) when you need previous values in state update. This will guarantee that most up to date state is used.
Your values are not an object with person property of type array, but array of object each having property persons as an array. So I believe you want to add new object to an array of objects. Do not mutate current array, instead copy existing one. Then push new object with all properties luxmedType, companyCost, comment, persons. Push new person to newly added objects persons array.Then setValue with updated array of objects (use callback update as in 4th step).
Please try to correct code yourself and ask questions if something unclear so community can give you more hints and suggestions if you stuck.
I want to change a state of an array which will have values derived from data.js file as a starting point. Changing a state means running function setAllThingsArray and adding new element which will be set using a function setThingsArray based on previous state (thingsArray). Do I need to map allThingsArray to get updated array with added elements and to list them ?
import React from "react";
import data from "../data";
import Stamp from "../components/Stamp";
export default function AddItem(props) {
const [thingsArray, setThingsArray] = React.useState({
key: 9,
img: "../images/stamp1.jpg",
name: "Stamp 5",
description: "AAAA",
rating: 78.0,
});
function addItem() {
setAllThingsArray((prevAllThingsArray) => ({
arr: [
...prevAllThingsArray.arr,
setThingsArray((prevThingsArray) => ({
...prevThingsArray,
key: prevThingsArray.key + 1,
img: "../images/stamp1.jpg",
name: "Stamp 5",
description: "AAAA",
rating: 78.0,
})),
],
}));
}
const [allThingsArray, setAllThingsArray] = React.useState(data);
const allThingsElements = allThingsArray.map((st) => {
return <Stamp key={st.key} st={st} />;
});
return (
<>
<div className="form">
<input type="text" placeholder="Image" className="form--input" />
<input type="text" placeholder="Stamp title" className="form--input" />
<input type="text" placeholder="Description" className="form--input" />
<input type="text" placeholder="Rating" className="form--input" />
{/* <button className="form--button" onClick={addStampToJson(json_stamp)}>
Add Stamp
</button> */}
<button className="form--button" onClick={addItem}>
Add Stamp
</button>
</div>
<section className="stamps-list">{allThingsElements}</section>
</>
);
}
Is it correct to place setThingsArray function inside setAllThingsArray function?
state setters (setAllThingsArray and setThingsArray) are asynchronous, so you cannot use a state update within another state update. setThingsArray itself is also a function (not a state value), so you cannot set it directly into setAllThingsArray either.
I'd propose that you should add another variable to keep an updated object (not an updated state), and then update states separately with the updated object.
function addItem() {
//add an updated object
const updatedThingsArray = {
...thingsArray, //this is an actual previous state
key: prevThingsArray.key + 1,
img: "../images/stamp1.jpg",
name: "Stamp 5",
description: "AAAA",
rating: 78.0,
}
setAllThingsArray((prevAllThingsArray) => ([
...prevAllThingsArray,
updatedThingsArray, //it's become a new object with your updated data
]));
setThingsArray(updatedThingsArray) //update it back to the `thingsArray` as the new state
}
The JSON data is used to create dynamic Input fields for each item in the array. I would like the JSON data to be updated to match the quantity selected but am unsure the best way to go about this?
I plan on using Hooks to initially store the number of items selected then update the JSON file with a button press, although I am very open to the JSON file updating onChange. what is the best practise for this can you dynamically create react hooks?
here is my current code(I want the quantity to update in the JSON file).
JSON:
//Json data for the shopping ingredients
export default [
{
bread: {
Quantity: 0,
},
Name: 'Bread',
Price: "1.10",
},
{
milk: {
Quantity: 0,
},
Name: 'Milk',
Price: "0.50",
},
{
cheese: {
Quantity: 0,
},
Name: 'Cheese',
Price: "0.90",
},
{
soup: {
Quantity: 0,
},
Name: 'Soup',
Price: "0.60",
},
{
butter: {
Quantity: 0,
},
Name: 'Butter',
Price: "1.20",
}
]
React:
import React, { useState, useEffect } from "react";
import Data from '../shoppingData/Ingredients';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState("pageOne");
//updates quatity of ingredients
const [bread_quantity, setBread_quantity] = useState(0);
const [milk_quantity, setMilk_quantity] = useState(0);
const [cheese_quantity, setCheese_quantity] = useState(0);
const [soup_quantity, setSoup_quantity] = useState(0);
const [butter_quantity, setButter_quantity] = useState(0);
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show("pageOne");
} else {
setPageone_show("pageOne hide");
}
}, [props.showOne]);
return (
<div className={"Shopping_Content " + pageone_show}>
{Data.map((Ingredients) => {
return <div className="Shopping_input" key={Ingredients.Name}>
<p>{Ingredients.Name} £{Ingredients.Price}</p>
<input onChange={} type="number"></input>
</div>
})}
<div className="Shopping_Buttons">
<p onClick={props.next_ClickHandler}>Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
Having input fields generated dynamically from a JSON file is great but using static hooks to update the JSON seems rather silly.
To work with a file system you need to have particular modules. You can do it using NodeJS using 'fs' module https://nodejs.org/dist/latest-v15.x/docs/api/fs.html.
You need a separate endpoint that will be responsible for data updating that will be on the server-side.