Custom Export to CSV button inside Tab-element - react-admin

Working with react-admin version 2.9.6. And I want to implement a export to CSV button inside a Tab element. The export should not be all the items for a specific resource but rather a sub field on a specific resource id.
I want to export the "export_data" for a specific post in the example below.
Example resource /posts/1
{
id: 1,
title: "some title"
....
export_data: {
some_data: "ddddd",
...
}
}
The Show-view looks something like this:
export const PostsShow = (props) => (
<Show {...props}>
<TabbedShowLayout>
<Tab label="General">
...
</Tab>
<Tab label="Other">
...
<ExportButton ... />
</Tab>
</TabbedShowLayout>
</Show>
);

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

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

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>

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

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

OnPress change VIEW content

I want to have two tab buttons on top and some content underneath.
After that, the content I need a View like this :
<Form style={styles.form}>
<Label style={styles.label}>
data 1
</Label>
<Item >
<Input/>
</Item>
<Label style={styles.label}>
Data2
</Label>
<Item>
<Input/>
</Item>
</Form>
When I clicking on the first button, it is active. I need that form to appear.
After that, when I clicking on the second button, I need that form change to:
<Form style={styles.form}>
<Label style={styles.label}>
data 3
</Label>
<Item >
<Input />
</Item>
</Form>
What I'm understanding is that I need a state variable.
state = {showFirst : true, showSecond:false }
and have somewhere a conditional:
if showFirst true, display FORM1
if showSecond true, display FORM2
And
onPress {() => {this.setState{{the state = true)}}
But I am not sure how to bind this together as I'm using React Native for the first time.
Currently what I'm using now is it a good practice?
I set separate states variables for both forms, because another button may be added later.
So I can't only one button:
state = { showForm: true}
showForm?Form1:Form2
onPress={() => {this.setState{{showForm:false)}}
How can I get this to work?
This is a minimum example Component for what you said you were trying to achieve:
import React, {Component} from ‘react’;
import {Button, View} from ‘react-native’;
export default class ExampleComponent extends Component {
constructor(props) {
super(props);
this.state = {
showForm: 0
};
}
render() {
var form;
if (this.state.showForm === 0) {
form = (
<View> INSERT_FORM1 </View>
);
} else if (this.state.showForm === 1) {
form = (
<View> INSERT_FORM2 </View>
);
}
return (
<View>
<Button title=‘Show Form 1’ onPress={() => this.setState({showForm: 0})}/>
<Button title=‘Show Form 2’ onPress{() => this.setState({showForm: 1})}/>
{form}
</View>
);
}
}
You can dynamically choose what content to show based on the Component props and state.
In the example above I used a numerical value to determine what form to show to minimize the amount of state values you would have to track later if the form count expanded.
A switch statement would be a better choice in the event of more available form choices, but I used if-else here for easy of typing for now.