From react-admin v3 to v4 - react-admin

I am new to react-admin, and i'd like to implement custom forms for related records.
I found this on the doc but the code doesn't seem to work for version 4 anymore.
I'd like to do the same. Open a modal for the related record.
Is there any v4 example ?

React-admin v4 has built-in support for related record creation, see the Creating new choices documentation.
import { SelectInput, Create, SimpleForm, TextInput } from 'react-admin';
const PostCreate = () => {
const categories = [
{ name: 'Tech', id: 'tech' },
{ name: 'Lifestyle', id: 'lifestyle' },
];
return (
<Create>
<SimpleForm>
<TextInput source="title" />
<SelectInput
onCreate={() => {
const newCategoryName = prompt('Enter a new category');
const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName };
categories.push(newCategory);
return newCategory;
}}
source="category"
choices={categories}
/>
</SimpleForm>
</Create>
);
}

Related

Autocompletearrayinput TypeError: Polyglot.transformPhrase expects argument #1 to be string

Hello stack overflow I was wondering if its possible to pre-populate with default values upon rendering. I'm also getting an error when using the from react admin. Here's my how i'm using my .
return (
<Edit {...props}>
<SimpleForm>
<TextInput source="audience_name" />
<ReferenceInput label="entity_ids" source="entity_ids" reference="posts">
<EntityInput setEntityLabel={onChangeLabel} onSelectEntity={addEntity} entityNames={entityNames} />
</ReferenceInput>
<br />
<AutocompleteArrayInput
source="tags"
shouldRenderSuggestions={(val) => {
console.log(val);
return val.trim().length > 0;
}}
choices={[
{ id: 'programming', name: 'Programming' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'photography', name: 'Photography' },
]}
/>
</SimpleForm>
</Edit>
);
};
Try to add the props translateChoice={false}, like:
<AutocompleteInput source="first_name" choices={choices} translateChoice={false}/>
You can try recreating the i18nProvider, like this:
import polyglotI18nProvider from "ra-i18n-polyglot"; // Install this package
import engMessages from "ra-language-english"; // Install this package
const App = () => {
const i18nProvider = polyglotI18nProvider((locale) => engMessages, "en", {
allowMissing: true,
onMissingKey: (key, _, __) => key,
});
return (
<Admin
...
i18nProvider={i18nProvider}
>
)
}

React-Admin: implementing dependant filters

I'm working on a react-admin page where I want to display some filters depending on the values of another one. To do so, I'm using the filterValues from the ListContext and simply show/hide the dependant filters:
const PostFilter = (props) => {
const { filterValues } = useListContext();
const { mainType } = filterValues;
return (
<Filter {...props}>
<SelectInput
source="mainType"
alwaysOn
choices={[
{ id: "type_1", name: "Type 1" },
{ id: "type_2", name: "Type 2" },
{ id: "type_3", name: "Type 3" }
]}
/>
{mainType === "type_1" && (
<SelectInput
source="type1"
alwaysOn
choices={[
{ id: "type_1_a", name: "A" },
{ id: "type_1_b", name: "B" },
{ id: "type_1_c", name: "C" }
]}
/>
)}
{mainType === "type_2" && (
<SelectInput
source="type2"
alwaysOn
choices={[
{ id: "type_2_a", name: "A" },
{ id: "type_2_b", name: "B" },
{ id: "type_2_c", name: "C" }
]}
/>
)}
</Filter>
);
};
This visually works fine but if I choose a value in one of the child filter and then change the main filter, the url is still containing the filter value even though it's not on screen anymore. As the url contains a wrong filter, then the list is not filtered properly.
To workaround this, I played with a useEffect and the setFilters function but it feels super hacky:
const { filterValues, displayedFilters, setFilters } = useListContext();
const { mainType } = filterValues;
// we need this workaround because these 3 values are not stable
// and get be used as the effect dependencies. However, we still want to be sure
// to always reference their latest version
const refs = React.useRef({ filterValues, displayedFilters, setFilters });
React.useEffect(() => {
refs.current = { filterValues, displayedFilters, setFilters };
});
React.useEffect(() => {
const filters = { ...refs.current.filterValues };
if (mainType !== "type_1") {
delete filters.type1;
}
if (mainType !== "type_2") {
delete filters.type_2;
}
refs.current.setFilters(filters, refs.current.displayedFilters);
}, [mainType]);
Here's a sandbox showing the result: https://codesandbox.io/s/hidden-mountain-i62p3?file=/src/posts.js
Is there a better way to achieve what I want?

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>
);
}

