How to make inputs more DRY in React Apollo? - apollo-client

How can I reuse variable arguments in GQL Apollo React? e.g. in a situation where the inputs are the same but we need different data corresponding for a different situation.
Is there a way to define this inside or outside of this query call?
const REQUEST_1 = gql`
query QueryA(
$page: Int
$a: String
$b: Number
$etc: EtcFilter
) {
getData(
limit: 24
page: $page
a: $a
b: $b
etc: $etc
) {
total
result {
id
time
location
}
}
}
`
const REQUEST_2 = gql`
query QueryA(
$page: Int
$a: String
$b: Number
$etc: EtcFilter
) {
getData(
limit: 24
page: $page
a: $a
b: $b
etc: $etc
) {
total
result {
postContent
date
coordinates
id
}
}
}
`

Based on your Query example, the short/sad answer is you cannot do that.
According to the current GraphQL specs:
Variables must be defined at the top of an operation and are in scope throughout the execution of that operation.
That operation is referring to a query/mutation, in your example, REQUEST_1 and REQUEST_2 are two separate operations.
There is an open issue in the graphql-specs that could possible address this use case, but it is not anywhere near implementation.
Edit with one operation example (from comments)
You could combine both operations in one, in something like this:
const REQUEST = gql`
query QueryA(
$page: Int
$a: String
$b: Number
$etc: EtcFilter
) {
getData(
limit: 24
page: $page
a: $a
b: $b
etc: $etc
) {
total
result1: result {
id
time
location
}
result2: result {
postContent
date
coordinates
id
}
}
}
`
because you would be querying the same result object just with different fields, you will have to give an alias to it.
This will of course make the full query every time, but it will keep it cleaner of input parameters.

Related

How to properly escape parameters for JSONB deep query in node-postgres

When bypassing an ORM and doing direct queries to node-postgres, there are a nice pile of weird edge issues to keep in mind. For example, you have probably already encountered the fact that camelCaseColumns have to be in double-quotes, and also parameterized type casting…
client.query(`SELECT id, "authorFirstName", "authorLastName" FROM books WHERE isbn = $1::int`, [1444723448]`)
client.query(`SELECT id FROM books WHERE "authorLastName" = $1::string`, ['King']`)
JSON and JSONB types add another aspect of weirdness. The important thing to keep in mind is, "$1" is not merely a variable placeholder; it is an indicator of a discrete unit of information.
Given a table where characters is a column of type JSONB, this will not work…
client.query(
`SELECT id FROM books WHERE characters #> ([ {'name': $1::string} ])`,
['Roland Deschain']
)
This fails because the unit of information is the JSON object, not a string you're inserting into a blob of text.
This is a little more clear when one looks at a simpler SELECT and an UPDATE…
const userData = await client.query(
`SELECT characters FROM books WHERE id = $1::uuid`,
[ some_book_id ]
)
const newCharacters = JSON.stringify([
...userData[0].characters,
{ name: 'Jake' },
{ name: 'Eddie' },
{ name: 'Odetta' }
])
await this.databaseService.executeQuery(
`UPDATE books SET characters = $1::jsonb WHERE id = $2::uuid`,
[ newCharacters, some_book_id ]
)
The deep search query should be formed thusly:
const searchBundle = JSON.stringify([
{'name': 'Roland Deschain'}
])
client.query(
`SELECT id FROM books WHERE characters #> ($1::jsonb)`,
[searchBundle]
)

How to use (opaque) cursors in GraphQL / Relay when using filter arguments and order by

