There is a component UserListWidget with following container definition:
fragments: {
users: () => Relay.QL`
fragment on User #relay(plural: true) {
id
displayName
}
`
}
There is also parent component that needs to render connection of users as plain list. But following won't work:
<UserListWidget users={userConnection.edges.map(edge => edge.node)} />
It fails with error:
Invariant Violation: RelayContainer: Invalid prop `users` supplied to `UserListWidget`, expected element at index 0 to have query data.
Changing UserListWidget to accept userConnection instead of plain list of users is not an option, because it is a generic component and other parent components will only provide lists of users, not connections.
So I guess there should be a way to treat connection as plain list of nodes somehow?
This error can occur if the parent component doesn't include the child fragment. Does the parent include userConnection(...) { edges { node { ${UserListWidget.getFragment('users')} } } }?
Related
I have the next query:
const foundDeal: any = await dealRepository.findOne({
where: { id: dealId },
relations: ['negotiationPointsDeals', 'chosenInventoryToSubtractQuantity',
'chosenInventoryToSubtractQuantity.inventoryItemType',
'chosenInventoryToSubtractQuantity.inventoryItemType.quality', 'negotiationPointsDeals.negotiationPointsTemplate',
'chosenInventoryToSubtractQuantity.addressOfOriginId', 'chosenInventoryToSubtractQuantity.currentLocationAddress',
'chosenInventoryToSubtractQuantity.labAttestationDocs',
'chosenInventoryToSubtractQuantity.labAttestationDocs.storage',
'chosenInventoryToSubtractQuantity.proveDocuments', 'chosenInventoryToSubtractQuantity.proveDocuments.storage',
'chosenInventoryToSubtractQuantity.inventoryItemSavedFields', 'chosenInventoryToSubtractQuantity.inventoryItemSavedFields.proveDocuments',
'chosenInventoryToSubtractQuantity.inventoryItemSavedFields.proveDocuments.storage',
'sellerBroker', 'sellerBroker.users',
'seller', 'seller.users',
'buyerBroker', 'buyerBroker.users',
'buyer', 'buyer.users',
'order', 'order.inventory', 'order.inventory.inventoryItemType',
'order.inventory.inventoryItemType.quality',
'order.inventory.addressOfOriginId', 'order.inventory.currentLocationAddress',
'order.inventory.inventoryItemSavedFields', 'order.inventory.inventoryItemSavedFields.proveDocuments',
'order.inventory.inventoryItemSavedFields.proveDocuments.storage',
'order.inventory.labAttestationDocs', 'order.inventory.labAttestationDocs.storage',
// 'postTradeProcessingDeal', 'postTradeProcessingDeal.postTradeProcessingStepsDeal',
'order.inventory.proveDocuments',
'order.inventory.proveDocuments.storage',
'negotiationPointsDeals.negotiationPointsTemplate.negotiationPointsTemplateChoices',
'postTradeProcessing',
],
});
So, the error is next:
error: table name "Deal__chosenInventoryToSubtractQuantity_Deal__chosenInventoryTo" specified more than once.
But I can't see any doubles in query.
I ran into this issue when switching to start using the snake case naming strategy.
I think somehow the aliases that TypeORM generates by default do not collide if you "re-join" to existing eagerly-loaded relations.
However, under the new naming strategy it threw an error if I tried to add in a relation that was already eagerly loaded.
The solution for me was to find and eliminate places where I was doing relations: ["foo"] in a query where foo was already eagerly loaded by the entity.
The issue is documented in this TypeORM issue.
After some digging, I realized this error is due to TypeORM creating some kind of variable when using eager loading that is longer than Postgres limit for names.
For example, if you are eager loading products with customer, typeorm will create something along the lines of customer_products, connecting the two. If that name is longer than 63 bytes (Postgres limit) the query will crash.
Basically, it happens when your variable names are too long or there's too much nesting. Make your entity names shorter and it will work. Otherwise, you could join the tables manually using queryBuilder and assign aliases for them.
It looks like you are using Nestjs, typeorm, and the snakeNamingStrategy as well, so I'll show how I fixed this with my system. I use the SnakeNamingStrategy for Typeorm which might be creating more issues as well. Instead of removing it, I extended it and wrote an overwriting function for eager-loaded aliases.
Here is my solution:
// short-snake-naming.strategy.ts
import { SnakeNamingStrategy } from "typeorm-naming-strategies";
import { NamingStrategyInterface } from "typeorm";
export class ShortSnakeNamingStrategy
extends SnakeNamingStrategy
implements NamingStrategyInterface
{
eagerJoinRelationAlias(alias: string, propertyPath: string): string {
return `${alias.replace(
/[a-zA-Z]+(_[a-zA-Z]+)*/g,
(w) => `${w[0]}_`
)}_${propertyPath}`;
}
}
// read-database.configuration.ts
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from "#nestjs/typeorm";
import { SnakeNamingStrategy } from "typeorm-naming-strategies";
import { ShortSnakeNamingStrategy } from "./short-snake-naming.strategy";
export class ReadDatabaseConfiguration implements TypeOrmOptionsFactory {
createTypeOrmOptions(): TypeOrmModuleOptions | Promise<TypeOrmModuleOptions> {
return {
name: "read",
type: "postgres",
...
namingStrategy: new ShortSnakeNamingStrategy(),
};
}
}
The ShortSnakeNamingStrategy Class takes each eager-loaded relationship and shortens its name from Product__change_log___auth_user___roles__permissions to P_____c____a___r__permissions
So far this has generated no collisions and kept it below the 63 character max index length.
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.
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 have the following (simplified) Entity SQL query:
SELECT VALUE a
FROM Customers AS a
WHERE a.Status NOT IN { 2, 3 }
The Status property is an enumeration type, call it CustomerStatus. The enumeration is defined in the EDMX file.
As it is, this query doesn't work, throwing an exception to the effect that CustomerStatus is incompatible with Int32 (its underlying type is int). However, I couldn't find a way to define a list of CustomerStatus values for the IN {} clause, no matter what namespace I prefixed to the enumeration name. For example,
SELECT VALUE a
FROM Customers AS a
WHERE a.Status NOT IN { MyModelEntities.CustomerStatus.Reject, MyModelEntities.CustomerStatus.Accept }
did not work, throwing an exception saying it could not find MyModelEntities.CustomerStatus in the container, or some such.
Eventually I resorted to casting the Status to int, such as
SELECT VALUE a
FROM Customers AS a
WHERE CAST(a.Status AS System.Int32) NOT IN { 2, 3 }
but I was hoping for a more elegant solution.
Oooh, you are writing Entity SQL directly. I see... Any reason you aren't using DbSet instead of writing Entity SQL by hand? Could always do
var statuses = new [] { Status.A, Status.B };
var query = context.SomeTable.Where(a => !statuses.Contains(a.Status)).ToList();
I'm using useShadowDom: false with my components in an attempt to support more browsers without having to use the troublesome web_components polyfill. With the shadow DOM enabled, I would do something like this:
void onShadowRoot(ShadowRoot root) {
root.querySelector('.btn-go-back').onClick.listen((e) {
if (goBackHandler != null) {
goBackHandler();
}
});
}
onShadowRoot would run after my component's template was loaded and therefore all the components elements exist in the DOM. Without Shadow DOM enabled, I inject the component's root element in the constructor, and do something like this:
MyComponent(this._root){
_root.querySelector('.btn-go-back').onClick.listen((e) {
if (goBackHandler != null) {
goBackHandler();
}
});
}
This doesn't work because the component's template hasn't been loaded into the DOM yet, so the root element doesn't have any children to query yet.
I've tried implementing AttachAware, and querying the root element in the attach() method, and the template isn't loaded at that point either.
So, if I'm not using the shadow DOM, how can I know when the template has been loaded into the DOM so I can query elements within my component?
Edit
Attempting to use ShadowRootAware and onShadowRoot with useShadowRoot: false will result in the following error if you try to query against the provided ShadowRoot object:
Unsupported operation: Not supported
STACKTRACE:
#0 EmulatedShadowRoot._notSupported (package:angular/core_dom/emulated_shadow_root.dart:5:21)
#1 EmulatedShadowRoot.querySelector (package:angular/core_dom/emulated_shadow_root.dart:32:63)
I also tried a combination of:
Injecting the root element in the constructor and
querying against the root element within onShadowRoot which worked, kinda, but now I'm seeing this in the console output:
[WebPlatformShim] WARNING: Failed to set up Shadow DOM shim for [find-result].
InvalidCharacterError: The string contains invalid characters. '[find-result]' is not a valid attribute name.
So, for some reason, even with useShadowDom set to false on all my Components, it's still attempting to use the ShadowDom shim. I'm assuming this is because it is because I've implemented ShadowRootAware which constructs an EmulatedShadowRoot. So, I think I need a solution that avoids onShadowRoot
You can query the template of a emulated component as follows:
class Component implements ShadowRootAware {
Element el;
Component(this.el);
void onShadowRoot(_) {
this.el.querySelector('.blah');
}
}
The error message:
[WebPlatformShim] WARNING: Failed to set up Shadow DOM shim for [find-result].
is caused by the css shim. This is one of the limitations of the shim (you can use only element selectors).
You can disable the css shim. Then you will not see the error, but you won't have CSS encapsulation.
See more here:
https://github.com/angular/angular.dart/wiki/CSS-Shim
We schedule child elements querying on the next event loop iteration. I'm not aware about particular internal implementation details, but it reliably works fine for us:
MyComponent(Element root) {
// Schedule child elements querying on the next event loop iteration when
// AngularDart will render the child DOM.
new Future(() {
root.querySelector('.btn-go-back').onClick.listen((e) {
if (goBackHandler != null) {
goBackHandler();
}
});
});
}
You can also use onShadowRoot even with useShadowDom: false. However the parameter provided is not a ShadowRoot object.