relay fragment spread not working - reactjs

I'm in the learning process of relay and facing a very wired issue. Relay is not returning the data from network response if I use fragment spread operator (actual data is returning from graphql, confirmed from the network tab). But if I define the field requirements in the query itself, it returns data.
This is index.js of the app:
import React from 'react'
import ReactDOM from 'react-dom'
import {
graphql,
QueryRenderer
} from 'react-relay'
import environment from './relay/environment'
import AllTodo from './components/AllTodo'
const query = graphql`
query frontendQuery {
...AllTodo_todos
}
`
ReactDOM.render(
<QueryRenderer
environment={environment}
query={query}
render={({ error, props }) => {
if (error) return <div>{error}</div>
else if (props) {
console.log(props)
return <AllTodo { ...props } />
}
else return <div>loading...</div>
}}
/>,
document.getElementById('root')
)
AllTodo component:
import React, { Component } from 'react'
import { graphql, createFragmentContainer } from 'react-relay'
class AllTodo extends Component {
render() {
return (
<div>
{ this.props.todos.map(todo => {
<div>{ todo.id } { todo.description }</div>
}) }
</div>
)
}
}
export default createFragmentContainer(AllTodo, graphql`
fragment AllTodo_todos on RootQueryType {
allTodos {
id
description
complete
}
}
`);
Relay environment:
import {
Environment,
Network,
RecordSource,
Store,
} from 'relay-runtime'
import { BACKEND_URL } from '../../constants'
// a function that fetches the results of an operation (query/mutation/etc)
// and returns its results as a Promise:
function fetchQuery(
operation,
variables,
cacheConfig,
uploadables,
) {
return fetch(BACKEND_URL + '/graphql', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json();
});
}
// a network layer from the fetch function
const network = Network.create(fetchQuery);
// export the environment
export default new Environment({
network: network,
store: new Store(new RecordSource())
})
The graphql schema:
schema {
query: RootQueryType
mutation: RootMutationType
}
type RootMutationType {
# Create a new todo item
createTodo(description: String): Todo
# Update a todo item
updateTodo(id: String, description: String, complete: Boolean): Todo
# Delete a single todo item
deleteTodo(id: String): Todo
}
type RootQueryType {
# List of all todo items
allTodos: [Todo]
# A single todo item
todo(id: String): Todo
}
# A single todo item
type Todo {
id: String
description: String
complete: Boolean
}
This is the response I'm getting while console.log(props) on index.js:
Please help me to understand what I'm missing here. Thanks in advance.

I'm having the exact same problem. Basically, Relay doesn't know how to deal with queries spreading fragments on the root.
That said, you could try to refactor your query to
query frontendQuery {
allTodos {
...AllTodo_todos
}
}
and redefine your fragment container to
export default createFragmentContainer(AllTodo, {
todos: graphql`
fragment AllTodo_todos on Todo {
id
description
complete
}
`
});
In my case it's even a little bit more complicated because I'm using a refetch container and the only solution I've found so far is to put my field under another root field; the old and trusty viewer
EDIT: I found a way to avoid moving stuff under viewer. Basically you pass all the data from the QueryRenderer as a prop for the corresponding container. To have an idea see: https://github.com/facebook/relay/issues/1937

Related

Show ApolloClient mutation result

