Relay.Renderer render callback doesn't expose fetched data - relayjs

I have a problem with render callback in Relay.Renderer.
export const renderCallback = ({ done, error, retry, stale, props, element }) => {
console.log(props)
// if returning undefined, previously rendered view is rendered
return undefined
}
props is null until it fetches the data and when it's finally not null, it doesn't contain fetched data.
For example, if it's node query, logged props.node is the following:
node: {
__dataID__: "AH4zdDo1ODBjYVWkZTZmNzFgDSJmMTg1YQQ5Y2E="
__fragments__: {
4::client: [
0: {
postId: null
}
]
}
}
Component actually has the data but it doesn't expose it in Relay.Renderer render callback.
Is this normal and how to access the Relay fetched data in render callback?

You should check the parameter of Relay.Renderer render
Like this https://github.com/sibelius/ExNavRelay/blob/master/src/RelayUtils.js#L114

Related

Pagination - How can I store and provide a non-numeric page identifier?

I am trying to add pagination to my Zapier trigger.
The API I am using for the trigger supports pagination, but not using a page number in the traditional sense (ie. page 1,2,3,...). Instead, the API response includes a key (ie. "q1w2e3r4") which should be passed as a parameter to the next request to get the next page of results.
From looking at the docs, I can use {{bundle.meta.page}} (which defaults to 0 unless otherwise set).
I am trying to set {{bundle.meta.page}} in the code editor, with an example shown below:
const options = {
url: 'company_xyz.com/api/widgets',
method: 'GET',
...,
params: {
...,
'pagination_key': bundle.meta.page,
}
}
return z.request(options)
.then((response) => {
response.throwForStatus();
const json_response = response.json;
widgets = json_response.widgets
...
bundle.meta.page = json_response["next_pagination_key"]
return widgets;
});
The problem is that when Zapier tries to retrieve the next page, bundle.meta.page will be 1 instead of the value of "next_pagination_key" from the result of the previous request.
There are docs on cursor-based pagination in the CLI docs.
The relevant block is:
const performWithAsync = async (z, bundle) => {
let cursor;
if (bundle.meta.page) {
cursor = await z.cursor.get(); // string | null
}
const response = await z.request(
'https://5ae7ad3547436a00143e104d.mockapi.io/api/recipes',
{
// if cursor is null, it's sent as an empty query
// param and should be ignored by the server
params: { cursor: cursor }
}
);
// we successfully got page 1, should store the cursor in case the user wants page 2
await z.cursor.set(response.nextPage);
return response.items;
};
This should work in the Zapier Visual Builder, but you might need to use the CLI instead. You can export your integration using the zapier convert CLI command (docs).

Angular Material Data Table - How To Setup filterPredicate For A Column With Type Ahead / Auto Complete Search?

