Is there a way to let Apollo Client globally insert empty strings during loading? - graphql

I'm using Apollo Client to receive the GraphQL data for my application. Over time, I see a pattern emerging where for every value I'm querying, I have to include a conditional statement to handle the moment where my data is still loading.
Assume a query looks like this:
query TestQuery($userId: Int!) {
getUser(id: $userId) {
name
}
}
Then, in every place where I want to display the user name, I have to write something like:
{ !this.props.data.loading && this.props.data.getUser.name }
or
{ this.props.data.getUser && this.props.data.getUser.name }
I don't want to display "Loading..." or a rotating spinner in any of these places. Is there a way to avoid this conditional statement by globally replacing all this.props.data.x.y.z values with null or an empty String during loading?
If so, how? Would this be considered an antipattern or bad practice?
If not, which of the above two forms is preferred?
Thanks.

How about this approach?
class GraphqlComponent extends React.Component {
renderError(){
// ...
}
renderLoading(){
// ...
}
renderLoaded(){
}
render(){
const { loading, error } = this.props;
if(error){
return renderError();
}
if(loading){
return renderLoading();
}
return renderLoaded();
}
}
class MyComponent extends GraphqlComponent{
renderLoaded(){
// your logic goes here
}
}

Related

GraphQL using a dynamic ID from set of returned results to render correct set of data

