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
}
}
Related
so I have a GRAPHQL server running locally and I need to fetch data in my react app and I have to build that app with class components. how can I do it ?
You will want to use a library like graphql-request. You will call the request in one of the lifecycle methods if you are using class components and not functional components. You can set the result into state and use it from there.
import { request, gql } from 'graphql-request'
const query = gql`
{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}
`
request('https://api.graph.cool/simple/v1/movies', query).then((data) => console.log(data))
import { gql } from "#apollo/client";
export const GET_DATA = gql`
// your data goes here
`
export class App extends Component{
render(){
return(
<Query query={GET_DATA}>
{({ error, loading, data }) => {
if (error) return 'something went wrong';
if (loading) return 'loading...';
if (data) console.log(data)
</Query
)
}
}
Following this react-firestore-tutorial
and the GitHub code. I wonder if the following is correct way to use the onAuthStateChanged or if I have understod this incorrect I'm just confused if this is the right way.
CodeSandBox fully connect with a test-account with apikey to Firebase!! so you can try it what I mean and I can learn this.
(NOTE: Firebase is blocking Codesandbox url even it's in Authorised domains, sorry about that but you can still see the code)
t {code: "auth/too-many-requests", message: "We have blocked all
requests from this device due to unusual activity. Try again later.",
a: null}a:
Note this is a Reactjs-Vanilla fully fledge advanced website using only;
React 16.6
React Router 5
Firebase 7
Here in the code the Firebase.js have this onAuthStateChanged and its called from two different components and also multiple times and what I understand one should only set it up once and then listen for it's callback. Calling it multiple times will that not create many listeners?
Can someone have a look at this code is this normal in Reactjs to handle onAuthStateChanged?
(src\components\Firebase\firebase.js)
import app from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';
class Firebase {
constructor() {
app.initializeApp(config);
.......
}
.....
onAuthUserListener = (next, fallback) =>
this.auth.onAuthStateChanged(authUser => {
if (authUser) {
this.user(authUser.uid)
.get()
.then(snapshot => {
const dbUser = snapshot.data();
// default empty roles
if (!dbUser.roles) {
dbUser.roles = {};
}
// merge auth and db user
authUser = {
uid: authUser.uid,
email: authUser.email,
emailVerified: authUser.emailVerified,
providerData: authUser.providerData,
...dbUser,
};
next(authUser);
});
} else {
fallback();
}
});
user = uid => this.db.doc(`users/${uid}`);
}
export default Firebase;
This two rect-higher-order Components:
First withAuthentication:
(src\components\Session\withAuthentication.js)
import React from 'react';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
const withAuthentication = Component => {
class WithAuthentication extends React.Component {
constructor(props) {
super(props);
this.state = {
authUser: JSON.parse(localStorage.getItem('authUser')),
};
}
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
},
() => {
localStorage.removeItem('authUser');
this.setState({ authUser: null });
},
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Provider value={this.state.authUser}>
<Component {...this.props} />
</AuthUserContext.Provider>
);
}
}
return withFirebase(WithAuthentication);
};
export default withAuthentication;
And withAuthorization:
(src\components\Session\withAuthorization.js)
import React from 'react';
import { withRouter } from 'react-router-dom';
import { compose } from 'recompose';
import AuthUserContext from './context';
import { withFirebase } from '../Firebase';
import * as ROUTES from '../../constants/routes';
const withAuthorization = condition => Component => {
class WithAuthorization extends React.Component {
componentDidMount() {
this.listener = this.props.firebase.onAuthUserListener(
authUser => {
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
},
() => this.props.history.push(ROUTES.SIGN_IN),
);
}
componentWillUnmount() {
this.listener();
}
render() {
return (
<AuthUserContext.Consumer>
{authUser =>
condition(authUser) ? <Component {...this.props} /> : null
}
</AuthUserContext.Consumer>
);
}
}
return compose(
withRouter,
withFirebase,
)(WithAuthorization);
};
export default withAuthorization;
This is normal. onAuthStateChanged receives an observer function to which a user object is passed if sign-in is successful, else not.
Author has wrapped onAuthStateChanged with a higher order function – onAuthUserListener. The HOF receives two parameters as functions, next and fallback. These two parameters are the sole difference when creating HOC's withAuthentication and withAuthorization.
The former's next parameter is a function which stores user data on localStorage
localStorage.setItem('authUser', JSON.stringify(authUser));
this.setState({ authUser });
while the latter's next parameter redirects to a new route based on condition.
if (!condition(authUser)) {
this.props.history.push(ROUTES.SIGN_IN);
}
So, we are just passing different observer function based on different requirements. The component's we will be wrapping our HOC with will get their respective observer function on instantiation. The observer function are serving different functionality based on the auth state change event. Hence, to answer your question, it's completely valid.
Reference:
https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onauthstatechanged
https://reactjs.org/docs/higher-order-components.html
I'm trying out some state management in React using the Context API; what I want to achieve is that when I reach a specific route I load data from the server, store it in the context, and display it in the page itself. This is causing an infinite loop where the request to the server is done over and over (and never stops).
I'm trying to use higher order components for the provider and consumer logic:
import React, { Component, createContext } from 'react';
import RequestStatus from '../RequestStatus';
import { getData } from '../Api';
const dataCtx = createContext({
data: [],
getData: () => {},
requestStatus: RequestStatus.INACTIVE,
});
export default dataCtx;
export function dataContextProvider(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
getData: this.getData.bind(this),
requestStatus: RequestStatus.INACTIVE,
};
}
async getData() {
this.setState({ requestStatus: RequestStatus.RUNNING });
try {
const data = await getData();
this.setState({ data, requestStatus: RequestStatus.INACTIVE });
} catch (error) {
this.setState({ requestStatus: RequestStatus.FAILED });
}
}
render() {
return (
<dataCtx.Provider value={this.state}>
<WrappedComponent {...this.props} />
</dataCtx.Provider>
);
}
};
}
export function dataContextConsumer(WrappedComponent) {
return function component(props) {
return (
<dataCtx.Consumer>
{dataContext => <WrappedComponent dataCtx={dataContext} {...props} />}
</dataCtx.Consumer>
);
};
}
the provider is the App component itself:
import React, { Fragment } from 'react';
import { dataContextProvider } from './contexts/DataContext';
import { userContextProvider } from './contexts/UserContext';
import AppRoutes from './AppRoutes';
function App() {
return (
<Fragment>
<main>
<AppRoutes />
</main>
</Fragment>
);
}
export default userContextProvider(dataContextProvider(App));
and here's the consumer that causes the loop:
import React, { Component } from 'react';
import RequestStatus from './RequestStatus';
import { dataContextConsumer } from './contexts/DataContext';
class DataList extends Component {
async componentDidMount() {
const { dataCtx: { getData } } = this.props;
await getData();
}
render() {
const { dataCtx: { data, requestStatus } } = this.props;
return (
{/* display the data here */}
);
}
}
export default dataContextConsumer(DataList);
I've tried switching away from the HOC for the consumer, but it didn't help:
import React, { Component } from 'react';
import RequestStatus from './RequestStatus';
import dataCtx from './contexts/DataContext';
class DataList extends Component {
async componentDidMount() {
const { getData } = this.context;
await getData();
}
render() {
const { data, requestStatus } = this.context;
return (
{/* display the data here */}
);
}
}
DataList.contextType = dataCtx;
export default DataList;
The DataList is only one of the pages from where I'd like to trigger a context update.
I'm guessing that the Provider is causing a re-render of the whole App, but why? Where am I going wrong, and how can I fix this?
Ok, after trying to replicate the problem in a sandbox I realized what the problem was: I was wrapping a parent component in a HOC inside a render function, like so:
<Route exact path="/datapage" component={requireLoggedInUser(Page)} />
which forced the DataList component to be destroyed + recreated every time the App re-rendered.
the request loop happens because the DataList component gets re-rendered, calling ComponentDidMount, which calls getData() after each render.
A component renders if there is a change to the props or state of the component.
getData() sets the state property requestStatus (which is why your whole app gets re-rendered) which is a prop of DataList - causing a re-render of DataList.
you should not use requestStatus as a prop of DataList as you are getting that from the context anyway.
This could be because of the fact that your provider (dataContextProvider) level function getData has the same namespace as your function that you are importing from ../Api.
And then I believe that when the following line const data = await getData(); runs within the code block below, it actually calls the providers getData function, thus causing a loop.
async getData() {
this.setState({ requestStatus: RequestStatus.RUNNING });
try {
const data = await getData();
this.setState({ data, requestStatus: RequestStatus.INACTIVE });
} catch (error) {
this.setState({ requestStatus: RequestStatus.FAILED });
}
}
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
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 😀