I've read the various implementations of filterPredicate on SO, Github, etc but they aren't helpful for me to understand what to do with type ahead searches.
I enter a letter into an input form field, say p, and I receive all the data with last names starting with p from the db. That part of my setup works fine. However, I don't want to hit the db again when I type the next letter, say r. I want to filter the data table for last names starting with pr. This is where the trouble starts.
When I type the second letter I have an if/else statement that tests if the var I'm using has >1 in the string. When it does I pass params to a function for the custom filtering on the table with the data already downloaded from the db. I'm avoiding a db call with every letter, which does work. I don't understand "(data, filter)". They seem like params but aren't. How do they work? What code is needed to finish this?
(I have `dataSource.filter = filterValue; working fine elsewhere.)
Params explained:
column = user_name
filterValue = pr...
The confusion:
public filterColumn(column: string, filterValue: string, dataSource) {
dataSource.filterPredicate = (data, filter) => {
console.log('data in filter column: ', data); // Never called.
// What goes here?
// return ???;
}
}
My dataSource object. I see filterPredicate, data, and filter properties to work with. Rather abstract how to use them.
dataSource in filterColumn: MatTableDataSource {_renderData: BehaviorSubject, _filter: BehaviorSubject, _internalPageChanges: Subject, _renderChangesSubscription: Subscriber, sortingDataAccessor: ƒ, …}
filterPredicate: (data, filter) => {…}arguments: [Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
at Function.invokeGetter (<anonymous>:2:14)]caller: (...)length: 2name: ""__proto__: ƒ ()[[FunctionLocation]]: data-utilities.service.ts:43[[Scopes]]: Scopes[3]
filteredData: (3) [{…}, {…}, {…}]
sortData: (data, sort) => {…}
sortingDataAccessor: (data, sortHeaderId) => {…}
_data: BehaviorSubject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
_filter: BehaviorSubject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
_internalPageChanges: Subject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
_paginator: MatPaginator {_isInitialized: true, _pendingSubscribers: null, initialized: Observable, _disabled: false, _intl: MatPaginatorIntl, …}
_renderChangesSubscription: Subscriber {closed: false, _parentOrParents: null, _subscriptions: Array(1), syncErrorValue: null, syncErrorThrown: false, …}
_renderData: BehaviorSubject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}data: (...)filter: (...)paginator: (...)sort: (...)__proto__: DataSource
I've included most of the component I made in Angular for typeahead search. The guts of the typeahead code is in the utilities shared component at the bottom. I used a shared component here because I'll use this in many places. However, I think it is a hack and a more elegant answer is possible. This works, it is easy, but not all that pretty. I can't afford more time to figure out pretty now. I suspect the answer is in RegEx.
In the typeahead.compoent in the .pipe you'll find how I call the code in the utility.
This code is in a shared component typeahead.component.ts
public searchLastName$ = new Subject<string>(); // Binds to the html text box element.
ngAfterViewInit() {
// -------- For Column Incremental Queries --------- //
// searchLastName$ binds to the html element.
this.searchLastName$.subscribe(result => {
this.queryLastName(result);
});
}
// --------- LAST NAME INCREMENTAL QUERY --------------- //
private queryLastName(filterValue) {
// Custom filter for this function. If in ngOnInit on the calling component then it applies
// to the whole calling component. We need various filters so that doesn't work.
this.membersComponent.dataSource.filterPredicate = (data: { last_name: string }, filterValue: string) =>
data.last_name.trim().toLowerCase().indexOf(filterValue) !== -1;
// When the first letter is typed then get data from db. After that just filter the table.
if (filterValue.length === 1) {
filterValue = filterValue.trim(); // Remove whitespace
// filterValue = filterValue.toUpperCase(); // MatTableDataSource defaults to lowercase matches
const lastNameSearch = gql`
query ($last_name: String!) {
lastNameSearch(last_name: $last_name) {
...membersTableFrag
}
}
${membersTableFrag}
`;
this.apollo
.watchQuery({
query: lastNameSearch,
variables: {
last_name: filterValue,
},
})
.valueChanges
.pipe(
map(returnedArray => {
// console.log('returnedArray in map: ', returnedArray); // All last_name's with the letter in them someplace.
const membersArray = returnedArray.data['lastNameSearch']; // extract items array from GraphQL JSON array
// For case insensitive search
const newArray = membersArray.filter(this.utilitiesService.filterBy(filterValue, 'last_name'));
return newArray;
})
)
.subscribe(result => {
this.membersComponent.dataSource.data = result;
});
} else {
// Filter the table instead of calling the db for each letter entered.
// Note: Apollo client doesn't seem able to query the cache with this kind of search.
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // MatTableDataSource defaults to lowercase matches
// Interface and redefinition of filterPredicate in the ngOnInit
this.membersComponent.dataSource.filter = filterValue; // Filters all columns unless modifed by filterPredicate.
}
}
utilities.service.ts
// -------------- DATABASE COLUMN SEARCH -------------
// Shared with other components with tables.
// For case insensitive search.
// THIS NEEDS TO BE CLEANED UP BUT I'M MOVING ON, MAYBE LATER
public filterBy = (filterValue, column) => {
return (item) => {
const charTest = item[column].charAt(0);
if (charTest === filterValue.toLowerCase()) {
return true;
} else if (charTest === filterValue.toUpperCase()) {
return true;
} else {
return false;
}
}
};

How to retrieve JSON object stored in cache from service worker?

I have a Json object stored in cache , Please see my cache here.
And I want to retrieve the json values from my service worker
caches.open('my-post-request').then(function (cache) {
cache.match('/cached-products.json').then(function (matchedResponse) {
return fetch('/cached-products.json').then(function (response) {
return response;
})
});
});
is there a way to do that? exploring the response in the console I can just see the properties headers, ok, status, type, url, body, but I cant find my json values anywhere.
I would appreciate any suggestion.
Thanks
You could try something like this:
var CACHE_NAME = 'dependencies-cache';
self.addEventListener('install', function(event) {
console.log('[install] Kicking off service worker registration!');
event.waitUntil(
caches.open(CACHE_NAME).then(function(cache) { // With the cache opened, load a JSON file containing an array of files to be cached
return fetch('files-to-cache.json').then(function(response) {
return response.json(); // Once the contents are loaded, convert the raw text to a JavaScript object
}).then(function(files) {
console.log('[install] Adding files from JSON file: ', files); // this will log the cached json file
return cache.addAll(files); // Use cache.addAll just as you would a hardcoded array of items
});
})
.then(function() {
console.log(
'[install] All required resources have been cached;',
'the Service Worker was successfully installed!'
);
return self.skipWaiting(); // Force activation
})
);
});
This will solve your problem.
From the code above, you can simply return your response as response.json() to convert the raw text to a Javascript Object. For full implementation of Service Worker to cache JSON file, you can visit this documentation.

search functionality using relay

How to implement a search functionality with relay?
So, the workflow is
user navigate to search form.
there should not be any query (as in relay container) when initializing the view.
user fills the field values, and press the action/search button.
a relay query is sent to the server
results are received from the server.
page displays it and relay reconciles the filtered results with local cache.
I have not seen an example of ad hoc query but only part of a relay container (which it resolves before component initialization). So, how to model it. should it be like a mutation?
If I understand correctly you'd like to not send any query at all for the component until the user enters some search text, at which point the query should sent. This can be accomplished with the example posted by #Xuorig, with one addition: use GraphQL's #include directive to skip the fragment until a variable is set. Here's the extended example:
export default Relay.createContainer(Search, {
initialVariables: {
count: 3,
query: null,
hasQuery: false, // `#include(if: ...)` takes a boolean
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
# add `#include` to skip the fragment unless $query/$hasQuery are set
items(first: $count, query: $query) #include(if: $hasQuery) {
edges {
node {
...
}
}
}
}
`,
},
});
This query will be skipped initially since the include condition is falsy. Then, the component can call setVariables({query: someQueryText, hasQuery: true}) when text input is changed, at which point the #include condition will become true and the query will be sent to the server.
This is the way I've implemented simple search in my project:
export default Relay.createContainer(Search, {
initialVariables: {
count: 3,
title: null,
category: null,
},
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
items(first: $count, title: $title, category: $category) {
edges {
node {
...
}
}
}
}
`,
},
});
Your search form simply has to update the initialVariables using this.props.relay.setVariables and relay will query the new data.

