Apollo mutation not appearing in props - reactjs

Here's my component:
import React, { Component } from 'react';
import { gql, graphql, compose } from 'react-apollo';
export class Test extends Component {
render() {
console.log("this.props: " + JSON.stringify(this.props, null, 2));
return null;
}
}
const deletePostMutation = gql`
mutation deletePost ($_id: String) {
deletePost (_id: $_id)
}
`;
export const TestWithMutation = graphql(deletePostMutation)(Test)
This seems like a pretty simple example, but when I run it the props are empty:
props: {}

You have to add return statemant in your resolve function of mutation in your schema.
Example:
resolve: function(source, args) {
// Add the user to the data store
exampleCollection.insert({
category: args.category,
subCategory: args.subCategory,
title: args.title,
description: args.description,
salary: args.salary,
region: args.region,
created_at: new Date()
});
// return some value.
return args.title;
}

Actually the code in the question above is a good example of how to do it. The function is there. The problem is JSON.stringify is not showing functions 🤦🏼‍♂️. At least this question serves as an example of how to do it 😀

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
}
}

React modal container pattern with typescript and graphQL

Spectrum uses specific pattern for modals which I would like to replicate with typescript, graphQL and graphQL-codegen generated hooks.
Now what I currently have:
GraphQL Client Schema
enum ModalType {
LOGIN,
SIGNUP,
}
type LoginModalProps{
loginVal: String!
}
type SignupModalProps{
signupVal: String!
}
extend type Query {
openedModal: OpenedModal!
}
union ModalProps = LoginModalProps | SignupModalProps
type OpenedModal {
modalType: ModalType!
modalProps: ModalProps!
}
GraphQL Query
export const GET_OPENED_MODAL = gql`
query OpenedModal{
openedModal #client{
modalType,
modalProps{
... on LoginModalProps {
loginVal
}
... on SignupModalProps {
signupVal
}
}
}
}
`;
Code above is used by graphql-codegen for generating type safe query hook used below.
import React from 'react';
import LoginModal from '../containers/modals/LoginModal';
import SignupModal from '../containers/modals/SignupModal';
import {
useOpenedModalQuery,
ModalType,
LoginModalProps,
SignupModalProps
} from '../../generated/graphql';
const ModalContainer: React.FC = () => {
const { data } = useOpenedModalQuery();
switch(data?.openedModal.modalType){
case ModalType.Login:
return <LoginModal {...data?.openedModal.modalProps as LoginModalProps}/>
case ModalType.Signup:
return <SignupModal {...data?.openedModal.modalProps as SignupModalProps}/>
default:
return null;
}
}
export default ModalContainer
Given modal declaration then looks as follows
import { LoginModalProps } from '../../../generated/graphql';
const LoginModal: React.FC<LoginModalProps> = ({
loginVal
}) => {
I am new in this typed and graphQL world and it feels to me that it could be done better - especially the switch case statement that I had to use instead of the original object.
Is there some way how to preserve coupling between ModalType and ModalPropsType?
And is the code still safe with the as casting on ModalProps?

Cant Set Apollo Local State with nested values

I'm testing out Apollo Graphql with React and I'm trying to update the local state with Apollo Graphql with a nested object. I'm running into an issue. The data returns a null value and does not even return the value I set as a default. The only warning I see is Missing field __typename. I'm not sure what I'm missing or if this is not how you properly set nested values with Graphql or Apollo issue. I have a code sandbox with the example I'm trying to do https://codesandbox.io/embed/throbbing-river-xwe2y
index.js
import React from "react";
import ReactDOM from "react-dom";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "#apollo/react-hooks";
import App from "./App";
import "./styles.css";
const client = new ApolloClient({
clientState: {
defaults: {
name: {
firstName: "Michael",
lastName: "Jordan"
}
},
resolvers: {},
typeDefs: `
type Query {
name: FullName
}
type FullName {
firsName: String
lastName: String
}
`
}
});
client.writeData({
data: {
name: {
firstName: "Kobe",
lastName: "Bryant"
}
}
});
const rootElement = document.getElementById("root");
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
rootElement
);
App.js
import React from "react";
import Name from "./Name";
import { useApolloClient } from "#apollo/react-hooks";
function App() {
const client = useApolloClient();
client.writeData({
data: {
name: {
firstName: "Lebron",
lastName: "James"
}
}
});
return (
<div>
<Name />
</div>
);
}
export default App;
Name.js
import React from "react";
import { NAME } from "./Queries";
import { useApolloClient } from "#apollo/react-hooks";
const Name = async props => {
const client = useApolloClient();
const { loading, data } = await client.query({ query: NAME });
console.log(data);
return <div>Hello {data.name.firstName}</div>;
};
export default Name;
QUERIES.js
import gql from "graphql-tag";
export const GET_NAME = gql`
{
name #client {
firstName
lastName
}
}
`;
Unfortunately, Apollo Client's documentation is not good in this manner and simply starts using __typename without properly explaining the reasoning behind it directly. I've seen other engineers struggling to understand its purpose before. As the warning is suggesting, you must pass a __typename property to objects you write directly to the cache, as Apollo Client will use this value by default in its data normalization process internally, to save/identify the data.
On all your calls to client.writeData, you should include a __typename property, like:
client.writeData({
data: {
name: {
__typename: 'FullName', // this is the name of the type this data represents, as you defined in your typeDefs
firstName: 'Lebron',
lastName: 'James',
},
},
});
Also, you can't use async/await on the render method of your component -- in the case of function components, the main body itself, as Promises are not valid React elements. So you have two options:
switch from client.query to the useQuery hook; or
since you're only requesting client-side fields, you can use the client.readQuery method which is synchronous and will return the data to you without a Promise. Note that with this method you're only able to make client-side requests, i.e if you want to request client and server fields at the same time, it won't work.

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.

relay fragment spread not working

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

Resources