Use the Datagrid component with custom queries - react-admin

Receive below errors, when using Datagrid component with custom queries. Below code works with react-admin ver 3.3.1, whereas it doesn't work with ver 3.8.1
TypeError: Cannot read property 'includes' of undefined
Browser's console info: List components must be used inside a <ListContext.Provider>. Relying on props rather than context to get List data and callbacks is deprecated and won't be supported in the next major version of react-admin.
Refer: https://marmelab.com/react-admin/List.html
#Tip: You can use the Datagrid component with custom queries:
import keyBy from 'lodash/keyBy'
import { useQuery, Datagrid, TextField, Pagination, Loading } from 'react-admin'
const CustomList = () => {
const [page, setPage] = useState(1);
const perPage = 50;
const { data, total, loading, error } = useQuery({
type: 'GET_LIST',
resource: 'posts',
payload: {
pagination: { page, perPage },
sort: { field: 'id', order: 'ASC' },
filter: {},
}
});
if (loading) {
return <Loading />
}
if (error) {
return <p>ERROR: {error}</p>
}
return (
<>
<Datagrid
data={keyBy(data, 'id')}
ids={data.map(({ id }) => id)}
currentSort={{ field: 'id', order: 'ASC' }}
basePath="/posts" // required only if you set use "rowClick"
rowClick="edit"
>
<TextField source="id" />
<TextField source="name" />
</Datagrid>
<Pagination
page={page}
perPage={perPage}
setPage={setPage}
total={total}
/>
</>
)
} ```
Please help!
Since react-admin 3.7, <Datagrid> and <Pagination> read data from a ListContext, instead of expecting the data to be injected by props. See for instance the updated <Datagrid> docs at https://marmelab.com/react-admin/List.html#the-datagrid-component.
Your code will work if you wrap it in a <ListContextProvider>:
import React, { useState } from 'react';
import keyBy from 'lodash/keyBy'
import { useQuery, Datagrid, TextField, Pagination, Loading, ListContextProvider } from 'react-admin'
export const CustomList = () => {
const [page, setPage] = useState(1);
const perPage = 50;
const { data, total, loading, error } = useQuery({
type: 'GET_LIST',
resource: 'posts',
payload: {
pagination: { page, perPage },
sort: { field: 'id', order: 'ASC' },
filter: {},
}
});
if (loading) {
return <Loading />
}
if (error) {
return <p>ERROR: {error}</p>
}
return (
<ListContextProvider value={{
data: keyBy(data, 'id'),
ids: data.map(({ id }) => id),
total,
page,
perPage,
setPage,
currentSort: { field: 'id', order: 'ASC' },
basePath: "/posts",
resource: 'posts',
selectedIds: []
}}>
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="name" />
</Datagrid>
<Pagination />
</ListContextProvider >
)
}
<ReferenceManyField>, as well as other relationship-related components, also implement a ListContext. That means you can use a <Datagrid> of a <Pagination> inside this component.
https://marmelab.com/react-admin/List.html#uselistcontext
Your code should look like this:
import React, { useState } from 'react';
import keyBy from 'lodash/keyBy'
import { useQuery, Datagrid, TextField, Pagination, Loading, ListContextProvider } from 'react-admin'
export const CustomList = () => {
return (
<ReferenceManyField reference="Your resource for pull the data" target="linked field">
<Datagrid rowClick="edit">
<TextField source="id" />
<TextField source="name" />
</Datagrid>
</ReferenceManyField>
)
}

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!