What you were expecting:
Bootstrap a dummy application with create-react-app with typescript, and implement a dummy dataProvider. It should compile just fine.
What happened instead:
Big Typescript error :
Type '{ getList: (resource: string, params: any) => Promise<{ data: { id: number; toto: string; }[]; total: number; }>; getOne: (resource: string, params: any) => Promise<{ data: { id: number; toto: string; }; }>; ... 6 more ...; deleteMany: (resource: string, params: any) => Promise<...>; }' is not assignable to type 'DataProvider | LegacyDataProvider'.
Type '{ getList: (resource: string, params: any) => Promise<{ data: { id: number; toto: string; }[]; total: number; }>; getOne: (resource: string, params: any) => Promise<{ data: { id: number; toto: string; }; }>; ... 6 more ...; deleteMany: (resource: string, params: any) => Promise<...>; }' is not assignable to type 'DataProvider'.
The types returned by 'getList(...)' are incompatible between these types.
Type 'Promise<{ data: { id: number; toto: string; }[]; total: number; }>' is not assignable to type 'Promise<GetListResult<RecordType>>'.
Type '{ data: { id: number; toto: string; }[]; total: number; }' is not assignable to type 'GetListResult<RecordType>'.
Types of property 'data' are incompatible.
Type '{ id: number; toto: string; }[]' is not assignable to type 'RecordType[]'.
Type '{ id: number; toto: string; }' is not assignable to type 'RecordType'.
'{ id: number; toto: string; }' is assignable to the constraint of type 'RecordType', but 'RecordType' could be instantiated with a different subtype of constraint 'Record'. TS2322
65 | const App: React.FC = () => {
66 | return (
> 67 | <Admin loginPage={LoginPage} dataProvider={dataProvider}/>
| ^
68 | );
69 | };
70 |
Steps to reproduce:
npx create-react-app my-app --template typescriptcd my_appnpm install react-adminnpm startRelated code:
In App.tsx:
import React from 'react';
import {Admin} from "react-admin";
const dataProvider = {
getList: (resource: string, params: any) => Promise.resolve({data: [{id: 1, toto: "tata"},{id: 2, toto: "tata"}], total: 0}),
getOne: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
getMany: (resource: string, params: any) => Promise.resolve({data: [{id: 1, toto: "tata"}]}),
getManyReference: (resource: string, params: any) => Promise.resolve({data: [{id: 1, toto: "tata"}]}),
create: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
update: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
updateMany: (resource: string, params: any) => Promise.resolve({data: [1]}),
delete: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
deleteMany: (resource: string, params: any) => Promise.resolve({data: [1]}),
};
function App() {
return (
<Admin dataProvider={dataProvider}/>
);
}
export default App;
Other information:
Environment
Thanks for reporting. Reproduced
In the meantime, you can make it compile with
import React from 'react';
-import {Admin} from "react-admin";
+import { Admin, DataProvider } from "react-admin";
const dataProvider = {
getList: (resource: string, params: any) => Promise.resolve({data: [{id: 1, toto: "tata"},{id: 2, toto: "tata"}], total: 0}),
getOne: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
getMany: (resource: string, params: any) => Promise.resolve({data: [{id: 1, toto: "tata"}]}),
getManyReference: (resource: string, params: any) => Promise.resolve({data: [{id: 1, toto: "tata"}]}),
create: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
update: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
updateMany: (resource: string, params: any) => Promise.resolve({data: [1]}),
delete: (resource: string, params: any) => Promise.resolve({data: {id: 1, toto: "tata"}}),
deleteMany: (resource: string, params: any) => Promise.resolve({data: [1]}),
-};
+} as DataProvider;
Thanks, I confirm this "trick" works. This unblocks me at least. Leaving the issue opened since there seems to be a deeper problem.
Note that the notation const dataProvider: DataProvier = { doesn't work either, same problem.
We struggled with the same and fixed as follows, to have typing of a data provider that includes custom methods:
v3(), that returns a data provider in react-admin v3 format.this in the constructor.
class DataProvider {
constructor(options: NDataProvider.IConstructorOptions) {
[
<your list of data provider methods>
].forEach((fnName) => {
(this as any)[fnName] = (this as any)[fnName].bind(this);
});
}
/**
* @returns the v3 data provider object
*/
v3() {
return {
...pick(this, [
'create',
'delete',
'deleteMany',
'getList',
'getMany',
'getManyReference',
'getOne',
'update',
'updateMany',
...<your extra custom methods>,
]),
};
}
}
// implement your methods, with ra typing or stricter typing
@Silly(FILE)
@CatchAllReject('Cannot create')
create<RecordType extends RaRecord = RaRecord>(
resource: EnumResource,
params: CreateParams,
): Promise<CreateResult<RecordType>> {
// ...
}
const [dataProvider,setDataProvider]= useState<any>(null);
useEffect(()=> {
const dataProvider = new DataProvider({
// any init options go here
});
setDataProvider(dataProvider);
},[]);
// include a `return <Loading/>` here since RA requires a valid data provider so you need to bail ahead of below if it's not yet initialized
<Admin dataProvider={dataProvider.v3()} > ...
pick will preserve the types of each of your methods...nice. export type TDataProvider = ReturnType<DataProvider['v3']>;
Caveat: this may not be 100% correct to cast what returns from useDataProvider to TDataProvider, since it doesn't include the Proxy, but more important for us to make sure we can check that input to, and response from, data provider methods is correctly typed everywhere)
The main reason we use a class is since our data provider is composed of different "sub-data providers" (one for each backend API) . Basically it's an abstract class where we can do things for initialization of each sub-data provider. Another benefit is that you can use decorators on class methods (eg as in above, decorators for logging and for returning a rejected promise from a thrown error, as required by react-admin)
Most helpful comment
In the meantime, you can make it compile with