<ArrayInput> with <SimpleFormIterator> of React Admin generate wrong output - react-admin

For the following:
<ArrayInput source="slotCaps" label="Schedule Caps">
<SimpleFormIterator>
<Box display="flex">
<Box mr="0.5em">
<NumberInput source="cap" step={1}/>
</Box>
<Box ml="0.5em">
<SelectInput
source="period"
choices={[
{id: "0", name: "Day"},
{id: "1", name: "Week"},
{id: "2", name: "Month"}
]}
/>
</Box>
</Box>
</SimpleFormIterator>
</ArrayInput>
I get:
{
"name": "test 5",
"description": "test 5",
"slotCaps": [
{},
{}
],
"cap": 1,
"period": "0"
}
I was expecting:
{
"name": "test 5",
"description": "test 5",
"slotCaps": [
{"cap": 1, "period": "0"},
],
}
Any idea what I am doing wrong? Could someone please explain what I have to change to obtain the second variant?
Thank you.
Edit
Not sure why but for some reason this works:
<ArrayInput source="slotCaps" label="Schedule Caps">
<SimpleFormIterator>
<NumberInput source="cap" step={1}/>
<SelectInput
source="period"
choices={[
{id: 1, name: "Day"},
{id: 2, name: "Week"},
{id: 3, name: "Month"}
]}
optionValue={"name"}
/>
</SimpleFormIterator>
</ArrayInput>
If anyone knows how to make the first variant work please provide your input. I need the fields to be formatted properly and it looks like using the Box element is the best way to achieve that.
Thank you.

Input components must be the first-level children of SimpleFormIterator, not nested ones.
If you wish to use nested Input components, which is useful for displaying the form properly with material-ui Box elements, you need to use a FormDataConsumer, like below:
<ArrayInput source="slotCaps" label="Schedule Caps">
<SimpleFormIterator>
<FormDataConsumer>
{({ getSource, scopedFormData }) => {
return (
<Box display="flex">
<Box mr="0.5em">
<NumberInput
source={getSource("cap")}
record={scopedFormData}
step={1}
/>
</Box>
<Box ml="0.5em">
<SelectInput
source={getSource("period")}
record={scopedFormData}
choices={[
{ id: "0", name: "Day" },
{ id: "1", name: "Week" },
{ id: "2", name: "Month" },
]}
/>
</Box>
</Box>
);
}}
</FormDataConsumer>
</SimpleFormIterator>
</ArrayInput>
Then, for each Input component, all you need to do is add getSource in the source prop and scopedFormData to the record prop.

remove source from NumberInput and SelectInput

Related

Vue js dynamic v-model for form which is rendered with v-for