Imagine the following GraphQL request:
{
books(
first:10,
filter: [{field: TITLE, contains: "Potter"}],
orderBy: [{sort: PRICE, direction: DESC}, {sort: TITLE}]
)
}
The result will return a connection with the Relay cursor information.
Should the cursor contain the filter and orderBy details?
Meaning querying the next set of data would only mean:
{
books(first:10, after:"opaque-cursor")
}
Or should the filter and orderBy be repeated?
In the latter case the user can specify different filter and/or orderBy details which would make the opaque cursor invalid.
I can't find anything in the Relay spec about this.
I've seen this done multiple ways, but I've found that with cursor-based pagination, your cursor exists only within your dataset, and to change the filters would change the dataset, making it invalid.
If you're using SQL (or something without cursor-based-pagination), then, you would need to include enough information in your cursor to be able to recover it. Your cursor would need to include all of your filter / order information, and you would need to disallow any additional filtering.
You'd have to throw an error if they sent "after" along with "filter / orderBy". You could, optionally, check to see if the arguments are the same as the ones in your cursor, in case of user error, but there simply is no use-case to get "page 2" of a DIFFERENT set of data.
I came across the same question / problem, and came to the same conclusion as #Dan Crews. The cursor must contain everything you need to execute the database query, except for LIMIT.
When your initial query is something like
SELECT *
FROM DataTable
WHERE filterField = 42
ORDER BY sortingField,ASC
LIMIT 10
-- with implicit OFFSET 0
then you could basically (don't do this in a real app, because of SQL Injections!) use exactly this query as your cursor. You just have to remove LIMIT x and append OFFSET y for every node.
Response:
{
edges: [
{
cursor: "SELECT ... WHERE ... ORDER BY ... OFFSET 0",
node: { ... }
},
{
cursor: "SELECT ... WHERE ... ORDER BY ... OFFSET 1",
node: { ... }
},
...,
{
cursor: "SELECT ... WHERE ... ORDER BY ... OFFSET 9",
node: { ... }
}
]
pageInfo: {
startCursor: "SELECT ... WHERE ... ORDER BY ... OFFSET 0"
endCursor: "SELECT ... WHERE ... ORDER BY ... OFFSET 9"
}
}
The next request will then use after: CURSOR, first: 10. Then you'll take the after argument and set the LIMIT and OFFSET:
LIMIT = first
OFFSET = OFFSET + 1
Then the resulting database query would be this when using after = endCursor:
SELECT *
FROM DataTable
WHERE filterField = 42
ORDER BY sortingField,ASC
LIMIT 10
OFFSET 10
As already mentioned above: This is only an example, and it's highly vulnerable to SQL Injections!
In a real world app, you could simply encode the provided filter and orderBy arguments within the cursor, and add offset as well:
function handleGraphQLRequest(first, after, filter, orderBy) {
let offset = 0; // initial offset, if after isn't provided
if(after != null) {
// combination of after + filter/orderBy is not allowed!
if(filter != null || orderBy != null) {
throw new Error("You can't combine after with filter and/or orderBy");
}
// parse filter, orderBy, offset from after cursor
cursorData = fromBase64String(after);
filter = cursorData.filter;
orderBy = cursorData.orderBy;
offset = cursorData.offset;
}
const databaseResult = executeDatabaseQuery(
filter, // = WHERE ...
orderBy, // = ORDER BY ...
first, // = LIMIT ...
offset // = OFFSET ...
);
const edges = []; // this is the resulting edges array
let currentOffset = offset; // this is used to calc the offset for each node
for(let node of databaseResult.nodes) { // iterate over the database results
currentOffset++;
const currentCursor = createCursorForNode(filter, orderBy, currentOffset);
edges.push({
cursor = currentCursor,
node = node
});
}
return {
edges: edges,
pageInfo: buildPageInfo(edges, totalCount, offset) // instead of
// of providing totalCount, you could also fetch (limit+1) from
// database to check if there is a next page available
}
}
// this function returns the cursor string
function createCursorForNode(filter, orderBy, offset) {
return toBase64String({
filter: filter,
orderBy: orderBy,
offset: offset
});
}
// function to build pageInfo object
function buildPageInfo(edges, totalCount, offset) {
return {
startCursor: edges.length ? edges[0].cursor : null,
endCursor: edges.length ? edges[edges.length - 1].cursor : null,
hasPreviousPage: offset > 0 && totalCount > 0,
hasNextPage: offset + edges.length < totalCount
}
}
The content of cursor depends mainly on your database and you database layout.
The code above emulates a simple pagination with limit and offset. But you could (if supported by your database) of course use something else.
In the meantime I came to another conclusion: I think it doesn't really matter whether you use an all-in-one cursor, or if you repeat filter and orderBy with each request.
There are basically two types of cursors:
(1.) You can treat a cursor as a "pointer to a specific item". This way the filter and sorting can change, but your cursor can stay the same. Kinda like the pivot element in quicksort, where the pivot element stays in place and everything around it can move.
Elasticsearch's Search After works like this. Here the cursor is just a pointer to a specific item in the dataset. But filter and orderBy can change independently.
The implementation for this style of cursor is dead simple: Just concat every sortable field. Done. Example: If your entity can be sorted by price and title (plus of course id, because you need some unique field as tie breaker), your cursor always consists of { id, price, title }.
(2.) The "all-in-one cursor" on the other hand acts like a "pointer to an item within a filtered and sorted result set". It has the benefit, that you can encode whatever you want. The server could for example change the filter and orderBy data (for whatever reason) without the client noticing it.
For example you could use Elasticsearch's Scroll API, which caches the result set on the server and though doesn't need filter and orderBy after the initial search request.
But aside from Elasticsearch's Scroll API, you always need filter, orderBy, limit, pointer in every request. Though I think it's an implementation detail and a matter of taste, whether you include everything within your cursor, or if you send it as separate arguments. The outcome is the same.

Enum for number in GraphQL (js)

I want to allow one of the fields to be either 1, 2 or 3. I am not exactly sure how to do that in http://graphql.org/graphql-js/type/
Because I would need to do something like:
var AgeType = new GraphQLEnumType({
name: 'Age',
values: {
1: { value: 0 },
2: { value: 1 },
3: { value: 2 }
}
});
And this doesn't work because the key is a number...
It's not possible as described here: https://github.com/graphql/graphiql/issues/586
GraphQL require enum values to match [_A-Za-z][_0-9A-Za-z] RegExp
Enum variables have to start with a letter, so you will have to use String or Int type. In order to ensure that only 1,2 and 3 are passed, you may implement some kind of validation rules in the resolvers.

Automatic update an input number field value based on number values in other input fields

I think I understand how formValueSelector works. But what I have not be able to figure out on how to use formValueSelector to update an INPUT field.
connect(
state => {
const { firstValue, secondValue } = selector(state, 'first', 'second')
// do some calculation
return {
sum: firstValue + secondValue
}
}
)(MyFormComponent)
In other words how do I get that sum value to be the value of on input field or global Redux state value?
There's an endpoint for such scenarios in redux-form API named as InitializeFromStateForm.
Refer here https://redux-form.com/6.6.3/examples/initializefromstate/
InitializeFromStateForm = reduxForm({
form: 'initializeFromState' // a unique identifier for this form
})(InitializeFromStateForm)
Or else there is no harm in having a new prop for the inputs you like to have an initial state fetched from store something like this.
{
sum: firstValue + secondValue,
otherInput: "initial value"
}
You can dispatch the change action from your component.
Ex:
this.props.change('formname','fieldname', this.props.sum);

Tablesorter filter on the total values

I return the values 10 by 10 in my table using the tablesorterPager.
This ajaxProcessing code treat my values returned by json.
When I use the filters (filter-select or search), it's only applied on the 10 values returned by my controller and I want to apply it to the total rows.
ajaxProcessing: function(data){
if (data && data.hasOwnProperty('rows')) {
var r, row, c, d = data.rows,
total = data.total_rows,
headers = data.headers,
rows = [],
len = total;
for ( r=0; r < len; r++ ) {
row = [];
for ( c in d[r] ) {
if (typeof(c) === "string") {
row.push(d[r][c]);
}
}
rows.push(row);
}
return [ total, rows, headers ];
}
},
Have you some ideas?
Thank you for your time.
Sounds like you may need to use the ajaxUrl and customAjaxUrl options. With the ajaxUrl option you set how the filter url parameters are formatted when a filter is activated. With customAjaxUrl you can convert the filter url parameters set in ajaxURl to whatever you need to properly filter the 1000 records according to how your program works.
For example, let's say you have ajaxUrl set as follows:
ajaxUrl: dataquery.php?{filter:filter}
Let's say you type "John" into the first column filter in the table. This will cause customAjaxUrl to run and the url variable will be dataquery.php?filter[0]=John. Let's say that for the query to properly filter your 1000 records you need the url query to be dataquery.php?name=John. To make that conversion, add coding within the customAjaxUrl option to change filter[0] to name and return the converted url dataquery.php?name=John. This will then filter the 1000 records and return the filtered result to your table.
You may not be using php but the concept would still apply.

Resources