Apollo Client subscriptions must provide schema - javascript

I have no idea why it is happening.
I am listening to subscription postAdded which gets published every time I create a post using mutation.
import React, { Component } from 'react'
import { graphql, gql } from 'react-apollo'
class App extends Component {
componentDidMount() {
this.props.subscribeToNewPosts()
}
render() {
return (
<div>
<h1>Hello world</h1>
{console.log(this.props)}
</div>
)
}
}
const Query = gql`
query Posts {
posts {
id
title
content
author {
firstName
lastName
}
}
}
`
const Subscription = gql`
subscription postAdded {
postAdded {
id
title
content
author {
firstName
lastName
}
}
}
`
App = graphql(Query, {
name: 'posts',
props: props => {
return {
subscribeToNewPosts: () => {
return props.posts.subscribeToMore({
document: Subscription,
updateQuery: (prev, { subscriptionData }) => {
if(!subscriptionData.data) {
return prev
}
const newPost = subscriptionData.data.postAdded
console.log(newPost)
return {
...prev,
posts: [...prev.posts, newPost]
}
}
})
},
posts: props.posts
}
}
})(App)
export default App
The error:

First, this is most likely a server implementation issue. The error is likely being throw by graphql-tools that's your graphql end point.
The exact same thing happened to me while following second part of the Full-stack + GraphQL tutorial on the official blog.
While it is not necessarily the same case, what happened to me was that the tutorial exports the schema object with: export { schema } which is equivalent to export { schema: schema } instead of the usual exports as default, e.g. export default schema
In my graphql server endpoint I import with import schema from './src/schema' which was wrong because schema was exported as a sub object and not the default export. Correct import for my case should have been import { schema } from './src/schema'
TL;DR Check your server code, check your export and import statements. The error message is really misleading but it most likely has to do with module import/ export.
It could have been avoided or giving more explicit information if we were compiling using webpack instead of using babel-node like in the tutorial.

Related

How do I use "then" with TypeScript?