I have 1 check box group and 9 radio button groups, and i need v-model for each group but i do not know how to put 9 differed data properties there.
My data looks like this:
data() {
return{
servicesId: [],
personId: '',
incomeId:'',
extraIncomeId: '',
pdvId: '',
paymentId: '',
clientId: '',
cashRegisterId: '',
eBankingId: '',
}
}
In template is like this:
<div v-for="data in formData" :key="data.id">
<h5>{{data.question_text}}</h5>
<div class="form-check" v-for="text_o in data.question_options" :key="text_o.id">
<input class="form-check-input"
:type="data.question_type.type"
:value="text_o.id" :id="text_o.id"
v-model="[HERE GOES "9 "data props"]">
<label class="form-check-label" :key="text_o.id" :for="text_o.id"> {{text_o.option_text}
</label>
</div>
</div>
Here is part of (because it is long, i can post everything from it if you want)formData array:
{
"id": 1,
"question_type_id": 1,
"title": "new ent",
"question_text": "Planirate da se bavite:",
"created_at": "2021-10-12T13:42:17.000000Z",
"updated_at": "2021-10-12T13:42:17.000000Z",
"question_options": [
{
"id": 1,
"question_id": 1,
"option_text": "Uslugama",
"price": 40,
"created_at": "2021-10-12T13:44:40.000000Z",
"updated_at": "2021-10-12T13:44:40.000000Z"
},
{
"id": 2,
"question_id": 1,
"option_text": "Trgovinom",
"price": 60,
"created_at": "2021-10-12T13:46:53.000000Z",
"updated_at": "2021-10-12T13:46:53.000000Z"
},
{
"id": 3,
"question_id": 1,
"option_text": "Proizvodnjom",
"price": 80,
"created_at": "2021-10-12T13:47:22.000000Z",
"updated_at": "2021-10-12T13:47:22.000000Z"
}
],
"question_type": {
"id": 1,
"type": "checkbox",
"created_at": "2021-10-12T13:40:17.000000Z",
"updated_at": "2021-10-12T13:40:17.000000Z"
}
}
Thanks.
If you want your values to be a part of data, you can create an object that will contain your form data, like this:
data() {
return {
formValue: {
servicesId: [],
personId: "",
incomeId: "",
extraIncomeId: "",
pdvId: "",
paymentId: "",
clientId: "",
cashRegisterId: "",
eBankingId: "",
},
}
}
after adding that object, just add a name to your form data object like this:
formData: [
{
id: 1,
question_type_id: 1,
title: "new ent",
question_text: "Planirate da se bavite:",
created_at: "2021-10-12T13:42:17.000000Z",
updated_at: "2021-10-12T13:42:17.000000Z",
name: "servicesId", <-- modified part
question_options: [...]
}
]
and now you can use that name to dynamically access your formValue object which is a part of data:
<div v-for="data in formData" :key="data.id">
<h5>{{ data.question_text }}</h5>
<div
class="form-check"
v-for="text_o in data.question_options"
:key="text_o.id"
>
<input
class="form-check-input"
:type="data.question_type.type"
:value="text_o.id"
:id="text_o.id"
v-model="formValue[data.name]" <-- modified part
/>
<label class="form-check-label" :key="text_o.id" :for="text_o.id">
{{ text_o.option_text }}
</label>
</div>
</div>
Hope this approach helps you, you will have your values in data, they won't be there as separate properties, but they will be a part of a wrapper object named formValue

Why query not updated value but get initial value in Function with ExtReact

I have form fn
code here
#update code. I initialization
query = {
field: '',
email: '',
}
not initialization url
export function HomePage() {
const [query, setQuery] = useState({
field: '',
email: '',
});
const handleChaneValue = (value) => {
// not received data on change
console.log('query', query);
setQuery({
...query,
[value.sender.name]: value.newValue
})
}
console.log('query2', query);
query2 received data on change
console.log('query2', query);
#Update code: I add
return (
<div>
<Container
padding={10}
platformConfig={{
desktop: {
maxWidth: 400,
}
}}
>
<FormPanel
padding={10}
shadow
defaults={{
errorTarget: 'under'
}}
margin="0 0 20 0"
title="Using Validators"
>
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={handleChaneValue}
/>
<EmailField
label="Email"
validators="email"
errorTarget="under"
name="email"
onChange={(e) => handleChaneValue(e)}
/>
<Urlfield
label="URL"
validators={{
type: 'url',
message: 'Website url example http://'
}}
errorTarget="under"
name="url"
onChange={handleChaneValue}
/>
<Container layout="center">
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{border: '1px solid black' }}/>
</Container>
</FormPanel>
</Container>
</div>
)
}
export default HomePage;
When I change value in TextField. Query is
query2: {field: 'abc'}
But i change value in Email field. Query is not give old value "{filed: 'abc'}" throught I use ES6 three dot.
query2 : {email: 'xyz'}
and Query in funciton always initialization
query: {}
image change value
#Update image: when I change value Url. fn handleChangeValue get initialization query
query: {
field: '',
email: '',
}
does not value query updated.
here issues were you did not initialize the value of textfield and emailfield I updated code pls check now
export function HomePage() {
const [query, setQuery] = useState({
field:'',
email:''
});
const handleChaneValue = (value) => {
// not received data on change
console.log("query", query);
setQuery({
...query,
[value.sender.name]: value.newValue,
});
};
console.log("query2", query);
return (
<div>
<Container
padding={10}
platformConfig={{
desktop: {
maxWidth: 400,
},
}}
>
<FormPanel
padding={10}
shadow
defaults={{
errorTarget: "under",
}}
margin="0 0 20 0"
title="Using Validators"
>
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={handleChaneValue}
value={query.field}
/>
<EmailField
label="Email"
validators="email"
errorTarget="under"
name="email"
value={query.email}
onChange={handleChaneValue}
/>
<Container layout="center">
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{ border: "1px solid black" }}
/>
</Container>
</FormPanel>
</Container>
</div>
);
}
export default HomePage;
I found the solution to the problem. I using framework ExtReact so when run function, states re-initialized initil value. To solve this problem you can useRef and refer here.
enter link description here

Count Entries from ArrayField or Datagrid in React-Admin

