React admin import button referenceManyField? - react-admin

I want add an import button for my react admin application.
This import will create records related to another record. For instance, I have a list of contracts that belong to a company.
I saw this repo: https://github.com/benwinding/react-admin-import-csv
And I was wondering if there was something similar for a referenceManyField.

I found a hacky solution for this in case anyone is interested.
Instead of using a ReferenceManyField I used a List inside a Show component.
To be able to do this I had to override the props of the list to specify the resource that I wanted to reference.
I also had to pass the show item id as a prop of the list component and filter the list by the id.
let companyId = '';
const ListActions = (props) => {
const { className } = props;
return (
<TopToolbar className={className}>
<ImportButton {...props} />
</TopToolbar>
);
};
export const ContractRow = ({ contractCompanyId }) => {
companyId = contractCompanyId;
const fakeProps: ListProps = {
basePath: '/contracts',
hasCreate: false,
hasEdit: false,
hasList: true,
hasShow: false,
location: { pathname: '/', search: '', hash: '', state: undefined },
match: { path: '/', url: '/', isExact: true, params: {} },
options: {},
permissions: null,
resource: 'contracts',
};
return (
<List
{...fakeProps}
filter={{ _company_id: contractCompanyId }}
actions={<ListActions />}
>
<Datagrid className={classes.insideTable}>
<TextField source="name" />
</Datagrid>
</List>
);
};

Related

How to use navigation types inside list child component?