This is my first time using Apollo and React so I'll try my best.
I have a GraphQl API from which I consume some data through ApolloClient mutations. The problem is that I don't know how to show the resulting information outside of the .result. I've tried to do so with a class that has a function to consume some data and a render to show it.
The mutation works and shows the data on the console but the page remains blank when the page is loaded, so the problem I've been stuck on is, how do I show this data?
Btw, if there's any advice on how to insert data from a form using this same mutation method I'd pretty much appreciate it.
Thanks in advance.
import React, { useEffect, useState, Component } from 'react';
import { graphql } from 'react-apollo';
import './modalSignUp.css';
import{header} from './Header.js';
import ReactDOM from "react-dom";
import { ApolloProvider, Query, mutation } from "react-apollo";
import { ApolloClient, InMemoryCache, gql, useMutation } from '#apollo/client';
export const client = new ApolloClient({
uri: 'http://localhost:4011/api',
cache: new InMemoryCache(),
});
client.mutate({
mutation: gql`
mutation signin{
login(data:{
username:"elasdfg",
password:"12345678"}){
id,roles,email,username}
}
`
}).then(result => console.log(result));
export class UserList extends Component {
displayUsers() {
console.log(this.result)
var data = this.props.data;
return data.login.map((user) => {
return (
<li>{user.email}</li>
);
})
}
render() {
return (
<div>
<li>
{this.displayUsers()}
</li>
</div>
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Header />);
Mutation result
I've tried to use a class to fetch the data given by the mutation and later render it in the component. I've also tried passing the result to a variable but I had no success with that.
I'm just expecting to see the data resulting from the mutation
You should request data inside the component and then save it to the state.
export class UserList extends Component {
constructor() {
this.state = {
newData: null,
};
this.mutateData = this.mutateData.bind(this);
}
mutateData() {
client
.mutate({
mutation: gql`
mutation signin {
login(data: { username: "elasdfg", password: "12345678" }) {
id
roles
email
username
}
}
`,
})
.then((result) => {
this.setState({ newData: result });
});
}
componentDidMount() {
this.mutateData();
}
render() {
// do something with new data
}
}

TypeScript: Object is of type 'unknown'.ts(2571)

I'm using relay and graphQL in react Typescript to fetch list of users. While I'm passing the props from ViewerUserList.tsx to UserList.tsx. I'm getting this error: Object is of type 'unknown'.ts(2571)
I've mentioned in below file where I'm getting the error exactly
This is my ViewerUserList.tsx
import React from 'react';
import {graphql, QueryRenderer} from 'react-relay';
import UserList from './UserList'
import environment from "../relayEnvironment"
export default class ViewerUserList extends React.Component {
render() {
return (
<QueryRenderer
environment={environment}
query={graphql`
query ViewerQuery {
viewer {
id
# Re-use the fragment here
...UserList_userData
}
}
`}
variables={{}}
render={({error, props}) => {
if (error) {
return <div>Error!</div>;
}
if (!props) {
return <div>Loading...</div>;
}
return (
<div>
<div>list for User {props.viewer.id}:</div> //I am getting error here on props
<UserList userData={props.viewer} />
</div>
);
}}
/>
);
}
}
This is UserList.tsx
// OPTIONAL: Flow type generated after running `yarn relay`, defining an Object type with shape of the fragment:
import type {UserList_userData} from './__generated__/UserList_userData.graphql';
import User from './User'
import React from 'react';
import {graphql, createFragmentContainer} from 'react-relay';
type Props = {
userData: UserList_userData,
}
class UserList extends React.Component<Props> {
render() {
const {userData: {apiVersion, users}} = this.props;
return (
<section>
<ul>
{users!.edges!.map(edge =>
<User
key={edge!.node!.id}
/*We pass the data required by Todo here*/
user = {edge!.node!}
/>
)}
</ul>
</section>
);
}
}
export default createFragmentContainer(
UserList,
{
userData: graphql`
# As a convention, we name the fragment as '<ComponentFileName>_<PropName>'
fragment UserList_userData on Query {
users(
first: 2147483647 # max GraphQLInt, to fetch all todos
) {
edges {
node {
id,
# We use the fragment defined by the child Todo component here
...User_user,
},
},
},
apiVersion
}
`,
},
);
I was facing similar problem, but with try / catch errors, so I did the following:
declare 'err' as unknow
create a const 'e' = err
change his type from unknow to ErrorEvent
use 'e'
Worked for me 👍
catch (err: unknown) {
const e = err as ErrorEvent;
return response.status(400).json({ Error: e.message });
}
In your case, you can try to:
declare unknow as TS mention;
get another const forcing the type to ErrorEvent;
use this last const for whatever you need.
See if it works.

Using graphql-tools, apollo-link-schema, and react-hooks always returning undefined when mocking

I'm new to using GraphQL in React and have been moving a project from a REST API to a new GraphQL one. As part of this, I wanted to setup mock data to work on the application independent of the GQL API being completed. I've spent a bunch of time trying to follow the Apollo and GraphQL Tools docs but no matter what, I can't seem to get the mock resolvers to work properly. For context, I am using this in a NextJS/React app, and here's a minimum example of what I'm trying to do:
Setup App.js
import React from 'react';
import ApolloClient from 'apollo-client';
import { ApolloProvider } from 'react-apollo';
import { SchemaLink } from 'apollo-link-schema';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { makeExecutableSchema } from '#graphql-tools/schema';
import { addMocksToSchema } from '#graphql-tools/mock';
export default function App() {
const schema = makeExecutableSchema({typeDefs:`
type Query {
getPerson: Person!
}
type Person {
name: String!
}
`});
const mocks = {
Query: () => ({
getPerson: () => ({
name: () => "Name"
})
})
}
addMocksToSchema({ mocks, schema });
const link = new SchemaLink({ schema });
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
connectToDevTools: true
});
return (
<ApolloProvider client={client}>
<Person />
</ApolloProvider>
)
}
Person.js
import React from 'react';
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
export default function Person() {
const { loading, error, data } = useQuery(gql`
query PersonQuery {
getPerson {
name
}
}
`, {
errorPolicy: 'all'
});
console.log(data);
if (loading) return "Loading...";
if (error) console.log(error);
return (
<h1>{data.getPerson.name}<h1>
)
}
Looking at the console.log(error) result yields the error Cannot return null for non-nullable field Query.getPerson and making it a nullable field just returns { getPerson: null } of course. I've tried returning the resolver results as objects vs functions. Logging within the resolvers shows the Query part is being executed but nothing nested within that.
Am I setting something up incorrectly? I also tried not passing in custom mocks as suggested should be possible based on the graphql-tools docs, but to no avail. I also saw this issue from the apollo hooks GitHub that said the newest version of hooks broke the usage of addMocksToSchema, so I tried using the suggested 3.1.3 version but again no luck. Any help would be greatly appreciated!
You need to provide the mock to the client, not the plain schema.
const schemaWithMocks = addMocksToSchema({
schema,
mocks: {},
preserveResolvers: false,
});
const client = new ApolloClient({
// link: new SchemaLink({ schema }); < -- REPLACE THIS
link: (new SchemaLink({ schema: schemaWithMocks }) as unknown) as ApolloLink, // https://github.com/apollographql/apollo-link/issues/1258
cache: new InMemoryCache(),
connectToDevTools: true,
});
Now console.log(data) prints
{"getPerson": {"__typename": "Person", "name": "Name"}} 🎉