How can i count and output the length of an array field as a textfield or numberfield?
Code of the ArrayField that works and outputs a DataGrid
<ArrayField source="re_benutzers">
<Datagrid>
<TextField source="vorname" />
</Datagrid>
</ArrayField>
Source: (from Strapi)
{
"id": 5,
"name": "sdf",
"re_benutzers": [{
"id": 1,
"vorname": "Something",
},
^this can hold multiple entries and i want to output the count.
I think you can use a custom component for that instead of the DataGrid.
ArrayField will inject all ids retrieved in a list context.
So you can do something like:
const LengthField = (props) => {
const { ids, loaded } = useListContext(props);
return loaded ? ids.length : null;
};
// And then
<ArrayField source="re_benutzers">
<LengthField />
</ArrayField>
You can do this using the FunctionField component:
https://marmelab.com/react-admin/Fields.html#functionfield:
<FunctionField label="Count" render={record => `${record?.re_benutzers?.length ?? ""}`} />

Display json nested objects in React

I am new to react and trying to display nested json object. I want to iterate "hookah_tobacco" nested object and display tobacco types.
Json part:
{
"id": 1,
"hookah_name": "Smoke city",
"city": 131,
"street": "Osinnya, 33",
"website": "",
"phone": "0672222222",
"description": "Cool bar.",
"credit_card": true,
"hookah_type": [],
"hookah_tobacco": [
{
"hookah_tobacco": "Al-Fakher"
},
{
"hookah_tobacco": "Serbetli"
}
],
"summer_terrace": false,
"hookah_images": [
{
"id": 1,
"hookah_image": "http://127.0.0.1:8000/hookahImages/O4P02PtmT22nv8LwB85KDw-752x440.jpg"
},
{
"id": 2,
"hookah_image": "http://127.0.0.1:8000/hookahImages/kalyannaya-zmist-kiev-vxod-pushkinskoy-1024x768.jpeg"
}
]
}
React part:
class HookahDetail extends Component{
render(){
const obj = this.props.hookahDetail;
return(
// TODO move style to css
<div style = {{ color: "yellow", border: "1px solid yellow" }}>
<h4>{obj.hookah_name}</h4>
<h5>
<p>{obj.city}</p>
{obj.street}
<p>{obj.hookah_style}</p>
<p>{obj.phone}</p>
Tobacco:
<div>
</div>
<p>{obj.description}</p>
<p>{obj.credit_card}</p>
<p>{obj.summer_terrace}</p>
<div>
{/* here is some mistake */}
{obj.hookah_tobacco.map((t) => {
return (
<div>{t.hookah_tobacco}</div>
)
})}
</div>
</h5>
</div>
)
}
}
Error part:
TypeError: obj.hookah_tobacco is undefined
Console log part when {console.log(obj.hookah_tobacco)}():
0: Object { hookah_tobacco: "Al-Fakher" }
​
1: Object { hookah_tobacco: "Serbetli" }
I can't understand why map() function does not work.
Try to use it like this. This way can handle the undefined error
...
{obj.hookah_tobacco ? obj.hookah_tobacco.map((t) => {
return (
<div>{t.hookah_tobacco}</div>
)
}) : null}

Best way to do calculation on a property of a Prop(coming from the parent) object and put the result in the template

I have an object that is being passed to the child component and I want to display those values in the child component template. However, the object contains properties that are arrays and objects so I need to do some parsing/manipulation on them before I set the value.
object:
{
"id": "111",
"ip": "10.192.1.112",
"profiles": [
"Japan",
"Japan222"
],
"network": [
{
"plan_id": "PLAN-UUID",
"plan_name": "1BCT"
},
{
"plan_id": "PLAN-UUID",
"plan_name": "1BCT2"
}
],
"status": "LOCKED",
"last_downloaded": "1547672769000"
}
template code in child:
<label label="id" :value=this.objectFromParent.id />
<label label="ip" :value=this.objectFromParent.ip />
<label label="ip" :value= calculateProfiles />
where calculateProfiles is the method to calculate it and return the string value? I'm not sure what's a good way to handle this.
Try this
<template>
<div>
<label label="id" :value="objectFromParent.id" />
<label label="ip" :value="objectFromParent.ip" />
<label label="ip" :value="calculateProfiles" />
</div>
</template>
<script>
export default {
props: {
objectFromParent: {
type: Object,
default() {
return {
id: '111',
ip: '10.192.1.112',
profiles: ['Japan', 'Japan222'],
network: [
{
plan_id: 'PLAN-UUID',
plan_name: '1BCT'
},
{
plan_id: 'PLAN-UUID',
plan_name: '1BCT2'
}
],
status: 'LOCKED',
last_downloaded: '1547672769000'
}
}
}
},
computed: {
calculateProfiles() {
return this.profiles.join(',')
}
}
}
</script>
To help him on the comment:
<label label="ip">
<md-icon v-if="objectFromParent.status==='LOCKED'">lock</md-icon>
{{calculateProfiles}}
</label>