Fetch data only the first time go to a Tab in a TabbedForm - react-admin

I have a tabbed form that the second tab will retrieve a list from the backend, similar to the react-admin official demo (e.g. post has many comments). The problem is when I switch the tab, there is always a backend call for the second tab which is comment list.
How can I avoid it and just load one time?
Because I have pagination on the second tab, if I switch tab, the pagination will be changed to the first page.
Thank you in advance!
<TabbedForm>
<FormTab label="Post">
<TextInput source="name"/>
</FormTab>
<FormTab label="Comment">
{/* This tab should fetch data only once if I switch tabs */}
<ReferenceManyField
pagination={<Pagination/>}
reference="comments"
target="id"
perPage={5}
addLabel={false}
>
<Datagrid>
<TextField source="name" />
<EditButton />
<DeleteButton undoable={false}/>
</Datagrid>
</ReferenceManyField>
</FormTab>
</TabbedForm>

If you are using Hooks, you could use a combination of useState and useEffect.
useState would hold the result of your data-fetching, which you would trigger in useEffect. To make that happen just once, use an empty array as dependency-array in useEffect:
const [data, setData] = useState(null)
useEffect(() => {
setData(fetchData())
}, [])

Related

Achieve real-time access to the value being entered in the dialog and modify it?

Purpose:
After the keyboard pops up, a shortcut input box with the specified text appears above it. After clicking, the corresponding text such as"https://" or "www." will be appended to the dialog box.
Problem:
I can't get the value in the input dialog in real time and modify it by functions other than keyboard input.
effort made:
The dialog I'm using is referenced from react-native-dialogs。I can't find a way to pass state into the input area of ​​this dialog.
Also didn't find a way to get the value of the live input.
I thought if there are other dialog components that support getting the value of the modified input, but I didn't find one.
Notice that the Dialog.Input of react-native-dialog is just a TextInput. Thus, we can achieve this as usual using the onChangeText callback and the value prop of TextInput.
Here is a minimal example.
const [visible, setVisible] = useState(false);
const [value, setValue] = useState("");
return (
<View style={styles.container}>
<Button title="Show dialog" onPress={() => setVisible(true)} />
<Dialog.Container visible={visible}>
<Dialog.Title>Please enter the url you want to bookmark</Dialog.Title>
<Dialog.Description>
Message - optional
</Dialog.Description>
<Dialog.Input onChangeText={setValue} value={value} />
<Dialog.Button label="Ok" onPress={() => setVisible(false)} />
<Dialog.Button label="Set Text via function" onPress={() => setValue("Hello World")} />
</Dialog.Container>
</View>
);
The state value will be updated while you are typing. You have access to it while the dialog is open. You can set the value manually by using the setValue function of the value state as usual. This can be done while the dialog is open.
Here is a little snack for showcasing.

TabbedForm not highlighting which tab has an error

