QueryRenderer takes a “query” prop, which contains a topmost query of the application made of fragments for downstream components:
const LinkListPage = () => (<QueryRenderer
query={ rootQuery }
{ ...otherProps }
render={
(error, props) =>
<LinkList viewer={ props.viewer } />
}
/>)
/* ... */
const rootQuery = graphql`
query LinkListPageQuery {
viewer {
...LinkList_viewer
}
}
`
In the above example, the fragment “LinkList_viewer” is self-sufficient, and it tells us which container it supplies data to, and which prop it fills in.
Why the relay compiler does not assemble that root query on its own? Why do we need to repeat the typing of props.viewer, when it's immediately obvious and unambiguous what to pass where? Is there any case when manual construction of the root query helps us?
The root query is used to distinguish asking for data that is idempotent (query) from asking for data that will mutate the state (mutations) from data the behaves in other ways (subscriptions).
I think the philosophy in the Relay library is to not try and have too much magic in the implementation of using it, hence that lack of automatically passing the data in a query with only one node.
Related
I am making a discord bot that needs to read a list of arguments, and with the first argument given, have it determine which branch to run.
Something kinda like this.
Mono.just(stringList)
.ifSelectmap(conditional1, branch1)
.ifSelectmap(conditional2, branch2)
.ifSelectmap(conditional3, branch3)
// non branch logic here
The only way I can figure out to do anything like this would just cause several deeply nested switchIfEmpty statements. Which would be hard to manage.
if the conditional logic doesn't involve latency-heavy operations (ie performing IO), then there is nothing wrong in passing a more fleshed out Function to map/flatMap.
I'm going to assume your "branches" are actually asynchronous operations represented as a Mono<R> or Flux<R> (that is, all the branches share the same return type R), so we're talking flatMap:
Flux<V> source; //...
Flux<R> result = source.flatMap(v -> {
if (conditional1) return branch1(v);
if (conditional2) return branch2(v);
if (conditional3) return branch3(v);
return Mono.empty(); //no conditional match == ignore
//you might want a default processing instead for the above
};
Whenever I need to pass data down the reactive chain I end up doing something like this:
public Mono<String> doFooAndPassDtoAsMono(Dto dto) {
return Mono.just(dto)
.flatMap(dtoMono -> {
Mono<String> result = // remote call returning a Mono
return Mono.zip(Mono.just(dtoMono), result);
})
.flatMap(tup2 -> {
return doSomething(tup2.getT1().getFoo(), tup2.getT2()); // do something that requires foo and result and returns a Mono
});
}
Given the below sample Dto class:
class Dto {
private String foo;
public String getFoo() {
return this.foo;
}
}
Because it often gets tedious to zip the data all the time to pass it down the chain (especially a few levels down) I was wondering if it's ok to simply reference the dto directly like so:
public Mono<String> doFooAndReferenceParam(Dto dto) {
Mono<String> result = // remote call returning a Mono
return result.flatMap(result -> {
return doSomething(dto.getFoo(), result); // do something that requires foo and result and returns a Mono
});
}
My concern about the second approach is that assuming a subscriber subscribes to this Mono on a thread pool would I need to guarantee that Dto is thread safe (the above example is simple because it just carries a String but what if it's not)?
Also, which one is considered "best practice"?
Based on what you have shared, you can simply do following:
public Mono<String> doFooAndPassDtoAsMono(Dto dto) {
return Mono.just(dto.getFoo());
}
The way you are using zip in the first option doesn't solve any purpose. Similarly, the 2nd option will not work either as once the mono is empty then the next flat map will not be triggered.
The case is simple if
The reference data is available from the beginning (i.e. before the creation of the chain), and
The chain is created for processing at most one event (i.e. starts with a Mono), and
The reference data is immutable.
Then you can simple refer to the reference data in a parameter or local variable – just like in your second solution. This is completely okay, and there are no concurrency issues.
Using mutable data in reactive flows is strongly discouraged. If you had a mutable Dto class, you might still be able to use it (assuming proper synchronization) – but this will be very surprising to readers of your code.
I am currently doing the facebook relayjs tutorial and I need help understanding this part of the tutorial, it states
Next, let's define a node interface and type. We need only provide a
way for Relay to map from an object to the GraphQL type associated
with that object, and from a global ID to the object it points to
const {nodeInterface, nodeField} = nodeDefinitions(
(globalId) => {
const {type, id} = fromGlobalId(globalId);
if (type === 'Game') {
return getGame(id);
} else if (type === 'HidingSpot') {
return getHidingSpot(id);
} else {
return null;
}
},
(obj) => {
if (obj instanceof Game) {
return gameType;
} else if (obj instanceof HidingSpot) {
return hidingSpotType;
} else {
return null;
}
}
);
On the first argument on nodeDefinition,where did it get its' globalId? is Game and HidingSpot a name on the GraphQLSchema? What does this 'const {type, id} = fromGlobalId(globalId);' do? and also what is the 2nd argument? I need help understanding nodeDefinitions, somehow I can't find nodeDefinitions on the official documentation. Thank you.
If you were writing a GraphQL server without Relay, you'd define a number of entry points on the Query type, eg:
type Query {
picture(id: Int!): Picture
user(id: Int!): User
...etc
}
So when you want to get a User, you can easily get it because user is available as an entry point into the graph. When you build a query for your page/screen, it'll typically be several levels deep, you might go user -> followers -> pictures.
Sometimes you want to be able to refetch only part of your query, perhaps you're paginating over a connection, or you've run a mutation. What Relay's Node interface does is give you a standard way to fetch any type that implements it via a globally unique ID. Relay is capable of recognising such nodes in its queries, and will use them if possible to make refetching and paginating more efficient. We add the node type to the root Query type:
type Query {
picture(id: Int!): Picture
user(id: Int!): User
...etc
node(id: ID!): Node
}
Now for nodeDefinitions. Essentially this function lets us define two things:
How to return an object given its globalId.
How to return a type given an object.
The first is used to take the ID argument of the node field and use it to resolve an object. The second allows your GraphQL server to work out which type of object was returned - this is necessary in order for us to be able to define fragments on specific types when querying node, so that we can actually get the data we want. Without this, we couldn't be able to successfully execute a query such as this:
query Test {
node(id: 'something') {
...fragment on Picture {
url
}
...fragment on User {
username
}
}
}
Relay uses global object identification, which means, in my understanding, if your application ever try to search for an object. In your example, try to look for a game, or try to look for a hidingSpot. Relay will try to fetches objects in the standard node interface. i.e. find by {id: 123} of the Game, or find by {id:abc} of the hidingSpot. If your schema (Game, HidingSpot) doesn't set up the node interface, Relay will not be able to fetch an object.
Therefore, if your application requires a search in a "Game", in the schema, you need to define the node interfaces.
By using graphql-relay helper, use nodeDefinitions function only once in your application to basically map globally defined Ids into actual data objects and their GraphQL types.
The first argument receives the globalId, we map the globalId into its corresponding data object. And the globalId can actually be used to read the type of the object using fromGlobalId function.
The second function receives the result object and Relay uses that to map an object to its GraphQL data type. So if the object is an instance of Game, it will return gameType, etc.
Hope it will help you understand. I am on my way learning, too.
I'm using ANTLR4 to create a parse tree for my grammar, what I want to do is modify certain nodes in the tree. This will include removing certain nodes and inserting new ones. The purpose behind this is optimization for the language I am writing. I have yet to find a solution to this problem. What would be the best way to go about this?
While there is currently no real support or tools for tree rewriting, it is very possible to do. It's not even that painful.
The ParseTreeListener or your MyBaseListener can be used with a ParseTreeWalker to walk your parse tree.
From here, you can remove nodes with ParserRuleContext.removeLastChild(), however when doing this, you have to watch out for ParseTreeWalker.walk:
public void walk(ParseTreeListener listener, ParseTree t) {
if ( t instanceof ErrorNode) {
listener.visitErrorNode((ErrorNode)t);
return;
}
else if ( t instanceof TerminalNode) {
listener.visitTerminal((TerminalNode)t);
return;
}
RuleNode r = (RuleNode)t;
enterRule(listener, r);
int n = r.getChildCount();
for (int i = 0; i<n; i++) {
walk(listener, r.getChild(i));
}
exitRule(listener, r);
}
You must replace removed nodes with something if the walker has visited parents of those nodes, I usually pick empty ParseRuleContext objects (this is because of the cached value of n in the method above). This prevents the ParseTreeWalker from throwing a NPE.
When adding nodes, make sure to set the mutable parent on the ParseRuleContext to the new parent. Also, because of the cached n in the method above, a good strategy is to detect where the changes need to be before you hit where you want your changes to go in the walk, so the ParseTreeWalker will walk over them in the same pass (other wise you might need multiple passes...)
Your pseudo code should look like this:
public void enterRewriteTarget(#NotNull MyParser.RewriteTargetContext ctx){
if(shouldRewrite(ctx)){
ArrayList<ParseTree> nodesReplaced = replaceNodes(ctx);
addChildTo(ctx, createNewParentFor(nodesReplaced));
}
}
I've used this method to write a transpiler that compiled a synchronous internal language into asynchronous javascript. It was pretty painful.
Another approach would be to write a ParseTreeVisitor that converts the tree back to a string. (This can be trivial in some cases, because you are only calling TerminalNode.getText() and concatenate in aggregateResult(..).)
You then add the modifications to this visitor so that the resulting string representation contains the modifications you try to achieve.
Then parse the string and you get a parse tree with the desired modifications.
This is certainly hackish in some ways, since you parse the string twice. On the other hand the solution does not rely on antlr implementation details.
I needed something similar for simple transformations. I ended up using a ParseTreeWalker and a custom ...BaseListener where I overwrote the enter... methods. Inside this method the ParserRuleContext.children is available and can be manipulated.
class MyListener extends ...BaseListener {
#Override
public void enter...(...Context ctx) {
super.enter...(ctx);
ctx.children.add(...);
}
}
new ParseTreeWalker().walk(new MyListener(), parseTree);
I have a pretty simple thing I want to accomplish but I cannot figure out how or if it is even possible. I am using the Hot Towel template to start with. In the shell viewmodel I have a user observable. I would like to be able to reference that user observable from other pages on my site. For example from the home page. I tried a couple of things but it doenst appear as though I can access the shell from the composed view. I have a working solution at the moment that uses event pub/sub calls from the shell to pass the user data to anyone listening whenever the data changes (home view in this example). This works it just seems a little clunky and not really the ideal way to handle this. This user observable will need to be used all throughout the site to determine when certain features should be available and to show a particular users projects.
Is there a way to data bind to a knockout observable contained in the shell viewmodel from the home view?
You might consider having a global.js that returns a singleton, which you include in view models as needed.
define(function () {
return {
sharedObservable: ko.observable(),
sharedObservableArray: ko.observableArray(),
...
};
});
Using global in a viewmodel.
define([..., global], function (..., global) {
...
global.sharedObservable('updated');
// As an alternative use a local var for easier access
// var localVar = global.sharedObservable;
// localVar('updated')
...
});
The easiest way is to import the shell module into your viewmodel with requirejs and then expose the shell viewmodel as a variable on the module's viewmodel.
Somehting like this:
// Some viewmodel
define(['shell'], function(shell){
var vm = {
shell: shell,
...
};
return vm;
});
Then in your view, you can bind using $root.shell
<span data-bind="text: $root.shell.shellObservable"></span>
Non of previous answers worked for me. Thus, I tried another variant of access to shell and it made the job done.
I've got this in my shell.js:
define(['plugins/router', 'durandal/app'], function (router, app) {
return {
// I need an access to this array in my view
breadcrumbs: ko.observableArray([]),
...
};
});
And this in my view:
define(function () {
var ctor = function () {
this.pageTitle = 'Preview';
this.activate = function () {
require('viewmodels/shell').breadcrumbs([...]);
};
};
return ctor;
});
So, require('viewmodels/shell') actually returns reference to a shell object.