Sorting an array and saving to Mongo doesn't seem to trigger reactivity on my page

I have a template that I am loading from a route like so:
this.route('formEdit', {
path: '/admin/form/:_id',
data: function() { return Forms.findOne({_id: this.params._id}); },
onBeforeAction: function() { AccountUtils.authenticationRequired(this, ['ADMIN']); }
});
In which I have a template defined like:
<template name="formEdit">
<div id="formContainer">
...
{{#each header_fields}}
<div class="sortable">
{{> headerFieldViewRow }}
</div>
{{/each}}
</div>
</template>
And:
<template name="headerFieldViewRow">
{{#with header_field}}
...
{{/with}}
</template>
I then make the container around all the header fields sortable using jQuery UI Sortable:
Template.formEdit.rendered = function() {
$('.sortable').sortable({
axis: "y",
stop: function(event, ui) {
var form = Blaze.getData($('#formContainer')[0]);
var newFormHeaders = [];
$('#headerFieldsTable div.headerField').each(function(idx, headerFieldDiv) {
var header = Blaze.getData(headerFieldDiv);
header.sequence = idx;
Meteor.call('saveHeaderField', header);
newFormHeaders.push({header_field_id: header._id});
});
form.header_fields = newFormHeaders;
Meteor.call('saveForm', form);
}
});
}
Basically, when sorting stops, loop through all the headers, getting the data for each and updating the sequence number, then re-build the array in Forms and save them back. In the server code I have printouts for the two save calls, and the do properly print out the correct order of both the headers and the form.
The problem I am running into is that, after sorting, the visual display of the form and it's headers "snaps" back to the pre-sorted state, even though the data in the DB is correct. If I simply reload the form, either by hitting enter in the Address bar or by simply re-loading it from the menu, everything is displayed correctly. It's as if the reactive piece isn't working.
I have noted that I am getting an error when I update the client code in my server log that reads:
=> Client modified -- refreshing
I20141010-18:25:47.017(-4)? Failed to receive keepalive! Exiting.
=> Exited with code: 1
I don't think this is related as I was getting that error prior to adding this sorting code.
Update: Adding code for saveForm and saveHeader
saveForm:
// Saves the Form to the DB
saveForm: function(params) {
// Structure the data to send
var formEntry = {
title: params.title,
client_id: params.client_id,
header_fields: params.header_fields,
form_fields: params.form_fields,
created_by: Meteor.userId(),
created_on: new Date()
};
if (params._id == null) {
console.log("Saving new Form entry: %j", formEntry);
formEntry._id = Forms.insert(formEntry);
} else {
formEntry._id = params._id;
console.log("Updating Form entry: %j", formEntry);
Forms.update({_id: formEntry._id}, formEntry);
}
return formEntry;
}
saveHeader:
// Saves the HeaderField to the DB
saveHeaderField: function(params) {
// Structure the data to send
var headerFieldEntry = {
label: params.label,
field_type: params.field_type,
field_options: params.field_options,
form_id: params.form_id,
required: params.required,
allows_pictures: params.allows_pictures,
record_geo: params.record_geo
};
if (params._id == null) {
console.log("Saving new HeaderField entry: %j", headerFieldEntry);
headerFieldEntry._id = HeaderFields.insert(headerFieldEntry);
} else {
headerFieldEntry._id = params._id;
console.log("Updating HeaderField entry: %j", headerFieldEntry);
HeaderFields.update({_id: headerFieldEntry._id}, headerFieldEntry);
}
return headerFieldEntry;
}
I think the issue here is that Meteor.call will run on the server - you either need to use a callback or invalidate your template, if you want to return a value Meteor.call. From the docs:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
There is more info in this answer and this answer and the Meteor.call docs.
Hope that helps!

Resources