React-Admin: Implementing a Custom Request Type - react-admin

Is it possible to implement a custom request type in a custom provider in the react-admin framework?
My Use Case
In my case I have 2 separate cases of reference fields.
1. Reference ID field (normal)
api -> users/1
api -> comments/1
2. Sub Entity Reference
api -> users/1/comments/1
So I was planning to implement another request type, like this:
switch (type) {
case GET_LIST:
return apiGetList(resourceName, params);
case GET_MANY:
return apiGetMany(resourceName, params);
case GET_MANY_REFERENCE:
return apiGetManyReference(resourceName, params);
case GET_MANY_REFERENCE_CUSTOM:
return apiGetManyReferenceCustom(resourceName, params);
}
But I can't figure out how to trigger the type from the custom field?

Update for react-admin 3.x
So with React Admin 3.x the data provider now uses method calls instead of a switch case.
For example you can create your own dataprovider method, and the consumer can check if it exists by calling it.
try {
const response = await dataProvider.createMany(resource, { data: values });
return response;
} catch (error) {
const shouldTryFallback = error.toString().includes("Unknown dataProvider");
const apiError = !shouldTryFallback;
if (apiError) {
// handle api error
}
if (shouldTryFallback) {
console.log(
"createInDataProvider",
"createMany not found on data provider (you may need to implement it)"
);
try {
// try and use fallback dataprovider methods
} catch (error) {
// handle fallback error
}
}
}
return reportItems;
Full example of how this is used: https://github.com/benwinding/react-admin-import-csv/blob/0868ca554501c3545dac28a5101ee60a20736aa2/src/uploader.ts#L78

Related

Vue2 Composition Api - How do I fetch data from api?

I am using Vue2.6 with composition api.
I need to reroute to different pages depends on an api response.
Can someone please guide me, please?
I tried using onBeforeMount but it renders the UI elements then rerouted to the corresponding page to the api response..so I can see a flash of the wrong UI..
setup() {
const myData = 'myData';
onBeforeMount(async () => {
try {
const results = await fetchData();
// do reroute depends on results response
}
} catch (err) {
console.log(err);
}
});
return {
myData,
};
I also tried adding async in the setup method but it errored saying my ref variables "Property or method "myData" is not defined on the instance but referenced during render."
async setup() {
const myData = 'myData';
onMounted(async () => {
try {
const results = await fetchData();
// do reroute depends on results response
}
} catch (err) {
console.log(err);
}
});
return {
myData,
};
It looks like you're trying to handle routing (re-routing) dynamically from inside a component. I can't see the rest of the apps, so can't speak to the validity of such a solution, but would like you dissuade you from doing that. routing logic should, IMO, not be handled in a component. The components should mostly just handle the template and user interaction. By the time you're rendering a component, that API should have been resolved already.
I would recommend to resolve the API response before the route is
even completed. You or use a navigationGuard to resolve the API during the route execution. This functionality is asynchronous, so you can await the response before proceeding.
Alternatively, if you really want to handle it in the component, you will have that delay while the API is resolving, but you can implement some loader animation to improve the experience.

Computed params with skip/enable

I was getting annoyed that some of my apollo requests were working and some were not. The ones that don't seem to work are requests with computed params.
Here is an example of one that does work:
import { computed } from "#vue/composition-api";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
import { useGraphQuery } from "./graph-query";
export function useGetCategory(context) {
const params = computed(() => {
const slug = context.root.$route.params.categorySlug;
if (!slug) return;
return { slug };
});
const { response, error, loading } = useGraphQuery(
params,
getCategoryBySlug,
(data) => data.categoryBySlug
);
return { category: response, categoryError: error, categoryLoading: loading };
}
As I am computing my params on the categorySlug, it is available on the route, so it should never be null/undefined.
My useGraphQuery method looks like this:
import { useQuery, useResult } from "#vue/apollo-composable";
export function useGraphQuery(params, gql, pathFn, clientId = "apiClient") {
// if (!params?.value)
// return {
// response: ref(undefined),
// loading: ref(false),
// error: ref(undefined),
// query: ref(undefined),
// };
// TODO: figure our a way to skip the call if the parameters are null
const { result, loading, error, query, fetchMore } = useQuery(gql, params, {
clientId,
//enabled: !!params?.value,
});
const response = useResult(result, null, pathFn);
return { response, loading, error, query, fetchMore };
}
As you can see, I am having an issue because I can't skip and enabled doesn't seem to work as a suitable workaround (for skip).
I tried to return a reference if the parameters are null/undefined, but this never tried to execute the query if the computed params became available.
So my question is how can I skip the request or wait until the params are available?
You should consider changing the flow that calls this method, so it would be called only when the params are defined, instead of trying to skip it when not ready and try to retrigger it again from within the method. Most of the time this will make the code clearer, and also make it more resource-efficient as it won't make unneeded method calls.
If you depend on user input try to validate the input is ready before calling this method.
You can also add a watcher on the params that will trigger the flow when they change, and check in the watcher that all the relevant values are defined before calling the method.
Of course, if you can use computed variables it is better than using watchers in most cases, but in some cases, it can help (mostly when the variable is calculated by an async function, for example, the use of apollo request).