I am pretty new to graphql, so my question might sound a bit strange. it may not be correct way of doing what I need, but I can't think of any other way.
Basically, I would like to know how I can use an ID i am being returned from my graphql query and use that ID in the second query to render the data I need.
currently my full query looks like:
export const query = graphql`
query myQuery {
allSanityFrontpage {
nodes {
content {
... on SanityHero {
_key
_type
heroIntro
heroHeader
heroButtonText
heroProjectReference {
id
title
slug {
current
}
}
}
}
}
}
sanityProject(id: { eq: "-8571b76b-8cfd-55cf-b848-151586f5c57a" }) {
id
title
coverImage {
asset {
title
fluid(maxWidth: 800) {
...GatsbySanityImageFluid
}
}
}
}
}
`;
I am getting an ID returned from this part of the query
heroProjectReference {
id
And currently I am using that ID statically to render the content I need by doing this:
sanityProject(id: { eq: "-8571b76b-8cfd-55cf-b848-151586f5c57a" }) {
id
title
How do I replace that static ID with something dynamic, so I can update my CMS and see whatever project I have updated it to be?
Client-side you must call the chained queries serially, populating the ID in the dependent query with the output from the first query.
Server-side, of course, you can combine the two in the resolver.

Reusing GraphQL fragments

I am using Prismic, and I have two identical custom types, one is called Content and one is called Theme. Their data is identical so I would like to reuse my fragments, is it possible?
An example fragment looks like:
import { graphql } from 'gatsby'
export const CollectionFragment = graphql`
fragment CollectionFragment on PrismicContentBodyCollection {
...
}
`
So right now it is hardcoded to PrismicContentBodyCollection.
A GraphiQL example would look like:
query MyQuery {
allPrismicTheme {
nodes {
data {
body {
... on PrismicThemeBodyHero {
slice_type
}
}
}
}
}
allPrismicContent {
nodes {
data {
body {
... on PrismicContentBodyHero {
slice_type
}
}
}
}
}
}
I'm not 100% sure, but I don't think this is possible because it needs to be specified with the type that matches the type of document you're looking for to make sure that your query is valid and that the fields you are trying to receive actually exist on the object.
So in your case, if you're looking for the Collection Slice, the fragments would need to be
PrismicThemeBodyCollection and PrismicContentBodyCollection respectively.
I have made a few tests myself and I keep getting errors that say I'm missing the correct content type name

GraphQL queries for Gatsby - Contentful setup with a flexible content model

I have a gatsby site with the contentful plugin and graphql queries (setup is working).
[EDIT]
My gatsby setup pulls data dynamically using the pageCreate feature. And populates my template component, the root graphql query of which I've shared below. I can create multiple pages using the setup if the pages on contentful follow the structure given in the below query.
[/EDIT]
My question is about a limitation I seemed to have come across or just don't know enough grpahql to understand this yet.
My high level content model 'BasicPageLayout' consists of references to other content types through the field 'Section'. So, it's flexible in terms of which content types are contained in the 'BasicPageLayout' and the order in which they are added.
Root page query
export const pageQuery = graphql`
query basicPageQuery {
contentfulBasicPageLayout(pageName: {eq: "Home"}) {
heroSection {
parent {
id
}
...HeroFields
}
section1 {
parent {
id
}
...ContentText
}
section2 {
parent {
id
}
...ContentTextOverMedia
}
section3 {
parent {
id
}
...ContentTextAndImage
}
section4 {
parent {
id
}
...ContentText
}
}
}
The content type fragments all live in the respecitve UI components.
The above query and setup are working.
Now, I have "Home" Hard coded because I'm having trouble creating a flexible reusable query. I'm taking advantage of contentful's flexible nature when creating the models, but haven't found a way to create that flexibility in the graphql query for it.
What I do know:
Graphql query is resolved at run time, so everything that needs to be fetched should be in that query. It can't be 'dynamic'.
Issue: The 'Section' fields in the basicPageLayout can link to any content type. So we can mix and match the granular level content types. How do I add the content type fragment (like ContentTextAndImage vs ContentText) so it is appropriate for that section instance ('Section' field in the query)?
In other words
I'd like the root query to get 'Home' data which might have 4 sections, all of type - ContentTextOverMedia
as well as 'About ' data that might have also have 4 sections but with alternating types - ContentText and ContentTextAndImage
This is the goal because I want to create content (Pages) by mix-matching content types on contentful, without needing to update the code each time a new Page is created. Which is why Contentful is useful and was picked in the first place.
My ideas so far:
A. Run two queries, in series. One fetches the parent.id on each section and that holds the content type info. Second fetches the data using the appropriate fragment.
B. Fetch the JSON file of the basicPageLayouts content instance (such as 'Home') separately through Contentful API, and using that JSON file create the graphql string to be used in each instance (So, different layout for Home, About, and so on)
This needs more experimentation, not sure if it's viable, could also be more complex then it needs to be.
So, please share thoughts on the above paths that I'm exploring or another solution that I haven't considered using graphql or gatsby's features.
This is my first question on SO btw, I've spent some time on refining it and trying to follow the guidelines but please do give me feedback in comments so I can improve even if you don't have an answer to my question.
Thanks in advance.
If I understood correctly you want to create pages dynamically from the data coming from Contentful.
You can achieve this using the Gatsbyjs Node API specifically createPage.
In your gatsby-node.js file you can have something like this
const fs = require('fs-extra')
const path = require('path')
exports.createPages = ({graphql, boundActionCreators}) => {
const {createPage} = boundActionCreators
return new Promise((resolve, reject) => {
const landingPageTemplate = path.resolve('src/templates/landing-page.js')
resolve(
graphql(`
{
allContentfulBesicPageLayout {
edges {
node {
pageName
}
}
}
}
`).then((result) => {
if (result.errors) {
reject(result.errors)
}
result.data.allContentfulBesicPageLayout.edges.forEach((edge) => {
createPage ({
path: `${edge.node.pageName}`,
component: landingPageTemplate,
context: {
slug: edge.node.pageName // this will passed to each page gatsby create
}
})
})
return
})
)
})
}
Now in your src/templates/landing-page.js
import React, { Component } from 'react'
const LandingPage = ({data}) => {
return (<div>Add you html here</div>)
}
export const pageQuery = graphql`
query basicPageQuery($pageName: String!) {
contentfulBasicPageLayout(pageName: {eq: $pageName}) {
heroSection {
parent {
id
}
...HeroFields
}
section1 {
parent {
id
}
...ContentText
}
section2 {
parent {
id
}
...ContentTextOverMedia
}
section3 {
parent {
id
}
...ContentTextAndImage
}
section4 {
parent {
id
}
...ContentText
}
}
}
note the $pageName param that's what was passed to the component context when creating a page.
This way you will end up creating as many pages as you want.
Please note: the react part of the code was not tested but I hope you get the idea.
Update:
To have a flexible query you instead of having your content Types as single ref field, you can have one field called sections and you can add the section you want there in the order you desire.
Your query will look like this
export const pageQuery = graphql`
query basicPageQuery($pageName: String!) {
contentfulBasicPageLayout(pageName: {eq: $pageName}) {
sections {
... on ContentfulHeroFields {
internal {
type
}
}
}
}
Khaled

How do I create a generic reducer plugin, that fires on all forms?

Currently my code works, but only for the forms specified specifically in the combine reducer function. But, I would like to have my code work generally for all forms loaded in my single page app.
Here is the relevant code:
import { reducer as formReducer } from 'redux-form';
export default combineReducers({
someReducer,
anotherReducer,
form: formReducer.plugin({
specificFormId: (state, action) => { // <-- I don't want this only for specificFormId, I want this to happen for all my forms,
// or at least have a dynamic way of adding more forms
const {type, payload} = action;
switch(type) {
case 'RESET_LINK_TYPE_FIELDS': {
return {
...state,
registeredFields: {
...state.registeredFields,
// Do some custom restting here based on payload
}
};
}
default:
return state;
}
}
})
});
So, anytime my <Field ..of a certain type/> fires off this the RESET_LINK_TYPE_FIELDS action, I want the correct form to respond to it.
In the action payload, I can specifically the form identifier or anything else I would need to make this work.
In fact, if the .plugin let me do my own form state slicing, I could easily do this, but because it forces me to pass an object, with a hardcoded form identifier it doesn't work.
Is there a way to have the plugin give me the WHOLE form state, and then I will slice as needed, and return state as needed based on payload?
There is currently no way to do this with the existing API.
You could jury rig a solution by wrapping the redux-form reducer in your own thing.
export default combineReducers({
someReducer,
anotherReducer,
form: resetHack(formReducer)
})
function resetHack(formReducer) {
return (state, action) => {
if(action.RESET_LINK_TYPE_FIELDS) {
// manipulate slice somehow
} else {
return formReducer(state, action)
}
}
}

Performance issue with geb lookup on module object attributes vs. using selectors

Problem Description
I am writing a geb/spock spec which fetches test data from DB2 into a map (the map variable is called "preFilledFields" - see the "MySpec" class further down).
This map is then being iterated over, and for each iteration I check too see if the value matches one in a row on the page.
When I perform the assertion above accessing the module object attributes, then the average execution time per assertion is approx. 5-6 seconds. If I perform the assertion using selectors directly, then the average execution time per assertion is approx. 70-80 ms. See the "MyPage" class for more details regarding the assertions.
Does anyone know what could be the cause of this? Is the bad performance a result of my code, or is there a general problem with regards to performance when using modules in geb?
Appreciate any help and input I can get.
Code:
My "RowModule" class looks like this:
class RowModule extends Module {
static final PREDEFINED_ATTR = "data-predefined-amount"
static content = {
cell { $("td", it) }
description { cell(0).text() }
rubrikNum { cell(1).text().toInteger() }
preDefinedAmount { cell(0).parent("tr").$("td[$PREDEFINED_ATTR]").attr("$PREDEFINED_ATTR") }
inputField { cell(0).parent("tr").$("td input") ?: false }
dataType{ cell(0).parent("tr").attr("data-type") }
}
}
My Page class looks like this:
class MyPage extends Page {
static url = "<some_url>"
static at = { $("h1").text() == "<some_text>" }
static content = {
submitButton { $("input", name:"<some_name>") }
myPageItems {
$("table tr").collect { it.module(RowModule) }
}
}
void verifyPrePopulatedFields(name, amount) {
long now = System.currentTimeMillis();
assert amount == selvangivelseItems.find { it.dataType== name}.preDefinedAmount.toInteger()
//assert amount == $("tr[data-type='" + name+ "']").$(".skts-tooltip-holder").text().toInteger()
println "Execution time" + (System.currentTimeMillis() - now) + " ms"
}
void submit() { submitTaxReturnButton.click() }
}
My Spec file looks like this:
class MySpec extends GebReportingSpec {
#Unroll
def "field #name is pre-populated with amount #amount from the database"() {
expect:
page(MyPage) verifyPrePopulatedFields(name, amount)
where:
name << preFilledFields.keySet()
amount << preFilledFields.values()
}
}
There are no general performance problems with using modules in Geb, at least none that I know of. Your selectors on the other hand are definitely suboptimal.
Firstly by doing myPageItems.find { it.dataType == name } you are iterating over all rows in your table and executing 3 WebDriver commands (that is http request between your test and the browser that is being driven) for each of them. You could improve the selector for dataType to dataType { attr("data-type") } (not 100% sure here because I don't see your DOM structure but this is what logic would suggest) but it would still mean potentially making a lot of requests. You should instead add a site content definition like this:
myItem { dataType ->
$("table tr[data-type='$dataType']").module(RowModule)
}
And then use it like:
assert amount == myPageItem(name).preDefinedAmount.toInteger()
Secondly you can simplify and improve performance of your selectors in the module (if my assumptions about your DOM are correct):
static content = {
cell { $("td", it) }
description { cell(0).text() }
rubrikNum { cell(1).text().toInteger() }
preDefinedAmount { $("td[$PREDEFINED_ATTR]").attr("$PREDEFINED_ATTR") }
inputField { $("td input") ?: false }
dataType{ attr("data-type") }
}
You should avoid using multiple selectors for things that can be found using a single selector or using unnecessary selectors because they will always carry a performance penalty.

Resources