Relay. Accessing data of sibling component - relayjs

Playing with Relay I got problems with accessing data. I was trying to reproduce the issues with official Todo example of the Relay project. Please consider gist in order to change Todo example.
here
Here are the questions:
Why Summary component cant get access to sibling (viewer) component data?
What the reason for "queries must have exactly one field"? GraphQL doesn't have such limitations I believe.
Why I got Invariant Violation: Relay(TodoApp).getFragment(): summary is not a valid fragment name ?
Thanks in advance!

Whatever data Summary wants to use from viewer has to be delclared as GraphQL in the Summary container, and composed all the way down to the root of the application.
class Summary extends React.Component {
render() {
return <span>{this.props.viewer.bar}</span>;
}
}
export default Relay.createContainer(Summary, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
bar
}
`,
},
});
class TodoApp extends React.Component {
render() {
return <Summary viewer={this.props.viewer} />;
}
}
export default Relay.createContainer(TodoApp, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
foo
${Summary.getFragment('viewer')}
}
`,
},
});
Note that the foo field will not be available inside Summary. We mask it out since Summary didn't ask for it.
You can have multiple queries in a Relay.Route, but only one root field per query. We need this one-to-one correspondence so that we know which result to assign to which prop.
class MyRoute extends Relay.Route {
queries: {
summary: () => Relay.QL`query { summary }`,
viewer: () => Relay.QL`query { viewer }`,
},
/* ... */
}
You need to compose the summary fragment all the way down to the root of the application, making it available on TodoApp.
export default Relay.createContainer(TodoApp, {
fragments: {
summary: () => Relay.QL`
fragment on Summary {
${Summary.getFragment('summary')}
}
`,
},
});

Related

NestJS / CrudController Swagger as GetManyBase