Recommended way to use GraphQL in Next.js app

In my apps, I am using following NPM modules to play with Strapi, GraphQL and Next.js:
react-apollo
next-apollo
graphql
gql
recompose
In the next step, I am creating Apollo config file, example below:
import { HttpLink } from "apollo-link-http";
import { withData } from "next-apollo";
const config = {
link: new HttpLink({
uri: "http://localhost:1337/graphql",
})
};
export default withData(config);
and then inside a class component, I am using a static method getInitialProps() to fetch data from the Strapi via GraphQL query.
Everything is fine but maybe there is another, better way via React hooks or any other?
I found one more nice hook solution for Next.js and GraphQL.
I want to share it with you. Let's start.
Note: I assume that you have Next.js application already installed. If not please follow this guide.
To build this solution we need:
#apollo/react-hooks
apollo-cache-inmemory
apollo-client
apollo-link-http
graphql
graphql-tag
isomorphic-unfetch
next-with-apollo
1. run npm command:
npm install --save #apollo/react-hooks apollo-cache-inmemory apollo-client apollo-link-http graphql graphql-tag isomorphic-unfetch next-with-apollo
2. create Appolo config file, eg. in folder ./config and call it appollo.js. File code below:
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import withApollo from "next-with-apollo";
import { createHttpLink } from "apollo-link-http";
import fetch from "isomorphic-unfetch";
const GRAPHQL_URL = process.env.BACKEND_URL || "https://api.graphql.url";
const link = createHttpLink({
fetch,
uri: GRAPHQL_URL
});
export default withApollo(
({ initialState }) =>
new ApolloClient({
link: link,
cache: new InMemoryCache()
.restore(initialState || {})
})
);
3. create _app.js file (kind of wrapper) in ./pages folder with below code:
import React from "react";
import Head from "next/head";
import { ApolloProvider } from "#apollo/react-hooks";
import withData from "../config/apollo";
const App = ({ Component, pageProps, apollo }) => {
return (
<ApolloProvider client={apollo}>
<Head>
<title>App Title</title>
</Head>
<Component {...pageProps} />
</ApolloProvider>
)
};
export default withData(App);
4. create reusable query component, eg. ./components/query.js
import React from "react";
import { useQuery } from "#apollo/react-hooks";
const Query = ({ children, query, id }) => {
const { data, loading, error } = useQuery(query, {
variables: { id: id }
});
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {JSON.stringify(error)}</p>;
return children({ data });
};
export default Query;
5. create a component for our data fetched via GraphQL
import React from "react";
import Query from "../components/query";
import GRAPHQL_TEST_QUERY from "../queries/test-query";
const Example = () => {
return (
<div>
<Query query={GRAPHQL_TEST_QUERY} id={null}>
{({ data: { graphqlData } }) => {
return (
<div>
{graphqlData.map((fetchedItem, i) => {
return (
<div key={fetchedItem.id}>
{fetchedItem.name}
</div>
);
})}
</div>
);
}}
</Query>
</div>
);
};
export default Example;
6. create our GraphQL query inside ./queries/test-query. Note: I assume that we have access to our example data and properties id and name via GraphQL
import gql from "graphql-tag";
const GRAPHQL_TEST_QUERY = gql`
query graphQLData {
exampleTypeOfData {
id
name
}
}
`;
export default GRAPHQL_TEST_QUERY;
7. to display our result create index.js file (homepage) in ./pages folder with below code:
import Example from './components/example';
const Index = () => <div><Example /></div>
export default Index;
That's all.. enjoy and extend this solution as you want..
I have found one more interestng solution with using apollo-server-micro and lodash
Quick guide:
create Next.js app (example name: next-app) and install required packages
npm i apollo-server-micro lodash
create required files in you Next.js app (next-app)
/next-app/pages/api/graphql/index.js
/next-app/pages/api/graphql/resolvers.js
/next-app/pages/api/graphql/typeDefs.js
add code to index.js
import { ApolloServer } from 'apollo-server-micro';
import resolvers from './resolvers';
import typeDefs from './TypeDef';
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
});
export const config = {
api: {
bodyParser: false
}
};
export default apolloServer.createHandler({ path: '/api/graphql' });
add code to typeDefs.js
import { gql } from 'apollo-server-micro';
const typeDefs = gql`
type User {
id: Int!
name: String!
age: Int
active: Boolean!
}
type Query {
getUser(id: Int): User
}
`;
export default typeDefs;
add code to resolvers.js
import lodash from 'lodash/collection';
const users = [
{ id: 1, name: 'Mario', age: 38, active: true },
{ id: 2, name: 'Luigi', age: 40, active: true},
{ id: 3, name: 'Wario', age: 36, active: false }
];
const resolvers = {
Query: {
getUser: (_, { id }) => {
return lodash.find(users, { id });
}
}
};
export default resolvers;
test your Next.js app (next-app) by running below command and checking graphql URL http://localhost:3000/api/graphql
npm run dev