I'm converting a React app from pure JS to TypeScript. It's linked to firebase; the firebase functions are in a separate file. The one I'm currently working on is to allow the user to change their password. I have a form which accepts the new password and saves it (there's also some validation, but I've left that out). In pure js, it all works fine, but when I convert to TypeScript I'm getting stuck on what to do with the "then" part.
So far my js files are as follows.
PasswordForm.js (so this was originally a js file, which I've changed to tsx; I've added a couple of interfaces and used them, but that's all I've changed):
import React, {useState} from 'react';
import { withFirebase } from '../Firebase';
interface FormProps {
firebase: {
doPasswordUpdate: (string) => void // Not sure about this line
}
}
interface FormState {
password: string
}
const INITIAL_STATE: FormState = {
password: ""
};
const ChangePasswordForm = ({ firebase }: FormProps) => {
const [formValues, setFormValues] = useState(INITIAL_STATE);
const handleSubmit = event => {
firebase
.doPasswordUpdate(formValues.password)
.then(() => { // THIS IS WHERE THE PROBLEM HAPPENS
... do other things ...
})
.catch(error => {...});
};
return (
<form
onSubmit={handleSubmit}>
<input
name="password"
value={formValues.password}
/>
<button type="submit">Submit</button>
</form>
);
export default withFirebase(ChangePasswordForm);
My firebase functions are wrapped in a Context, but the actual functions are in firebase.js (I haven't done anything to convert this to TypeScript):
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
const config = {...}; // Firebase keys etc
class Firebase {
constructor() {
app.initializeApp(config);
this.auth = app.auth();
this.db = app.database();
}
doPasswordUpdate = password =>
this.auth.currentUser.updatePassword(password);
}
export default Firebase;
The error I get (in VSCode) is:
Property 'then' does not exist on type 'void'.
Presumably this is because I've said that doPasswordUpdate should return void, which obviously doesn't have a "then" property. But what should I use instead of void? Is there something that does have a "then"? Or is there another way to do this?
The problem is that you are telling TypeScript lies about your firebase object.
interface FormProps {
firebase: {
doPasswordUpdate: (string) => void // Not sure about this line
}
}
Explicitly tells the code that doPasswordUpdate does not have a return value.
Instead, you should just use your class's declaration by importing it and then using it.
// import the class declaration
import Firebase, { withFirebase } from '../Firebase';
interface FormProps {
// tell the compiler that your firebase is a Firebase
firebase: Firebase
}
This way, the compiler knows to look at your Firebase class for the type information regarding doPasswordUpdate.
In VSCode you can press CTRL, move your cursor over updatePassword and see the function's definition. Use return type instead of void in your function.

Getting an "Exported queries are only executed for Page components." in Gatsby when trying to generate pages

This seems to be a relatively common problem. I am trying to generate blog post pages but am experiencing this error and the pages show a 404 on load. Which means that they are not being generated.
Here is my code for the gatsby.node.js file:
exports.createPages = async ({ graphql, useStaticQuery, actions: { createPage } }) => {
const postQuery = graphql(`
{
gcms {
posts(where: { stage: PUBLISHED }) {
id
slug
}
}
}
`);
const {
gcms: { posts },
} = useStaticQuery(postQuery);
posts.forEach(({ id, slug }) =>
createPage({
path: `/blog/${slug}`,
component: require.resolve(`./src/templates/PostPage.js`),
context: {
id: id,
slug: slug,
},
})
);
};
And my code for the blog post PostPage.js file:
/* eslint-disable react/prop-types */
import React from 'react';
import { graphql } from 'gatsby';
import Layout from "../components/layout";
//import galaxy from "../images/galaxy.jpg";
import SEO from "../components/seo";
export const postPageQuery = graphql`
query PostPageQuery($id: ID!) {
gcms {
post(where: { id: $id }) {
title
slug
excerpt
postContentMarkdown
tags
author {
name
biography
}
seo {
title
description
keywords
}
}
}
}
`;
const PostPage = ({data: {post}}) => {
return (
<Layout>
<SEO
keywords={[
`ui`,
`ux`,
]}
title="Blog" />
{post.slug}
</Layout>
);
};
export default PostPage;
There are a few things that caught my attention and may fix your issue.
The usage of useStaticQuery in your gatsby-node.js. You don't need to fetch postQuery data with the static query hook since you are using the hook outside a component.
The usage of where filter. According to GraphQL documentation, the way to filter data is by using filter filter. In addition, when filtering the filtered criteria are strings, so must be quoted.
When you pass a field via context API to your PostPage, you should avoid filter to all your gcms since your template has the information of that post, is not needed to redo the same previous query again (same than gatsby-node.js), it's not optimal. I will let it there since I don't know how is your data structured but should be refactored.
Applying it to your code should look like this.
gatsby-node.js:
exports.createPages = async ({ graphql, useStaticQuery, actions: { createPage } }) => {
const postQuery = graphql(`
{
gcms {
posts(filter: { stage: "PUBLISHED" }) {
id
slug
}
}
}
`);
let {posts}= postQuery.gcms;
posts.forEach(({ id, slug }) =>
createPage({
path: `/blog/${slug}`,
component: require.resolve(`./src/templates/PostPage.js`),
context: {
id: id,
slug: slug,
},
})
);
};
PostPage.js:
/* eslint-disable react/prop-types */
import React from 'react';
import { graphql } from 'gatsby';
import Layout from "../components/layout";
//import galaxy from "../images/galaxy.jpg";
import SEO from "../components/seo";
export const postPageQuery = graphql`
query PostPageQuery($id: ID!) {
gcms {
post(filter: { id: $id }) {
title
slug
excerpt
postContentMarkdown
tags
author {
name
biography
}
seo {
title
description
keywords
}
}
}
}
`;
const PostPage = ({data: {post}}) => {
return (
<Layout>
<SEO
keywords={[
`ui`,
`ux`,
]}
title="Blog" />
{post.slug}
</Layout>
);
};
export default PostPage;
I ended up fixing this by doing a complete recomposition of my project, with an update to the latest version of Gatsby, this with a bare bones gatsby starter, plugin by plugin. It ended up being plugin conflict issue. I'm not sure which plugin exactly it was, but most likely it was one of these:
gatsby-plugin-eslint, gatsby-plugin-offline, gatsby-plugin-root-import or possibly the prop-types NPM package.
I experienced this same issue after upgrading to the latest version of Gatsby.
Similarly to Tapha's answer, it was a plugin conflict for me. I had yet to upgrade the gatsby-source-strapi plugin. Upgrading that package to its latest available version solved the issue. Whatever your data source happens to be, I would check that it's still playing nice with Gatsby.
So, this warning/error message is very misleading, you can still use an exported query from templates, as detailed in the Gatsby documentation here.

React - Apollo Client, How to add query results to the state

I've created a react app driven by Apollo client and graphQL.
My schema is defined so the expected result is an array of objects ([{name:"metric 1", type:"type A"},{name:"metric 2", type:"type B"}])
On my jsx file I have the following query defined:
query metrics($id: String!) {
metrics(id: $id) {
type
name
}
}`;
I've wrapped the component with Apollo HOC like so:
export default graphql(metricsQuery, {
options: (ownProps) => {
return {
variables: {id: ownProps.id}
}
}
})(MetricsComp);
The Apollo client works fine and returns the expected list on the props in the render method.
I want to let the user manipulate the results on the client (edit / remove a metric from the list, no mutation to the actual data on the server is needed). However since the results are on the component props, I have to move them to the state in order to be able to mutate. How can I move the results to the state without causing an infinite loop?
If apollo works anything like relay in this matter, you could try using componentWillReceiveProps:
class ... extends Component {
componentWillReceiveProps({ metrics }) {
if(metrics) {
this.setState({
metrics,
})
}
}
}
something like this.
componentWillReceiveProps will be deprecated soon (reference link)
If you are using React 16 then you can do this:
class DemoClass extends Component {
state = {
demoState: null // This is the state value which is dependent on props
}
render() {
...
}
}
DemoClass.propTypes = {
demoProp: PropTypes.any.isRequired, // This prop will be set as state of the component (demoState)
}
DemoClass.getDerivedStateFromProps = (props, state) => {
if (state.demoState === null && props.demoProp) {
return {
demoState: props.demoProp,
}
}
return null;
}
You can learn more about this by reading these: link1, link2
you can use this:
import {useState} from 'react';
import {useQuery} from '#apollo/client';
const [metrics,setMetrics]=useState();
useQuery(metricsQuery,{
variables:{id: ownProps.id},
onCompleted({metrics}){
setMetrics(metrics);
}
});

Can I use HOC to wrap component with graphql and redux

I have plenty of template components, they resemble each other in way they are used.
Before being rendered to page, template components get wrapped in graphql and connected to redux.
I want to create a HOC to wrap my templates, so that I do not create a new container each time to connect template to data.
Like so:
Here is my page component, where I try to wrap the AppointmentsListTemplate template with gqlList HOC:
import React from 'react'
import { AdminTemplate, AppointmentsListTemplate } from 'components'
import { gqlList } from 'containers'
import {qyListAppointments} from 'services/gqlQueries/Appointments'
const AppointmentsListTemplateWrapped = gqlList(AppointmentsListTemplate, qyListAppointments)
const AdminAppointmentsPage = (props) => {
return (
<AdminTemplate>
<AppointmentsListTemplateWrapped />
</AdminTemplate>
)
}
export default AdminAppointmentsPage
And here is my gqlList HOC:
import React, {Component} from 'react'
import { graphql } from 'react-apollo'
import { connect } from 'react-redux'
import { saveQueryVars } from 'store/helper/actions'
const gqlList = (WrappedComponent, gqlQuery) => {
const GQL = graphql(gqlQuery)(WrappedComponent)
return connect(null, {
saveQueryVars,
})(GQL)
}
export default gqlList
But graphql connector part throws me this error:
Uncaught TypeError: Cannot read property 'displayName' of undefined
at getDisplayName (react-apollo.browser.umd.js:250)
at wrapWithApolloComponent (react-apollo.browser.umd.js:266)
at new eval (gqlList.js:22)
at eval (createClassProxy.js:95)
at instantiate (createClassProxy.js:103)
at Unknown (eval at proxyClass (createClassProxy.js:NaN), :4:17)
at eval (ReactCompositeComponent.js:303)
at measureLifeCyclePerf (ReactCompositeComponent.js:73)
at ReactCompositeComponentWrapper._constructComponentWithoutOwner
What am I doing wrong?
There doesn't seem to be anything wrong with your code, as far as I can tell. I would also rule out redux as the source of the error. But here's what I suggest:
Check your GraphQL query (gqlQuery) so that it gets what it needs and it can return what you need. I suspect it requires some parameters but doesn't get the right type of a parameter - resulting in a Type error.
Here's an example (without redux) of how to pass parameters:
const Data = graphql(fetchData, { options: {variables: {id: this.getId()} }})(Thing);
Here, fetchData requires the id of a Thing and returns data about that thing. Then you can render <Data/>.
You might want to improve your question by adding the query and variables (saveQueryVars) to it. Also, mention the version of react-apollo because that's the module throwing the error. As a side note, the error message coming from the Apollo client is not very helpful.
You can chain them together:
import { changeFoo } from './myReduxActions';
const QUERY = gql`{ FindSomething { Id, Value }}`;
const myComponent = connect(
store => ({
foo: store.fooReducer.foo
}),
dispatch => ({
changeFoo: (val) => dispatch(changeFoo(val))
})
)(graphql(QUERY)(
{props.data.loading && return <div>Loading...</div>}
let myNewFoo = 'abc';
return props.data.FindSomething ?
<div>{`Id is ${props.data.FindSomething.Id}, redux store value foo is ${props.foo}`}
<div onClick={() => props.ChangeFoo(myNewFoo)}></div></div> :
props.data.error ? <div>Error {props.data.error}</div> : null;
));
So you could do connect(graphql(pureComponent))) or written as connect => graphql => component. You can change the order of graphql and connect.

How to get vuex state from a javascript file (instead of a vue component)

I am working with vuex (2.1.1) and get things working within vue single file components. However to avoid too much cruft in my vue single file component I moved some functions to a utils.js module which I import into the vue-file. In this utils.js I would like to read the vuex state. How can I do that? As it seems approaching the state with getters etc is presuming you are working from within a vue component, or not?
I tried to import state from '../store/modules/myvuexmodule' and then refer to state.mystateproperty but it always gives 'undefined', whereas in the vue-devtools I can see the state property does have proper values.
My estimate at this point is that this is simply not 'the way to go' as the state.property value within the js file will not be reactive and thus will not update or something, but maybe someone can confirm/ prove me wrong.
It is possible to access the store as an object in an external js file, I have also added a test to demonstrate the changes in the state.
here is the external js file:
import { store } from '../store/store'
export function getAuth () {
return store.state.authorization.AUTH_STATE
}
The state module:
import * as NameSpace from '../NameSpace'
/*
Import everything in NameSpace.js as an object.
call that object NameSpace.
NameSpace exports const strings.
*/
import { ParseService } from '../../Services/parse'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
}
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
ParseService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
export default {
state,
getters,
mutations,
actions
}
The store:
import Vue from 'vue'
import Vuex from 'vuex'
import authorization from './modules/authorization'
Vue.use(Vuex)
export const store = new Vuex.Store({
modules: {
authorization
}
})
So far all I have done is create a js file which exports a function returning the AUTH_STATE property of authorization state variable.
A component for testing:
<template lang="html">
<label class="login-label" for="username">Username
<input class="login-input-field" type="text" name="username" v-model="username">
</label>
<label class="login-label" for="password" style="margin-top">Password
<input class="login-input-field" type="password" name="username" v-model="password">
</label>
<button class="login-submit-btn primary-green-bg" type="button" #click="login(username, password)">Login</button>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import * as NameSpace from '../../store/NameSpace'
import { getAuth } from '../../Services/test'
export default {
data () {
return {
username: '',
password: ''
}
},
computed: {
...mapGetters({
authStateObject: NameSpace.AUTH_GETTER
}),
authState () {
return this.authStateObject.auth
},
authError () {
return this.authStateObject.error
}
},
watch: {
authError () {
console.log('watch: ', getAuth()) // ------------------------- [3]
}
},
authState () {
if (this.authState.sessionToken) {
console.log('watch: ', getAuth()) // ------------------------- [2]
}
},
methods: {
...mapActions({
authorize: NameSpace.ASYNC_AUTH_ACTION
}),
login (username, password) {
this.authorize({username, password})
console.log(getAuth()) // ---------------------------[1]
}
}
}
</script>
On the button click default state is logged on to the console. The action in my case results in an api call, resulting a state change if the username - password combination had a record.
A success case results in showing the console in authState watch, the imported function can print the changes made to the state.
Likewise, on a fail case, the watch on authError will show the changes made to the state
For anyone wondering how to access a mutation from a javascript file, you can do the following:
import store from './store'
store.commit('mutation_name', mutation_argument);
Or for actions,
store.dispatch('action_name', action_argument)
import store from './store'
and than
store.commit('mutation_name', mutation_argument)
if you use js file
You can also access actions like:
import store from './store'
store.dispatch('action_name', action_argument)

Categories

Resources