how do I decorate a custom method inside my CrudController so that the Swagger documentation would be shown as the one from getManyBase? Meaning I need to have all of the filter fields.
I tried this way
#Get('/projects')
#UseInterceptors(CrudRequestInterceptor)
#ApiResponse({ status: 200, type: Project, isArray: true })
getManyProjects(#ParsedRequest() req: CrudRequest, #Request() request)
: Promise<GetManyDefaultResponse<Project> | Project[]> {
const { id, role } = request.user;
if (role === UserRoles.User) {
req.parsed.filter.push({
field: 'userId',
operator: 'eq',
value: id,
});
}
return this.projectService.getMany(req);
}
but the Swagger docs shows empty for the query parameters,
while I'm expecting something like getManyBase.
Funny thing is, the method would work properly if I send the filter string, but I need Swagger to display them as well.
Advice?
See this area in the nestjsx/crud repo.
If you add something like this to your constructor that should do it:
import { Swagger } from '#nestjsx/crud/lib/crud';
...
constructor() {
const metadata = Swagger.getParams(this.getManyProjects);
const queryParamsMeta = Swagger.createQueryParamsMeta('getManyBase');
Swagger.setParams([...metadata, ...queryParamsMeta], this.getManyProjects);
}
In my version "#nestjsx/crud": "^5.0.0-alpha.3"
import { Swagger } from '#nestjsx/crud/lib/crud';
...
constructor() {
const metadata = Swagger.getParams(this.getManyProjects);
const queryParamsMeta = Swagger.createQueryParamsMeta('getManyBase',{
model: { type: MyModel },
query: {
softDelete: false,
},
});
Swagger.setParams([...metadata, ...queryParamsMeta], this.getManyProjects);
}
If the constructor approach does not work for you. Then probably your controller has scope: REQUEST. So controller instance is not created while application initialisation. In this case, you can have custom method inside a controller, like
initSwagger() {
const metadata = Swagger.getParams(this.getManyProjects);
const queryParamsMeta = Swagger.createQueryParamsMeta('getManyBase',{
model: { type: MyModel },
query: {
softDelete: false,
},
});
Swagger.setParams([...metadata, ...queryParamsMeta], this.getManyProjects);
}
then in your main entrypoint file you can write:
app.get(YourController).initSwagger();
It will do the trick

Pass variables to fragment container in relay modern

I'm using Relay Modern (compat). I have a fragment that contains a field that has one argument, but I can't find a way of passing the variable value from the parent component:
// MyFragmentComponent.jsx
class MyFragmentComponent extends Component {...}
const fragments = {
employee: graphql`
fragment MyFragmentComponent_employee on Employee {
hoursWorked(includeOvertime: $includeOvertime)
dob
fullName
id
}
`,
}
export default Relay.createFragmentContainer(MyFragmentComponent, fragments)
It will end up saying $includeOvertime is not defined. The context where this component is being rendered looks like this:
// MyRootComponent.jsx
class MyRootComponent extends Component {
render() {
const { employee } = this.props
const includeOvertime = //... value is available here
return (
<div>
<MyFragmentComponent employee={employee} />
</div>
)
}
}
const query = graphql`
query MyRootComponentQuery($employeeId: String!) {
employee(id: $employeeId) {
fullName
...MyFragmentComponent_employee
}
}
`
export default MyUtils.createQueryRenderer(MyRootComponent, query) // this just returns a QueryRenderer
With relay classic you would pass variables this way:
....
employee(id: $employeeId) {
fullName
${MyFragmentComponent.getFragment('employee', variables)}
}
How can I achieve the same with relay modern?
Using #argumentDefinitions and #arguments directives seems to be the way to go. In relay versions before 1.4.0 graphql.experimental had to be used instead of graphql.
In the fragment definition:
const fragments = {
employee: graphql`
fragment MyFragmentComponent_employee on Employee
#argumentDefinitions(includeOvertime: { type: "Boolean", defaultValue: false }) {
hoursWorked(includeOvertime: $includeOvertime)
dob
fullName
id
}
`,
}
If you want the argument to be required:
#argumentDefinitions(includeOvertime: { type: "Boolean!" })
In the parent component you should specify the arguments for the fragment like this:
const query = graphql`
query MyRootComponentQuery($employeeId: String!, $includeOvertime: Boolean) {
employee(id: $employeeId) {
fullName
...MyFragmentComponent_employee #arguments(includeOvertime: $includeOvertime)
}
}
`
In this page in the official relay docs there is an example of directives for defining/passing arguments.
UPDATE:
Since relay version 1.4.0 graphql.experimental was deprecated and now all the features are supported by the regular graphql tag.
UPDATE:
In relay version 1.5.0 graphql.experimental was removed.

How access “provided / injected” values from a mixin in VueJS?

I'm trying to access provided / injected values from a mixin in VueJS. I can see those values from any component but not from the mixin. Is what I'm attempting possible?
https://jsfiddle.net/frang/c6c7kqhp/2/1
let myMixin = {
inject: ['myDependency'],
created: function() {
console.log('in mixin', this.myDependency)
}
}
Vue.component('my-component', {
inject: ['myDependency'],
created: function() {
console.log('in component', this.myDependency)
}
})
new Vue({
el: '#example',
provide() {
return {
myDependency: 'here is my value'
}
},
mixins: [myMixin]
})
The issue is that you are trying to inject the myDependency property in the same Vue instance that you are providing it.
Vue instances that specify a provide property give their child components access to that value via inject. But, you cannot inject the provided value on the same instance.
You need to use the mixin in a child component:
Vue.component('my-component', {
mixin: ['myMixin'],
created: function() {
console.log('in component', this.myDependency)
}
})
Here's a working fiddle.

What is preferred way to pass data dictionaries using relay

what is preferred way to pass data dictionaries using relay,
for example I have in interface
UsersList = [
{
userName
// each user has select control
CountrySelectControl {
value = Country
options = [All Countries List]
}
]
What is the right way to read All Countries List?
As I understand it's not a good idea to query graphQl like this
{ users { userName, country, countriesList } }
So the only way I see I need to query countriesList at root, and pass it via props manually to every children component?
class Blabla extends Relay.Route {
static queries = {
users: (Component) => Relay.QL`
query UsersQuery {
users { ${Component.getFragment('user')} },
}
`,
countriesList: (Component) => Relay.QL`
query countriesListQuery {
countriesList { ${Component.getFragment('countriesList')} },
}
`,
...
}
And if i have a lot of dictionaries and some more deep UI structure this becomes a pain.
Or I can somehow to pass root data deeper in the tree without explicitly write this data in props. (I mean without context)
Yes, you can pass root data deeper in tree without explicitly writing countryList as props.
Suppose, we have data of a continent and the countries that belongs to it. We have nested UI components. For example, ContinentComponent includes a CountryListComponent, which needs a list of countries. A CountryListComponent consists of a number of CountryComponent, which needs a list of states. Instead of having ContinentComponent pass country list and state list all the way down to CountryListComponent and CountryComponent, we can utilize high-level prop.
We can specify the high-level prop continent in the high-level component ContinentComponent as follows:
export default Relay.createContainer(ContinentComponent, {
fragments: {
continent: () => Relay.QL`
fragment on Continent {
${CountryListComponent.getFragment('continent')},
}
`,
},
});
Instead of country list prop, only the prop continent is passed to CountryListComponent from the ContinentComponent.
Next, we specify the necessary props in the CountryListComponent:
export default Relay.createContainer(CountryListComponent, {
fragments: {
continent: () => Relay.QL`
fragment on Continent {
countryList(first: 100) {
edges {
node {
id,
name,
},
${CountryComponent.getFragment('country')},
},
},
}
`,
},
});
Now, CountryListComponent passes a specific prop value this.props.continent.countryList.edges[index].node to CountryComponent.
This use case is one of the primary motivations of Relay.js.

Two issues with field validation

I am working on my first ASP.NET MVC 3 project. For some of the fields on an edit page I want the user to be able to either choose a value from previously-entered values or enter a new one, so I'm using the jQuery autocomplete to accomplish this. That part seems to work just fine. Now, for some fields the user can choose to enter a value or not and if they do enter one, I want to validate it against some rules, so I created my own ValidationAttribute.
The validation piece will definitely check the given value against the rules and return the correct boolean value for the IsValid call. Great.
The first problem that I'm having is that if my validator's IsValid returns false, it displays the error message I've specified but if I enter something which is valid, the TextBox clears it's error background color but the error message does not clear. This happens in either FF or IE8.
The second problem is that for FireFox, my autocomplete values will display again when I edit the text in the textbox but in IE 8, once the error condition exists, my autocomplete stops working. Occasionally, if I enter a value which I know is in the autocomplete list, it will show up but it seems a bit flaky.
I may just be doing this validation thing wrong. Here's the relevant code I use. I'd be quite interested in any guidance one can give about either of these issues. The attribute is a test one but it exhibits the behavior on my page.
My ValidationAttribute:
public class MyAttribute : ValidationAttribute
{
...
public override bool IsValid(object value)
{
if (value == null)
{
return true;
}
var stringValue = Convert.ToString(value);
if (stringValue.Length == 0)
{
return true;
}
if (stringValue.Trim().Length == 0)
{
return false;
}
return true;
}
...
}
My autocomplete code:
$("#toppingid").autocomplete({
source: function (request, response) {
$.ajax({
url: '#Url.Action("AvailableToppings", "IceCream")', type: "POST", dataType: "json",
data: { query: request.term },
success: function (data) {
response($.map(data, function (item) {
return { label: item, value: item };
}))
}
})
},
minLength: 1
});
My controller action:
public JsonResult AvailableToppings()
{
// I actually retrieve this differently, but you get the idea
List<string> all = new List<string>() { "BerryCrush", "Caramel", "Fudge" };
return Json(all, JsonRequestBehavior.AllowGet);
}
My view snippet:
#Html.TextBoxFor(model => model.Topping, new { #id = "toppingid" })
#Html.ValidationMessageFor(model => model.Topping)
My viewmodel snippet:
[DisplayName("Topping:")]
[MyAttribute(ErrorMessage = "Can't be all blanks!")]
public string Topping { get; set; }
In my post action, I have logic something like this:
[HttpPost]
public ActionResult Create(IceCreamCreateEditViewModel viewModel)
{
if (ModelState.IsValid)
{
// stuff happens here which isn't germane
return RedirectToAction("Index");
}
// redisplay the view to the user
return Create();
}
I think that's all the relevant pieces of code. Thanks for any guidance you can provide.
Concerning your first question, it looks like the autocomplete plugin removes the input-validation-error class from the textbox when a selection is made. Because of this the textbox clears its background. One way to workaround this is to subscribe for the select event and reapply this class if there is an error (by checking whether the error label is displayed):
$("#toppingid").autocomplete({
source: function (request, response) {
...
},
select: function (event, ui) {
var topping = $('#toppingid');
// find the corresponding error label
var errorLabel = $('span[data-valmsg-for="' + topping.attr('name') + '"]');
if (errorLabel.is(':visible')) {
// if the error label is visible reapply the CSS class
// to the textbox
topping.addClass('input-validation-error');
}
},
minLength: 1
});
As far as your second question is concerned, unfortunately I am unable to reproduce it. The autocomplete doesn't stop working in IE8 if there is a validation error.

Resources