How to call Http post and get request in angular 6

I am updating my application from MEANAngular4 to MEANAngular6 but still don't know how to do http post/get requests. Thats how I did it in Angular 4
registerUser(user) {
return this.http.post(this.domain + 'authentication/register', user).map(res =>
res.json());
}
First you'd notice that you don't need map part because, when the response comes it will already be unwrapped for you.
And also return observable from the api wrapper function and using it will be as simple as I have shown you with the one line below the function.
registerUser(user: any) : Observable<any> {
return this.http
.post(this.domain + 'authentication/register', user);
}
let result = await registerUser(user).toPromise();
import { map } from "rxjs/operators";
registerUser(user: any) {
return this.http.post(this.domain + 'authentication/register', user).pipe(map(res =>
res.json()));
}

Ember JSON API Adapter - customise request URL using dynamic param

When the action below is called in a route, the default the Ember JSON API Adapter will send a PATCH request to ${HOST}/${NAMESPACE}/${MODEL}/${ID}.
saveChanges: function(record) {
return record.save();
},
I would like to be able to send a single PATCH request to ${HOST}/${NAMESPACE}/${MODEL}/************/${ID} where the value of ************ can be passed to the action as a dynamic parameter when calling record.save().
Is there any way to do this using the JSONAPI adapter, or do I have to just use a vanilla AJAX request?
You could customize your application- or model-adapter
ember generate adapter application
or
ember generate adapter model-name
app/adapters/application.js or app/adapters/model-name.js
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
namespace: 'api',
urlForUpdateRecord(id, modelName, snapshot) {
let originalUpdateURL = this._super(...arguments);
let { adapterOptions } = snapshot;
if (adapterOptions && adapterOptions.customString) {
let modelPath = this.pathForType(modelName);
return originalUpdateURL.replace(`/${modelPath}/`, `/${modelPath}/${adapterOptions.customString}/`);
}
return originalUpdateURL;
}
});
after that you can call save-method of your model with adapterOptions passed:
this.get('model').save({
adapterOptions: { customString: 'hello-world' }
});
after that your patchURL will look like:
/api/your-model/hello-world/1

GraphQL + Relay: How can I perform authorization for refetching?

I am working on a GraphQL server built using Express and attempting to support Relay.
For a regular GraphQL query, I can handle authorization in the resolve function. E.g.:
var queryType = new GraphQLObjectType({
name: 'RootQueryType',
fields: () => ({
foo: {
type: new GraphQLList(bar),
description: 'I should have access to some but not all instances of bar',
resolve: (root, args, request) => getBarsIHaveAccessTo(request.user)
}
})
});
To support Relay refetching on the back-end, Facebook's Relay tutorial instructs us to have GraphQL objects implement a nodeInterface for mapping global ids to objects and objects to GraphQL types. The nodeInterface is defined by the nodeDefinitions function from graphql-relay.
const {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
const {type, id} = fromGlobalId(globalId);
if (type === 'bar') {
// since I don't have access to the request object here, I can't pass the user to getBar, so getBar can't perform authorization
return getBar(id);
} else {
return null;
}
},
(obj) => {
// return the object type
}
);
The refetching function that gets passed to nodeDefinitions doesn't get passed the request object, only the global id. How can I get access to the user during refetching so I can authorize those requests?
As a sanity check, I tried querying for nodes that the authenticated user doesn't otherwise have access to (and shouldn't) through the node interface, and got the requested data back:
{node(id:"id_of_something_unauthorized"){
... on bar {
field_this_user_shouldnt_see
}
}}
=>
{
"data": {
"node": {
"field_this_user_shouldnt_see": "a secret"
}
}
}
As it turns out, the request data actually does get passed to resolve. If we look at the source, we see that nodeDefinitions tosses out the parent parameter and passes the global id, the context (containing the request data), and the info arguments from nodeField's resolve function.
Ultimately, where a resolve call would get the following arguments:
(parent, args, context, info)
the idFetcher instead gets:
(id, context, info)
So we can implement authorization as follows:
const {nodeInterface, nodeField} = nodeDefinitions(
(globalId, context) => {
const {type, id} = fromGlobalId(globalId);
if (type === 'bar') {
// get Bar with id==id if context.user has access
return getBar(context.user, id);
} else {
return null;
}
},
(obj) => {
// return the object type
}
);
https://github.com/graphql/graphql-relay-js/blob/master/src/node/node.js#L94-L102