react-admin TypeError: Cannot read property 'save' of undefined - typeerror

I'm trying to show a list of users with react-admin.
CustomerList.tsx
import React from 'react';
import {
TextField,
Datagrid,
DateInput,
DateField,
Filter,
List,
EmailField,
SearchInput,
SimpleForm,
} from 'react-admin';
import { useMediaQuery, Theme } from '#material-ui/core';
import MobileGrid from './MobileGrid';
const CustomerFilter = (props: any) => (
<Filter {...props}>
<SearchInput source='q' alwaysOn />
<DateInput source='created_at' />
</Filter>
);
const CustomerList = (props: any) => {
const isXsmall = useMediaQuery<Theme>((theme) =>
theme.breakpoints.down('xs')
);
return (
<List
{...props}
filters={<CustomerFilter />}
sort={{ field: 'created_at', order: 'desc' }}
perPage={100}
>
{isXsmall ? (
<MobileGrid />
) : (
<SimpleForm>
<Datagrid optimized rowClick='edit'>
<TextField source='id' />
<DateField source='created_at' showTime />
<DateField source='updated_at' showTime />
<EmailField source='email' />
<TextField source='status' />
</Datagrid>
</SimpleForm>
)}
</List>
);
};
export default CustomerList;
But i have an error TypeError: Cannot read property 'save' of undefined.
Something Went Wrong
A client error occurred and your request couldn't be completed.
Détails
TypeError: Cannot read property 'save' of undefined
at SaveButton (http://localhost:3000/static/js/0.chunk.js:134106:25) at div at div at Toolbar (http://localhost:3000/static/js/0.chunk.js:31314:23) at WithStyles(ForwardRef(Toolbar)) (http://localhost:3000/static/js/0.chunk.js:41921:31) at Toolbar (http://localhost:3000/static/js/0.chunk.js:140673:38) at WithWidth(Toolbar) (http://localhost:3000/static/js/0.chunk.js:37871:90) at form at SimpleFormView (http://localhost:3000/static/js/0.chunk.js:139725:21) at FormView (http://localhost:3000/static/js/0.chunk.js:108800:19) at ReactFinalForm (http://localhost:3000/static/js/0.chunk.js:189371:20) at FormWithRedirect (http://localhost:3000/static/js/0.chunk.js:108697:18) at SimpleForm at div at Paper (http://localhost:3000/static/js/0.chunk.js:20241:23) at WithStyles(ForwardRef(Paper)) (http://localhost:3000/static/js/0.chunk.js:41921:31) at Card (http://localhost:3000/static/js/0.chunk.js:5097:23) at WithStyles(ForwardRef(Card)) (http://localhost:3000/static/js/0.chunk.js:41921:31) at div at div at ListView (http://localhost:3000/static/js/0.chunk.js:150222:23) at ListContextProvider (http://localhost:3000/static/js/0.chunk.js:96869:18) at List (http://localhost:3000/static/js/0.chunk.js:149806:79) at CustomerList (http://localhost:3000/static/js/main.chunk.js:4177:91) at WithPermissions (http://localhost:3000/static/js/0.chunk.js:95243:23) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at ResourceContextProvider (http://localhost:3000/static/js/0.chunk.js:103522:21) at ResourceRoutes (http://localhost:3000/static/js/0.chunk.js:103390:17) at Resource (http://localhost:3000/static/js/0.chunk.js:103460:15) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at RoutesWithLayout (http://localhost:3000/static/js/0.chunk.js:103566:21) at div at main at div at div at LayoutWithoutTheme (http://localhost:3000/static/js/0.chunk.js:147666:24) at WithStyles(LayoutWithoutTheme) (http://localhost:3000/static/js/0.chunk.js:41921:31) at withRouter(WithStyles(LayoutWithoutTheme)) (http://localhost:3000/static/js/0.chunk.js:193873:37) at Connect(withRouter(WithStyles(LayoutWithoutTheme))) (http://localhost:3000/static/js/0.chunk.js:190558:75) at ThemeProvider (http://localhost:3000/static/js/0.chunk.js:40623:24) at Layout (http://localhost:3000/static/js/0.chunk.js:147783:26) at MyLayout (http://localhost:3000/static/js/main.chunk.js:8952:80) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at div at CoreAdminRouter (http://localhost:3000/static/js/0.chunk.js:103033:87) at Route (http://localhost:3000/static/js/0.chunk.js:193616:29) at Switch (http://localhost:3000/static/js/0.chunk.js:193818:29) at CoreAdminUI (http://localhost:3000/static/js/0.chunk.js:103242:15) at AdminUI at Router (http://localhost:3000/static/js/0.chunk.js:193251:30) at ConnectedRouter (http://localhost:3000/static/js/0.chunk.js:49787:7) at ConnectedRouterWithContext (http://localhost:3000/static/js/0.chunk.js:49892:25) at Connect(ConnectedRouterWithContext) (http://localhost:3000/static/js/0.chunk.js:190558:75) at TranslationProvider (http://localhost:3000/static/js/0.chunk.js:110397:28) at Provider (http://localhost:3000/static/js/0.chunk.js:190271:20) at CoreAdminContext (http://localhost:3000/static/js/0.chunk.js:102802:25) at AdminContext at Admin (http://localhost:3000/static/js/0.chunk.js:154193:22) at App
This is the lastest version of react-admin and all dependencies were updated.
What's wrong with the save button?
Thanks & Regards

Related

react-admin label prop does not work when creating custom fields

I have created a simple wrapper component for ReferenceField in react-admin app:
import {FC} from "react";
import {ReferenceField, ReferenceFieldProps} from "react-admin";
export const UserReference: FC<Omit<ReferenceFieldProps, "reference" | "label">> = (props) => {
return <ReferenceField {...props} label="User" reference="users" />;
};
UserReference.displayName = "UserReference";
But when using the field the label is still inferred from source property (which is the default).
When I directly use ReferenceField with label it works:
// UserReference renders label "User id" and ReferenceField renders correct "User"
<Datagrid>
<UserReference source="user.id" />
<ReferenceField reference="users" label="User" source="user.id" />
</Datagrid>
Why does this happen and how can I fix it?
Do I have to forward refs?
<Datagrid> inspects its children label prop to build the header row. So to make it work in your case, you have to define the label in the defaultProps:
import {FC} from "react";
import {ReferenceField, ReferenceFieldProps} from "react-admin";
export const UserReference: FC<Omit<ReferenceFieldProps, "reference" | "label">> = (props) => {
return <ReferenceField {...props} reference="users" />;
};
UserReference.displayName = "UserReference";
UserReference.defaultProps = { label: 'User' };
this is documented in the react-admin documentation: https://marmelab.com/react-admin/Fields.html#writing-your-own-field-component

How can I close a Popover programatically with native base?

I am using Native Base Popover.
scenario
As a user,
I can press on the list,
So that I can select a fruit
My problem is I don't understand how to close the <Popover /> from the outside of the component.
Here is my organization
<Formik>
<Popover>
<FlatList>
<Pressable onPress={() => handlePress(item.id)} /> //Banaba
<Pressable onPress={() => handlePress(item.id)} /> //Potato
<Pressable onPress={() => handlePress(item.id)} /> //Ananas
NativeBase offers a useDisclose() hook for handling opening/closing of modals and other popup windows.
That hook provides an isOpen state (as #mainak's answer mentions) as well as onOpen() and onClose() functions to manipulate that state. You can pass these helpers as arguments as-needed into the props of the same name within the Popover component to handle open/close state.
Optionally, you can in addition pass true or false into useDisclose() to override the starting value of isOpen (defaults to false).
Here is an example below for reference.
import React from "react";
import { Popover, useDisclose } from "native-base";
function MyComponent() {
const { isOpen, onClose, onOpen } = useDisclose()
return (
<>
<Button onPress={onOpen}>Open the Popover</Button>
<Popover isOpen={isOpen} onClose={onClose}>
<Popover.Content>
<Popover.Arrow />
<Popover.CloseButton />
<Popover.Header>My Popover Title</Popover.Header>
<Popover.Body>You can place the content of your popover inside the body.</Popover.Body>
<Popover.Footer>
<Button onPress={onClose} variant="ghost">Cancel</Button>
</Popover.Footer>
</Popover.Content>
</Popover>
</>
)
}
can you try isOpen prop in Popover tag and have it as a state value like
const [isOpen, setOpen] = React.useState(true);
...
<Formik>
<Popover isOpen={isOpen}>
<FlatList>
...

How to create a custom record action button inside a List component with React-Admin?

I'm a totally newbie with React and React-Admin. IMHO, I'm trying to achieve something simple that many people must have already done but I cannot find any kind of tutorial anywhere.
I'd like to add another button to the list of action buttons (show/edit) within each row in a <List> component. This button would archive the record.
My last try looks like the code below.
import React from 'react';
import {
Datagrid,
EmailField,
List,
TextField,
ShowButton,
EditButton,
DeleteButton,
CloneButton,
} from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
import ArchiveIcon from '#material-ui/icons/Archive';
const useRowActionToolbarStyles = makeStyles({
toolbar: {
alignItems: 'center',
float: 'right',
width: '160px',
marginTop: -1,
marginBottom: -1,
},
icon_action_button: {
minWidth: '40px;'
},
});
const ArchiveButton = props => {
const transform = data => ({
...data,
archived: true
});
return <CloneButton {...props} transform={transform} />;
}
const RowActionToolbar = (props) => {
const classes = useRowActionToolbarStyles();
return (
<div className={classes.toolbar}>
<ShowButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<EditButton label="" basePath={props.basePath} record={props.record} className={classes.icon_action_button}/>
<ArchiveButton {...props} basePath={props.basePath} label="" icon={<ArchiveIcon/>} record={props.record} className={classes.icon_action_button} />
<DeleteButton basePath={props.basePath} label="" record={props.record} className={classes.icon_action_button}/>
</div>
);
};
export const UserList = props => {
return (
<List
{...props}
sort={{ field: 'first_name', order: 'ASC' }}
>
<Datagrid>
<TextField source="first_name"/>
<TextField source="last_name"/>
<EmailField source="email"/>
<RowActionToolbar/>
</Datagrid>
</List>
)
};
Obviously, this code does not work because the <CloneButton> component get rid of the id the record. Moreover, except if I did something wrong - which is totally possible -, it makes a GET request to a create endpoint.
I'm using different routes in my dataProvider (The back end is using Django and Django rest framework). I want to send a PATCH to the detail endpoint, like the <Edit> component does.
I also tried with a <SaveButton>, but it fails too.
Uncaught TypeError: Cannot read property 'save' of undefined
at useSaveContext (SaveContext.js:23)
I guess the <SaveButton> must be within a <SimpleForm>?
I'd like the save behaviour of the <DeleteButton>, i.e. update the record from the list, display the notification that the record has been archived (with the Undo link), send the request to the back end, refresh the list.
Any guidance, directions would be very appreciated.
I don't know that this is a full answer, but felt like more than a comment...
You are trying to archive the existing record, not create a whole new record, right? CloneButton is supposed to be used to create a new record with a new ID (which is why your ID is going away), so you don't want to us it here. note that I've never used CloneButton. it is not fully documented so I could be wrong about its use.
I am thinking that you should use the useRecordContext hook within your Archive button to pull in all of the record's data, including the id; read this little section: https://marmelab.com/react-admin/Architecture.html#context-pull-dont-push
And I don't think transform is what you're looking for here. You will need to use one of the dataProvider hooks, i'm assuming useUpdate: https://marmelab.com/react-admin/Actions.html#useupdate
//first create component
const MyButton = (props: any) => {
const [sendEmailLoading, setSendEmailLoading] =
React.useState<boolean>(false);
const record = useRecordContext(props);
const sendEmail = (id: Identifier) => {
setSendEmailLoading(true)
dataProvider.sendEmail(
"notifications", { id: id })
.then(({ data }: any) => {
if (data && data.status == "success")
notify('Email send success', { type: 'success' });
setSendEmailLoading(false);
refresh();
});
};
return (
<ButtonMUI color='primary' size="small" onClick={() => {
sendEmail(record.id) }}>
{
!record.publish &&(
!sendEmailLoading ? (
translate('resources.notifications.buttons.send')
) : (
<CircularProgress size={25} thickness={2} />
)
)
}
</ButtonMUI>
)
}
//and second add to datagrid list
<Datagrid>
<NumberField source="id" />
<TextFieldRA source="subject" />
<DateField source="date" />
<BooleanField source="publish" />
{/* <EditButton /> */}
<ShowButton />
<MyButton />
</Datagrid>

Adding custom success and error messages for the create and edit views

So in the docs it says just to add a prop on the edit component with an onSuccess / onFailure function and I've done that but navigating to the page to edit throws an error like this:
Warning: Unknown event handler property `onSuccess`. It will be ignored.
code here:
export const AffiliateEdit = (props) => {
const notify = useNotify();
const onSuccess = () => {
notify('Affiliate saved successfully');
}
return (
<Edit {...props} onSuccess={onSuccess}>
<SimpleForm redirect="list">
<ColorInput source="color" />
<TextInput source="name" validate={[required()]} />
<ReferenceInput
source="network_id"
reference="networks"
validate={[required()]}
>
<SelectInput optionText="name" />
</ReferenceInput>
<TextInput
source="reference"
validate={[required()]}
helperText="Please use all lower case, no spaces or underscores e.g affiliatename"
/>
</SimpleForm>
</Edit>
)
};
doc ref here: https://marmelab.com/react-admin/CreateEdit.html
Nvm figured it out, just needed to update react-admin lol

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>