I have a TabbedForm with 2 tabs, each tab having a single required() field. When I submit this form and the validation fails, I expect the unfocussed tab(s) to indicate that there is an error with a field within the tab (e.g. with a red underline or red text).
This appears to be working fine in a react-admin demo (https://marmelab.com/react-admin-demo/#/products/126) however even after looking a the source code for this example (https://github.com/marmelab/react-admin/blob/master/examples/demo/src/products/ProductEdit.tsx), I cannot seem to replicate the same functionality in my project.
I have the following code:
const App = () => {
const dataProvider = jsonServerProvider(
"https://jsonplaceholder.typicode.com"
);
return (
<Admin dataProvider={dataProvider}>
<Resource name="users" list={ListGuesser} edit={EditForm} />
</Admin>
);
};
export const EditForm = (props: EditProps) => {
return (
<Edit {...props}>
<TabbedForm>
<FormTab label="Tab 1">
<TextInput source="name" validate={required()} />
</FormTab>
<FormTab label="Tab 2">
<TextInput source="username" validate={required()} />
</FormTab>
</TabbedForm>
</Edit>
);
};
Image showing Tab 2 selected and is valid and there is a validation error on Tab 1, but no highlight on Tab 1 to tell the user that this is the Tab that has the error.
There has been a similar question asked here (Show Tab Form Validation For Inputs Not Direct Children Of <FormTab>) but the resolution does not apply to my problem.
Is there something I'm missing here?
plz check the demo source code: https://github.com/marmelab/react-admin/blob/master/examples/demo/src/products/ProductEdit.tsx, it's using validate function:
<RichTextInput source="description" label="" validate={req} />
and the "req" is defined at line 86:
const req = [required()];
I've encountered same problem, and solve it by using the way (validation function) of demo source code. HTH

React-Admin <SimpleForm> component doesn't trigger the "UPDATE" action in the data provider

So I'm using the ra-data-json-server as a data provider and the bult in <SimpleForm> component as a form for the <Edit> view, and I'm facing a pretty strange issue, as it says in documentation, when submitted, the <SimpleForm> forces data provider to make a PUT request to the API, but in my case it does not.
Here's my Edit view compnent:
export const UserEdit = props => {
return (
<Edit {...props}>
<SimpleForm>
<ArrayInput source="applications">
<SimpleFormIterator source="applications">
{/* some inputs */}
</SimpleFormIterator>
</ArrayInput>
</SimpleForm>
</Edit>
);
};
And the admin component itself:
export const AdminComp = () => {
return (
<Admin
loginPage={CustomLoginPage}
authProvider={authProvider}
locale="ru"
i18nProvider={i18nProvider}
dataProvider={dataProvider}
>
<Resource
name="students"
list={UserList}
edit={UserEdit}
/>
</Admin>
);
};
And everytime I get into the Edit view and hit the save button it just doesn't do anything.
I managed to make it at least call the update in data provider, but it would call it with the old form data even though it was modified already.
I also tried reinstalling react-admin to the latest version which is what some people reccomended but it didn't help.

How do I implement Algolia Search filtering in a React Native Modal component?

I am using Algolia search in my React Native app, following the instructions in guides/building-search-ui/going-further/native/react
I have created a RefinementList as per the instructions, and this works fine when placed directly with the app, as per
<InstantSearch
searchClient={searchClient}
indexName={THE_INDEX}
root={root}
>
<RefinementList attribute="brand.name" />
<SearchBox />
<InfiniteHits />
</InstantSearch>
When this runs and I enter a search term I happily get a list of my faceted brand names. But I need this to work within a modal. So following Algolia's instructions I have created a Filters component and use my RefinementList from within that instead:
<InstantSearch
searchClient={searchClient}
indexName={THE_INDEX}
root={root}
searchState={this.state.searchState}
onSearchStateChange={this.onSearchStateChange}
>
<Filters
isModalOpen={this.state.isModalOpen}
toggleModal={this.toggleModal}
indexName={THE_INDEX}
searchClient={searchClient}
searchState={this.state.searchState}
onSearchStateChange={this.onSearchStateChange}
/>
<SearchBox />
<FilterButton
onFilter={this.toggleModal}
/>
<InfiniteHits />
</InstantSearch>
However the modal is always empty. The console shows that when the RefinementList is simply a child of the InstantSearch then its items array gets populated, but when the RefinementList is invoked within the Filters component then it's empty. The Filters component is exactly as per Algolia's docs, apart from the fact I pass in the indexName as a prop. I've checked in the React Native debugger and the RefinementList has access to the same context, the searchState is being set, and onSearchStateChange is being invoked correctly. The indexName is correct.
What am I doing wrong?
To fix this I had to add a 'virtual' SearchBox, ala
const VirtualSearchBox = connectSearchBox(() => null)
which is used as a child of the InstantSearch component in the Filters component, making Filters more like
const Filters = ({
isModalOpen,
searchState,
searchClient,
toggleModal,
onSearchStateChange,
indexName
}) => (
<Modal animationType="slide" visible={isModalOpen}>
<SafeAreaView>
<InstantSearch
searchClient={searchClient}
indexName={indexName}
searchState={searchState}
onSearchStateChange={onSearchStateChange}
>
<VirtualSearchBox />
<RefinementList attribute="brand" />
<TouchableOpacity style={styles.closeButton} onPress={toggleModal}>
<Text style={styles.closeButtonText}>Close</Text>
</TouchableOpacity>
</InstantSearch>
</SafeAreaView>
</Modal>
)
Inside the modal the searchState is always falsy because the SearchBox is not mounted.
Each time a request happens inside the modal the emtpy state will be provided.
The refinements inside the searchState are not enough to apply a refinement on the search parameters. All of the widgets associated with the refinement have to be mounted (ref: Search State docs).

React-Admin | Bulk-Select with ReferenceField

In our application, we're trying to use Datagrid within ReferenceField to create/modify/delete related records, as shown in https://marmelab.com/blog/2018/07/09/react-admin-tutorials-form-for-related-records.html
All the functionality shown in the tutorial works well, except the bulk-actions added in a recent react-admin update. Clicking these checkboxes gives
Uncaught TypeError: _this.props.onToggleItem is not a function
I believe this is because the onToggleItem prop is normally provided by the List component, however in this application, Datagrid doesn't have a parent List component.
Adding a List component between ReferenceManyField and Datagrid allows bulk select/delete to work after some changes to the style, however this causes another issue: the current displayed page (i.e. records 1-10, 11-20, etc) is stored per-resource in the store, and so it is possible to have a situation where the store says we're on page 2, and displays page 2, which is empty because there are only enough related items to fill one page.
Am I missing something here? Or is bulk-select inside ReferenceManyField not possible at the moment?
export const NetworksShow = (props) => (
<Show title={<NetworksTitle />} actions={<NetworksShowActions />} {...props}>
<ReferenceManyField addLabel={false} target="ipid" reference="acl-network">
<List style={{ margin: '0px -24px -16px -24px' }} {...props} actions={<NetworkACLCardActions ipid={props.id}/>} filter={{ ipid: _.has(props, 'id') ? props.id : undefined }}>
<Datagrid hasBulkActions={true}>
<ReferenceField label="Network" source="ipid" reference="networks">
<TextField source="name" />
</ReferenceField>
<TextField label="URL" source="url" />
<BWChip label="Action" source="wb" />
<EditButton />
<DeleteButton />
</Datagrid>
</List>
</ReferenceManyField>
</Show>
);
As a side-effect of https://github.com/marmelab/react-admin/pull/2365, it is now possible to use ReferenceManyField -> List -> Datagrid in the way described in the question.
For example, we're now doing the following:
<ReferenceManyField addLabel={false} target="groupid" reference="users">
<List
style={{ margin: '0px -24px -16px -24px' }}
filter={{ groupid: id }}
{...props}
>
<Datagrid hasBulkActions>
<LinkField label="Name" source="name" />
<LinkField label="Username" source="email" />
<FlexibleBooleanField label="Email" source="allowemail" />
<ACLChip label="User Access" source="aclid" />
</Datagrid>
</List>
</ReferenceManyField>
Bulk actions works with the above, and any issues with pagination are avoided as react-admin now checks and modifies pagination if nothing appears on the current page.
As I've understood from the documentation, Datagrid is just an iterator "dumb component".
It just "shows" things that the parent - usually List (connected component) or in your case ReferenceManyField - element previously has fetched.
Thus I think that BulkActions can only be functional when provided by a List element.
For the second part of your issue, Lists should be used top-level and not within other elements that's why it breaks your pagination.
I implemented "DumbList" which takes data from parent component instead of loading it itself. This solves the problem:
import React from 'react';
import { ListController } from 'ra-core';
import { ListView } from 'ra-ui-materialui/esm/list/List';
export const DumbList = props =>
<ListController {...props}>
{controllerProps => {
let { data } = props
const ids = Object.keys(data || {})
const total = ids.length
return <ListView
{...props}
// This is important, otherwise bulkActionsToolbar ends up on very top of the page
classes={{ card: 'relative' }}
{...Object.assign(controllerProps, { data, ids, total })} />
}}
</ListController>