In react-admin documentation the use of ReferenceArrayInput is for this kind of data structure:
{
id: 1,
groups: [1, 2, 3]
}
And then:
<ReferenceArrayInput source="groups" reference="groups" allowEmpty>
<SelectArrayInput optionText="name"/>
</ReferenceArrayInput>
Using a custom json data provider, it will be make this request:
https://example.com/api/groups/?ids=[1,2,3]
or if the API doesn't support WHERE IN, it will be do individual calls for each id:
https://example.com/api/groups/1
https://example.com/api/groups/2
https://example.com/api/groups/3
But if I have the following data structure:
{
id: 1,
name: "Pepito Perez",
groups: [
{ id: 1, name: "HR"},
{ id: 2, name: "IT"},
{ id: 3, name: "FINANCE"}
]
}
I have the name field already, so I don't want make additional requests.
When I go to the edit view react-admin performs almost 70 requests unnecessary.
How can I avoid that? there's a way to reuse the data?
Also is tricky use ReferenceArrayInput component with an array of objects, I have to add a nonsense format prop to make it works: format={v => (v ? v.map(i => (i.id ? i.id : i)) : [])}
Guess it's related to the first problem.
Thanks in advance!
If the choices is not meant to be fetched, ReferenceInput is not what you want. You need only a SelectInput with programmatic setted choices. You can achieve that with FormDataConsumer:
<FormDataConsumer>
{({ formData, ...rest }) =>
<SelectArrayInput
source="selectedGroups"
optionText="name"
choices={formData.groups}
{...rest}
/>
}
</FormDataConsumer>
Note a different source, probably setting as groups, equal to choices "source", after first selected group, would result in a re-render, letting choices values equal to the single selected group.
That's almost exactly the use case of FormDataConsumer in documentation:
https://marmelab.com/react-admin/Inputs.html#linking-two-inputs
Related
I am loading form controls dynamically by making 2 api calls. First api to get form control's list and second api to get data for the controls loaded previously.
First half works fine and i am able to load controls dynamically,
schema:
[{ id: 1, input: 'TextBox'},
{ id: 2, input: 'TextArea'}]
code:
fields.map((type: any, i: any) => {
switch (type.input) {
case 'TextBox':
return (<input type="text" id={type.id}> />)
case 'TextArea':
return (<textarea itemType="text" id={type.id}> />)}});
Above code works fine and I am able to create form controls.
Next part is binding value to the dynamic controls, I make another API call to get data and I should map id field and bind data
schema:
[{ id: 1, value: 'testTextBox'},
{ id: 2, value: 'testTextArea'}]
How can I bind data to the controls now?
I have tried using state, but not able to achieve that.
or i can update first schema and add value key to it after i get second api response
something like below,
fields = [{ id: 1, input: 'TextBox', value: 'testTextBox'},
{ id: 2, input: 'TextArea', value: 'testTextArea'}]
Please suggest how to loop and add value key to fields array?
use:
setState({})
create the controls binded to state as:
(<input type="text" id={type.id}> value={this.state.id}/>)
upon receiving the values set it in the state as:
this.setState({id: value});
I have a list of Items of whatever type. I can query all of them with query items or one with query item(id).
I realize apollo can't know what will be returned. It knows the type, but it doesn't know the exact data. Maybe there is a way not to make additional request? Map one query onto another?
Pseudo-code:
// somewhere in Menu.tsx (renders first)
let items = useQuery(GET_ITEMS);
return items.map(item => <MenuItemRepresenation item={item} />);
// meanwhile in apollo cache (de-normalized for readability):
{ ROOT_QUERY: {
items: [ // query name per schema
{ id: 1, data: {...}, __typename: "Item" },
{ id: 2, data: {...}, __typename: "Item" },
{ id: 3, data: {...}, __typename: "Item" },
]
}
}
// somewhere in MainView.tsx (renders afterwards)
let neededId = getNeededId(); // 2
let item = useQuery(GET_ITEM, { variables: { id: neededId } } );
return <MainViewRepresentation item={item} />;
Code like this will do two fetches. Even though the data is already in the cache. But it seems apollo thinks on query level. I would like a way to explain to it: "If I make item query, you need to look over here at items query you did before. If it has no item with that id go ahead and make the request."
Something akin to this can be done by querying items in MainView.tsx and combing through the results. It might work for pseudo-code, but in a real app it's not that simple: cache might be empty in some cases. Or not sufficient to satisfy required fields. Which means we have to load all items when we need just one.
Upon further research Apollo Link looks promising. It might be possible to intercept outgoing queries. Will investigate tomorrow.
Never mind apollo link. What I was looking for is called cacheRedirects.
It's an option for ApolloClient or Cache constructor.
cacheRedirects: {
Query: {
node: (_, args, { getCacheKey }) => {
const cacheKey = getCacheKey({
__typename: "Item",
id: args.id,
});
return cacheKey;
},
},
},
I'd link to documentation but it's never stable. I've seen too many dead links from questions such as this.
I have a status field, coming from my API. It has a value between 0-3.
my response looks like this:
{ status: 0 }
I can show the value in Edit with TextInput, it shows the value (in this case 0).
However I want it to show it with SelectInput, as in Edit mode I want to change the value of the status.
my SelectInput looks like this:
<SelectInput label="Status" source="status" choices={[
{ id: '0', name: 'elfogadásra vár' },
{ id: '1', name: 'aktív' },
{ id: '2', name: 'inaktív' },
{ id: '3', name: 'archív' },
]}
optionText="name"
optionValue="id"
/>
Unfortunately when I save this, and refresh my page, my Status does not show the current value (which is 0 in this case, it should show me 'elfogadasra var', but it's empty)
What do I do wrong?
The way i achieved this was in the following manner
<ReferenceInput label="Country" source='country.id' reference="Country" sort={{ field: 'name', order: 'ASC' }} alwaysOn>
<SelectInput optionText="name" optionValue="id" allowEmpty />
</ReferenceInput>
I needed to load my choices from that database though however the concept is the same... i suspect that what you are using for source is wrong.. Look in the redux dev tools.. under state --> form --> record-form --> values and you should see something like status.id that you should be using for source instead.. i have the same type of object in my form country {id: 2}... but that is not what you use to get the input to show your existing value...
Your solution may be as simple as calling a diff value in source, but you'll know what that should be by peeking into the state {status: 0} must have something in front of it in state like something --> {status: 0}
Not the real reason but in case somebody is tackling with the same issue it is worth checking:
I found out I was passing initialValues to the Form component on create and haven't skipped them on the edit mode. That's why it was always resetting the SelectInput's.
I am using ng-admin to write a admin management. I met below issue, can somebody help me?
In my creationView, I would like to show different fields(text/video/pictures) according to the selection of "type" field. How I can make it?
var articles = nga.entity('articles');
articles.creationView().fields([
nga.field('type','choice')
.validation({ required: true })
.choices([
// 1: txt, 2: pic, 3: vid
{ value: 1, label: 'Text'},
{ value: 2, label: 'Picture'},
{ value: 3, label: 'Video'},
]),
nga.field('txt','file')
.attributes({ placeholder: 'Write something... !' }),
nga.field('pic','file')
.label('Picture(.jpg|.png)')
.uploadInformation({ 'url': '/api/adminapi/uploadPicture', 'apifilename': 'pictures', 'accept': 'image/jpg,image/png'}),
nga.field('vid','file')
.label('Video(.mp4)')
.uploadInformation({ 'url': '/api/adminapi/uploadVideo', 'apifilename': 'video', 'accept': 'video/mp4'}),
])
The Field Configuration doc page explains how to do this using the "template" field config:
template(String|Function, templateIncludesLabel=false) All field types
support the template() method, which makes it easy to customize the
look and feel of a particular field, without sacrificing the native
features.
...
To force the template to replace the
entire line (including the label), pass true as second argument to the
template() call. This can be very useful to conditionally hide a field
according to a property of the entry:
post.editionView()
.fields([
nga.field('category', 'choice')
.choices([
{ label: 'Tech', value: 'tech' },
{ label: 'Lifestyle', value: 'lifestyle' }
]),
nga.field('subcategory', 'choice')
.choices(function(entry) {
return subCategories.filter(function (c) {
return c.category === entry.values.category;
});
})
// display subcategory only if there is a category
.template('<ma-field ng-if="entry.values.category" field="::field" value="entry.values[field.name()]" entry="entry"
entity="::entity" form="formController.form"
datastore="::formController.dataStore"></ma-field>', true),
]);
I just have a work round method. That is using .attributes(onclick, "return updateButton();") for nga.field("type"). In updateButton() method, it will help to check current 'type' field value and using DOM methods to change the button enable and disabled.
However, I still realy appreciate that if this requirement can be support in future. It will be helpful for user to make UI controls easily.
I'm new to ReactJS and am unsure about the best place to put validation logic that is needed both by nested child components in my form, and the overall "parent" form component itself. Here is a over-simplified example that illustrates my question...
I have a object like this that represents a pet owner:
{
name: 'Jon Arbuckle',
pets: [
{ name: 'Odie', type: 'dog' },
{ name: 'Garfield', type: 'cat' }
]
}
I'm using a composite component called <PetOwnerForm> to render a form for editing this data. <PetOwnerForm> renders something like this:
<input type="text" value={name} />
<PetList value={petOwner.pets} />
<PetList> is a composite component that renders this:
<PetListItem value={this.props.value[i]} /> // Render this for each pet...
// buttons for adding/deleting pets
<PetListItem> renders something like this:
<input type="text" value={this.props.value.name} />
<PetTypePicker value={this.props.value.type} />
Lastly, <PetTypePicker> renders a <select> with <option>s for pet types.
<PetTypePicker> needs to know how to validate the selected type so it can display an inline error message (e.g., ensure that a value is selected).
However, <PetOwnerForm> also needs to know how to validate the pet type because it needs to know how to validate the entire object (on load, each time the form is updated, and before submitting the data back to the server). If any field is invalid, the "Save" button should be disabled.
So where, for example, should the "is a valid pet type selected?" logic go? (Bear in mind that this is a trivial example; in reality I have many fields like this and nested composite components).
The options I see so far are:
A) Replicate the validation logic for pet type (or whatever field) both in <PetOwnerForm> and <PetTypePicker>. This might just be a matter of calling the same, shared validation function in both places:
//PetOwnerForm.js:
validate(petOwnerObj) {
Util.isPetTypeValid(petOwnerObj.pets[i]) // for each pet
// validate the other properties in petOwnerObj...
}
//PetTypePicker.js:
validate(petType) {
Util.isPetTypeValid(petType)
}
B) Use custom PetOwner, Pet, and PetType models that have their own validators. This way you can always ask a model to validate itself, regardless of where it is. Maybe this would look something like this:
{
name: { value: 'Jon Arbuckle', isValid: ()=>{...} },
pets: [
{
name: { value: 'Garfield', isValid: ()=>{...} },
type: { value: 'cat', isValid: ()=>{...} }
},
...
]
}
C) Modify PetOwnerForm.js go recurse the pet owner object, validating each value, and setting an 'errors' property that child components can reference, resulting in an object like this:
{
name: { value: 'Jon Arbuckle asdfasdfasdf^^', errors: ['Too many characters', 'Contains invalid character']] },
pets: [
{
name: { value: '', errors: ['Required value missing'] },
type: { value: 'tree', errors: ['Invalid pet type'] }
},
...
]
}
Which option is recommended for React apps (or is there another option)?
It's a nice elaborate question. This question is not specific to ReactJS applications. It applies to all frameworks that follow component model.
Following are my recommendations:
Differentiate between action driven validation and data format validation.
Low level components are aware of data format they accept, so they must validate for it. For example, postal-code, email, phone, SSN etc have fixed formats and their corresponding components must validate for the right input format.
Low level components are not aware of actions being performed on the overall data. For example, selection of pet-owner-type can be mandatory for "create" pet-owner action but can be optional for "save draft" action. So, low level components which are not aware of end action must not perform action driven validations.
Action driven validation must be performed by the higher level component aware of action, for example PetOwnerForm. Such validation result must be notified to low level components so that they can display appropriate errors. Every low level component must have an error state to support it.