I have a problem to set proper types on card component that I render from flat list which should navigate from list to details screen.
All this works but I am getting types errors.
This is my navigators types:
export type RootStackParams = {
Drawer: DrawerStackParams;
};
export type DrawerStackParams = {
AppTabStack: AppTabStackParams;
};
export type AppTabStackParams = {
AppTabStack: HomeStackParams;
Clients: ClientsStackParams;
};
export type ClientsStackParams = {
Clients: undefined;
ClientDetails: {
clientId: number;
};
};
This is app tab navigator which contain clients navigator:
<AppTabStack.Navigator>
<AppTabStack.Screen
name="Clients"
component={ClientsScreenStack}
}}
/>
</AppTabStack.Navigator>
export const ClientsScreenStack = () => {
return (
<ClientsStack.Navigator>
<ClientsStack.Screen name="Clients" component={ClientsScreen} />
<ClientsStack.Screen
name="ClientDetails"
component={ClientDetailsScreen}
/>
</ClientsStack.Navigator>
);
};
In flat list in clients screen I have renderItem() component:
<FlatList ...
renderItem={({ item }) => (
<ClientCard id={item.id} navigation={navigation} />
)}
/>
Which should navigate to another screen:
const onCardPress = () => {
navigation.navigate('ClientDetails', { clientId: id });
};
This card component has following props:
type ClientCardProps = {
id: number;
navigation: NativeStackScreenProps<ClientsStackParams, 'Clients'>;
};
const ClientCard: FC<ClientCardProps> = ({ id, navigation }) => {...
But if I try to call navigation.navigate('ClientDetails', { clientId: id }); I get:
Property 'navigate' does not exist on type 'NativeStackScreenProps<ClientsStackParams, "Clients", undefined>'

How to hide button after pressing in material-table

I am using react material-table. I am in need of a feature like this: I use remote data mode to get a list of records, then I use the custom column rendering function to add a Material Button at the end of each row in the table, When the user presses this button I want it to be hidden. How can I do that. I look forward to receiving your help.
This is the illustration image
I made this example, on button click it gets disabled and a variable is set to to a loading state:
The key aspect here is to define something that identifies the row that is being updated. I use an extra column on which you could also display a spinner component:
{
field: "isUpdating",
render: (rowdata) =>
fetchingClient === rowdata.name
? "loading.." // Add your <Spinner />
: null
},
Since you want to render the button as a custom column (other way could be using actions), on the render attribute of that column, you can use rowdata parameter to access what you are looking for:
{
field: "join",
sorting: false,
render: (rowdata) => (
<button
disabled={fetchingClient === rowdata.name}
onClick={(event) => fetchDataFromRemote(rowdata.name)}
>
Go fetch
</button>
)
}
Here is the link to the sandbox and the complete code, I hope this works for you!
import React, { Fragment, useState } from "react";
import MaterialTable from "material-table";
export default function CustomEditComponent(props) {
const [fetchingClient, setFetchingClient] = useState("");
const fetchDataFromRemote = (clientName) => {
console.log(clientName);
setFetchingClient(clientName);
};
const tableColumns = [
{ title: "Client", field: "client" },
{ title: "Name", field: "name" },
{
field: "isUpdating",
render: (rowdata) =>
fetchingClient === rowdata.name
? "loading.." // Add your <Spinner />
: null,
},
{
field: "join",
sorting: false,
render: (rowdata) => (
<button
disabled={fetchingClient === rowdata.name}
onClick={(event) => fetchDataFromRemote(rowdata.name)}
>
Go fetch
</button>
),
},
];
const tableData = [
{
client: "client1",
name: "Jasnah",
year: "2019",
},
{
client: "client2",
name: "Dalinar",
year: "2018",
},
{
client: "client3",
name: "Kal",
year: "2019",
},
];
return (
<Fragment>
<MaterialTable
columns={tableColumns}
data={tableData}
title="Material Table - custom column "
options={{ search: false }}
/>
</Fragment>
);
}

How can I set a default value on ReferenceManyField component?

I'm rendering a ... which resource is "Users", so always is calling this ReferenceManyField on but sometimes "User" don't have "Name", and I want to display some default if "User" don't have name, I did't find any solution for this problem, sorry. Thank you!
You can replace the contents of the 'record' field before passing it to your components:
<ReferenceManyField reference="Users" target="id" >
<SingleFieldList>
<FormDataConsumer>
{ ({ formData, dispatch, record, ...rest }) => {
const defValues = { Name: 'MyName', Param1: 'Value1', Param2: 'Value2' }
const newRecord = { ...defValues, ...record }
const params = { record: newRecord, ...rest }
return (<TextField source="Name" {...params} />)
}
}
</FormDataConsumer>
</SingleFieldList>
</ReferenceManyField>

Use <List /> on React-Admin dashboard

I'm using react-admin v2.3.2 with a custom dashboard component as shown in the react-admin tutorial.
<Admin dashboard={MyDashboard}>
<Resource name="incidents ... />
</Admin>
Now I'd like to display a list of incidents on my dashboard using the component of react-admin but react-admin complains about missing properties like 'hasEdit'.
I simply passed the props of the dashboard component to the List but this does obviously not work:
class MyDashboard extends React.Component {
constructor(props) {
super(props)
render(
return <List {...this.props}>
<Datagrid> .... </Datagrid>
</List>
)
}
Is it possible to use react-admin's <List /> component on the dashboard and if so how can this be done?
Thanks in advance,
Thomas
I finally managed to use react-admin's components by faking the required props. Within the MyDashboard component I define props required by the component:
let fakeProps = {
basePath: "/incidents",
hasCreate: false,
hasEdit: false,
hasList: true,
hasShow: false,
history: {},
location: { pathname: "/", search: "", hash: "", state: undefined },
match: { path: "/", url: "/", isExact: true, params: {} },
options: {},
permissions: null,
resource: "incidents"
}
<List {...fakeProps}>
<DataGrid>
<TextField .... />
</DataGrid>
</List>
This is indeed a sub-optimal solution but in the first run it solves my problem.
We had a request to create a List in the Dashboard so I had use the accepted answer. Though pagination wouldn't trigger new requests to the server even though the url was changing.
This is the final solution that works with pagination using react-router v4.
In <Admin dashboard={Dashboard}> I added:
<Resource name="dashboard-stats"/>
In the Dashboard.js this is what I have:
import React, { Component } from 'react';
import { GET_LIST } from 'react-admin';
import Card from '#material-ui/core/Card';
import CardHeader from '#material-ui/core/CardHeader';
import dataProvider from './dataProvider';
import {
List,
TextField,
Datagrid
} from 'react-admin';
export default class Dashboard extends Component {
state = {};
initProps = {
basePath: "/",
hasCreate: false,
hasEdit: false,
hasList: true,
hasShow: false,
location: { pathname: "/", search: "", hash: "", state: undefined },
match: { path: "/", url: "/", isExact: true, params: {} },
options: {
},
permissions: null,
resource: "dashboard-stats",
perPage: 5
};
componentWillMount() {
this.unlisten = this.props.history.listen((location, action) => {
if (location.search) {
//regex from: https://stackoverflow.com/a/8649003/1501205
let queryParams = JSON.parse('{"' + decodeURI(location.search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}')
let perPage = queryParams.perPage;
let page = queryParams.page;
this.initProps.perPage = perPage;
this.initProps.page = page;
this.initProps.location = location;
this.setState({'initProps': this.initProps})
}
});
}
componentWillUnmount() {
this.unlisten();
}
componentDidMount() {
this.setState({'initProps': this.initProps});
dataProvider(GET_LIST, 'stats', {
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 50 },
})
.then(response => {
this.setState({'stats': response.data});
});
}
render() {
const {
initProps
} = this.state;
if(!initProps) {
return false;
}
return <Card>
<CardHeader title="Welcome to the Dashboard" />
<List {...initProps} >
<Datagrid>
<TextField source="description" />
<TextField source="state" />
<TextField source="date" />
</Datagrid>
</List>
</Card>;
}
}
don't forget to update the location this.initProps.location with the location just changed - otherwise it will work for the first click (route change) and then it will stop working
I came across this page researching similar.
The way to do this now (late 2020) is to create a ListContextProvider on the page for the main resource you want to show on the dashboard so you get everything provided by that context, including filters.
const controllerProps = useListController(props);
<ListContextProvider value={controllerProps}>
<MyDashboardView>
</ListContextProvider>
You must be wanting to display data from various resources, otherwise you would just use a regular "List" page.
Dashboard can do this. Have a look at the Demo Dashboard
Multiple dataProvider(GET_LIST,... that you pass on to components. You can use this Demo Dashboard Component as an example. Pending Orders
Thanks for the solution. Screen renders with below warning
How to remove all warnings ?
Warning: React does not recognize the basePath prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase basepath instead. If you accidentally passed it from a parent component, remove it from the DOM element.
Like basePath console shows same warnings with other props "currentSort, defaultTitle, displayedFilters,filterValues,hasCreate,hideFilter,isLoading,loadedOnce,onToggleItem,onUnselectItems,perPage,selectedIds,setFilters,setPage,setPerPage,setSort,showFilter,hasBulkActions"
Warning: Invalid value for prop translate on tag. Either remove it from the element, or pass a string or number value to keep it in the DOM. For details
const initProps = {
basePath: '/',
hasCreate: false,
hasEdit: false,
hasList: true,
hasShow: false,
location: { pathname: '/', search: '', hash: '', state: undefined },
match: { path: '/', url: '/', isExact: true, params: {} },
options: {},
permissions: null,
resource: 'dashboard',
perPage: 5
};
<List
{...initProps}
filterDefaultValues={{
arrivedTimestampStart: moment().format('YYYY-MM-DD'),
arrivedTimestamp: moment().format('YYYY-MM-DD')
}}
filters={<DashboardFilter />}
sort={{ field: 'arrivedTimestamp', order: 'DESC' }}
pagination={<Fragment />}
exporter={false}
>
<Responsive
medium={
<div style={styles.flex}>
<OtherComponent />
</div>
}
/>
</List>
I think the answer in v4+ is now the useList hook, which you can see on the List docs page: https://marmelab.com/react-admin/List.html. Copied from that page:
const data = [
{ id: 1, name: 'Arnold' },
{ id: 2, name: 'Sylvester' },
{ id: 3, name: 'Jean-Claude' },
]
const ids = [1, 2, 3];
const MyComponent = () => {
const listContext = useList({
data,
ids,
basePath: '/resource',
resource: 'resource',
});
return (
<ListContextProvider value={listContext}>
<Datagrid>
<TextField source="id" />
<TextField source="name" />
</Datagrid>
</ListContextProvider>
);
};

Required props for List and Edit components in route when using CustomApp

I'm trying to upgrade a custom app from admin-on-rest to react-admin (v2.15). After I figured out that the declareResources action was "replaced" by registerResource most seemed ok, but I still struggle with the List and Edit components route definitions that complains about missing required props (compared to what props are defined in the custom app documentation).
If I define a List component like this it works fine:
<Route exact path="/mystuffs" render={(routeProps) => <MystuffList hasCreate hasEdit hasShow={false} hasList resource="mystuffs" basePath="/mystuffs" {...routeProps} />} />
Similar the only way I can get an Edit-component to work is to pass the required props like so:
<Route exact path="/mystuffs/:id" render={(routeProps) => <MystuffEdit resource="mystuffs" id={routeProps.match.params.id} basePath="/mystuffs" {...routeProps} />} />
But to me it seems a bit tedious to define all of these props (i.e was not required with admin-on-rest). Is this the correct way of doing it or am I missing something obvious here since the custom app documentation doesn't specify all of the required props?
I ended up adding a custom resource component that is pretty similar to the Resource in react-admin. Something like this:
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Switch, Route } from 'react-router-dom';
const RACustomResource = ({ resource, list, edit, create, show, path, children, ...rest }) => {
const { computedMatch, ...options } = rest;
const listResource = list ?
(<Route
exact
path={path}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(list, {
basePath: path || routeProps.match.url,
resource,
hasCreate: !!create,
hasList: !!list,
hasEdit: !!edit,
hasShow: !!show,
...routeOpts,
...options,
});
}}
/>)
: null;
const createResource = create ?
(<Route
path={`${path}/create`}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(create, {
basePath: path || routeProps.match.url,
resource,
hasList: !!list,
hasShow: !!show,
record: {},
...routeOpts,
...options,
});
}}
/>)
: null;
const editResource = edit ?
(<Route
exact
path={`${path}/:id`}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(edit, {
basePath: path || routeProps.match.url,
resource,
hasCreate: !!create,
hasList: !!list,
hasEdit: !!edit,
hasShow: !!show,
id: routeProps.match.params.id,
...routeOpts,
...options,
});
}}
/>)
: null;
const showResource = show ?
(<Route
exact
path={`${path}/:id/show`}
render={(routeProps) => {
const { staticContext, ...routeOpts } = routeProps;
return React.createElement(show, {
basePath: path || routeProps.match.url,
resource,
hasCreate: !!create,
hasList: !!list,
hasEdit: !!edit,
hasShow: !!show,
id: routeProps.match.params.id,
...routeOpts,
...options,
});
}}
/>)
: null;
return (
<Switch>
{createResource}
{showResource}
{editResource}
{listResource}
{children}
</Switch>
);
};
RACustomResource.propTypes = {
resource: PropTypes.string.isRequired,
path: PropTypes.string,
basePath: PropTypes.string,
children: PropTypes.any,
list: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
create: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
edit: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
show: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
};
export default RACustomResource;
// used like this
// <RACustomResource path="/myresource" resource="myresource" list={MyResourceList} create={MyResourceCreate} edit={MyResourceEdit} />
It is indeed required. We have still a lot of work to do on the custom app side, including documentation.
You can help us! Can you explain why you needed to use react-admin this way? What wasn't possible using the default Admin? etc.
Thanks!