Apollo + React: data not appearing in componentDidMount lifecycle

I've got a React app that uses Redux for some in-app state management and Apollo for fetching data from a server. In my network tab, my graphql queries are succeeding and the response is what I expect, but when I try to reference the data in the componentDidMount lifecycle of the React Component, the data isn't there and the loading state is 'true'.
If I move my code to a different lifecycle function, like render(), the data does appear, but I need it to work in componentDidMount. I'm new to Apollo.
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import SdkMap from "#boundlessgeo/sdk/components/map";
import SdkZoomControl from "#boundlessgeo/sdk/components/map/zoom-control";
import * as mapActions from "#boundlessgeo/sdk/actions/map";
import { graphql } from "react-apollo";
import gql from "graphql-tag";
function mapStateToProps(state) {
return {
map: state.map
};
}
class Map extends Component {
static contextTypes = {
store: PropTypes.object.isRequired
};
componentDidMount() {
const store = this.context.store;
store.dispatch(mapActions.setView([-95.7129, 37.0902], 3));
/* ADD SITES LAYER */
store.dispatch(
mapActions.addSource("sites_src", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
})
);
store.dispatch(
mapActions.addLayer({
id: "sites",
source: "sites_src",
type: "circle",
paint: {
"circle-radius": 3,
"circle-color": "blue",
"circle-stroke-color": "white"
}
})
);
console.log(this.props.data); //response doesn't include query fields
if (this.props.data.allSites) {
let sites = this.props.data.allSites.edges;
for (let i = 0; i < sites.length; i++) {
let site = sites[i].node;
let geojson = site.geojson;
if (geojson) {
console.log(site);
const feature = {
type: "Feature",
geometry: geojson,
properties: {
id: site.id
}
};
store.dispatch(mapActions.addFeatures("sites_src", feature));
}
}
}
}
render() {
const store = this.context.store;
return (
<SdkMap store={store} >
<SdkZoomControl />
</SdkMap>
);
}
}
const query = graphql(
gql`
query {
allSites {
edges {
node {
id
projectId
location
areaAcres
geojson
}
}
}
}
`
);
const MapWithRedux = connect(mapStateToProps)(Map);
const MapWithApollo = query(MapWithRedux);
export default MapWithApollo;
First of all there is no need to access this.context by yourself. This is an anti-pattern. Always use connect(). If you need parts of your state in your component use mapStateToProps. If you want to dispatch actions from your component use mapDispatchToProps to pass functions into it that do the dispatching for you. This is the second parameter that connect() accepts.
Also there is no reason to pass down the store to child components because you can connect every component individually that needs anything from the store.
That being said your problem is that fetching data is asynchronous and your request is probably not completed when componentDidMount() is called. So the information that loading is true just means, that your fetch did not finish yet. Either you display that to the user by e.g. showing some kind of spinner or you fetch the required data before you render your component.

Resources