I thought that relay modern implemented a system whereby it would not try to fetch data until it was rendering the component that declared it. I am talking about fragment components. I have tried to test this but it is fetching all the data.
import React from "react";
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import {
RelayEnvironmentProvider,
} from "react-relay/hooks";
import "./App.css";
import QueryLoaderComponent from "./QueryLoaderComponent";
import QueryComponent from "./QueryComponent";
async function fetchGraphQL(text: string, variables: Record<any, any>) {
// Fetch data from GitHub's GraphQL API:
const response = await fetch("https://countries.trevorblades.com/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: text,
variables,
}),
});
// Get the response as JSON
return await response.json();
}
async function fetchRelay(params: any, variables: any) {
console.log(
`fetching query ${params.name} with ${JSON.stringify(variables)}`
);
return fetchGraphQL(params.text, variables);
}
// Export a singleton instance of Relay Environment configured with our network function:
const environment = new Environment({
network: Network.create(fetchRelay),
store: new Store(new RecordSource()),
});
function App() {
return (
<RelayEnvironmentProvider environment={environment}>
{/* <QueryLoaderComponent /> */}
<QueryComponent />
</RelayEnvironmentProvider>
);
}
export default App;
import { useState } from "react";
// #ts-ignore
import graphql from "babel-plugin-relay/macro";
import { QueryComponentQuery } from "./__generated__/QueryComponentQuery.graphql";
import { PreloadedQuery, useLazyLoadQuery, usePreloadedQuery } from "react-relay";
// import FragmentComponent from "./FragmentComponent";
const query = graphql`
query QueryComponentQuery($id: ID!) {
country(code: $id) {
name
...FragmentComponent_country
}
}
`;
interface Props {
// queryRef: PreloadedQuery<QueryComponentQuery>;
}
const QueryComponent = ({
// queryRef
}: Props) => {
const data = useLazyLoadQuery<QueryComponentQuery>(query, { id: "US"});
const [showContinent, setShowContinent] = useState(false);
return (
<div>
<button onClick={() => setShowContinent(!showContinent)}>
{showContinent ? "Hide" : "Show"} continent
</button>
<h1>{data.country?.name}</h1>
{/* <ul>
{data.countries.map((country: any) => (
<li key={country.name}>
{country.name}{" "}
{showContinent && <FragmentComponent country={country} />}
</li>
))}
</ul> */}
</div>
);
};
export default QueryComponent;
import { useFragment } from "react-relay";
// #ts-ignore
import graphql from "babel-plugin-relay/macro";
import { FragmentComponent_country$key } from "./__generated__/FragmentComponent_country.graphql";
export const fragment = graphql`
fragment FragmentComponent_country on Country {
continent {
name
}
}
`;
interface Props {
country: FragmentComponent_country$key;
}
const FragmentComponent = ({ country }: Props) => {
const data = useFragment(fragment, country);
return <div>{data.continent.name}</div>;
};
export default FragmentComponent;
this is fetching the data for the fragment component even though it is not rendering the fragment component. is there a way to defer it until it is rendering the component?
use
React Suspense
on the fragment or anywhere where fetching happens as wrapper
Related
I created a component in which i requested the data from rapid api. After that i got the coin summary in coinRanking after that i am trying to pass data in homePage but i couldn't. Please help me with sharing data using useContext.
import React, { useState } from 'react'
import { useQuery } from "react-query"
import axios from 'axios'
import { createContext } from 'react';
import HomePage from '../Pages/HomePage';
const options = {
method: 'GET',
url: 'https://coinranking1.p.rapidapi.com/coins',
params: {
referenceCurrencyUuid: 'yhjMzLPhuIDl',
timePeriod: '24h',
'tiers[0]': '1',
orderBy: 'marketCap',
orderDirection: 'desc',
limit: '50',
offset: '0'
},
headers: {
'X-RapidAPI-Key': 'ea3e0e9305msh25681129077648ep1f06f9jsnb2ee8da018dc',
'X-RapidAPI-Host': 'coinranking1.p.rapidapi.com'
}
};
const DataContext = createContext({coinRanking}) // i don't know what to pass over here.
function QueryData({children}) {
const getData = axios.request(options)
const {data, isLoading, isError, error, isFetching} = useQuery("getData", ()=>getData,{})
if (isLoading){
console.log("Loading");
return <p>Loading</p>
} else if (isError){
console.log("error");
return <p>{error}</p>
}
const coinRanking= data?.data?.data.stats
return (
<div>
<DataContext.Provider value={{coinRanking}}> //i am trying to pass coinRanking in Homepage
<HomePage/>
</DataContext.Provider>
</div>
)
}
export default DataContext
Well, i spent too much time on finding the answer on my own and watching and exploring lots of video and documentation. Finally, i got an answer where i was but due to minor error i thought this is not doable. However, i was wrong. I tried again and it is working...
import React, { useState } from 'react'
import { useQuery } from "react-query"
import axios from 'axios'
import { createContext } from 'react';
import HomePage from '../Pages/HomePage';
const options = {
method: 'GET',
url: 'https://coinranking1.p.rapidapi.com/coins',
params: {
referenceCurrencyUuid: 'yhjMzLPhuIDl',
timePeriod: '24h',
'tiers[0]': '1',
orderBy: 'marketCap',
orderDirection: 'desc',
limit: '50',
offset: '0'
},
headers: {
'X-RapidAPI-Key': 'ea3e0e9305msh25681129077648ep1f06f9jsnb2ee8da018dc',
'X-RapidAPI-Host': 'coinranking1.p.rapidapi.com'
}
};
export const DataContext = createContext() //i am trying to pass data through this context
function QueryProvider({children}) {
const getData = axios.request(options)
const {data, isLoading, isError, error, isFetching} = useQuery("getData", ()=>getData,{})
if (isLoading){
console.log("Loading");
return <p>Loading</p>
} else if (isError){
console.log("error");
return <p>{error}</p>
}
const coinRanking= data?.data?.data.stats
return (
<div>
{
Object.entries(coinRanking).map(([key, value], i)=>{
console.log("this is value");
return(
<>
<p>Hi, i am data</p>
<p key={i}>{key}--{value}</p>
</>
)
})
}
<DataContext.Provider value={{coinRanking}}>
{children}
</DataContext.Provider>
</div>
)
}
export default QueryProvider
and for Home page
import React from 'react'
import {DataContext} from '../DataQuery/dataQuery'
import { useContext } from 'react'
const HomePage = (props) => {
const {coinRanking} = useContext(DataContext)
console.log(coinRanking, "is in the homepage")
return (
<div>
<div>HomePage</div>
</div>
)
}
export default HomePage
most important is the app.js file where you have to use context provider with router
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { QueryClientProvider, QueryClient } from "react-query"
import './App.css'
import HomePage from './components/Pages/HomePage'
import QueryProvider,{DataContext} from './components/DataQuery/dataQuery'
import { ReactQueryDevtools } from 'react-query/devtools'
const queryClient = new QueryClient()
function App() {
return (
<div>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<QueryProvider>
<Routes>
<Route path="/" element={<HomePage/>}></Route>
</Routes>
</QueryProvider>
</BrowserRouter>
{/* <ReactQueryDevtools initialIsOpen={false} position="bottom-right"/> */}
</QueryClientProvider>
</div>
)
}
export default App
I have a code which access data from GraphQL API in an arrow function:
const LinkList = () => {
const { loading, error, data } = useQuery(CURRENCIES);
if (loading) return <Loader/>;
if (error) return <pre>{error.message}</pre>
return (
<div className="options">
{data.currencies.map((currency) => {
return (
<button
key={currency}
id={currency}
className="option"
>
{currency.symbol}
{currency.label}
</button>
);
})}
</div>
);
};
But I really need to implement this piece of code with access to it in a class component. I was searching a documentation with accessing data in a classes, but nothing.
Any ideas?
You can use #apollo/client package and we can use client.query directly in the class component
import {
ApolloClient,
gql,
InMemoryCache,
NormalizedCacheObject
} from '#apollo/client';
const client = new ApolloClient<NormalizedCacheObject> ({
cache: new InMemoryCache({}),
uri: 'https://countries.trevorblades.com'
});
import * as React from 'react';
const GET_Countries = gql`
query {
countries{
code
name
}
}
`;
class App extends React.Component {
getData = async () => {
let res = await client.query({
query: GET_Countries
})
console.log(res)
// Set the state to make changes in UI
}
componentDidMount() {
this.getData()
}
render() {
return "Test";
}
}
export default App;
I am using next-auth to manage JWT tokens and sessions for my next.js application. I'm also using urql as the GraphQL client. I initialise the urql client in a file as follows:
import { createClient } from 'urql';
const client = createClient({
url: process.env.NEXT_PUBLIC_API_URL,
fetchOptions: () => {
return {
headers: {}, // I'd like to set Auth header here...
};
},
});
export default client;
And then this is added to _app.tsx as follows:
import type { AppProps } from 'next/app';
import { Provider as AuthProvider } from 'next-auth/client';
import { Provider as GraphqlProvider } from 'urql';
import client from '../graphql/client';
function App({ Component, pageProps }: AppProps) {
const { session } = pageProps;
return (
<GraphqlProvider value={client}>
<AuthProvider session={session}>
<Component {...pageProps} />
</AuthProvider>
</GraphqlProvider>
);
}
export default App;
Nothing fancy at this stage.
Now my issue is that I want to be able to access the access token to add an Authorization header in client, however because this bit of code exists outside of a React component I can't use the useSession hook provided by next-auth.
Can anyone suggest the best way to approach this issue?
Thanks.
I was able to get a very helpful response from one of the next-auth maintainers on how to handle this scenario: https://github.com/nextauthjs/next-auth/discussions/1806
Important points are to have the AuthProvider as the most parent component in _app.tsx, then create a useClient hook that you pass to urql's provider:
/graphql/client.ts
import { createClient } from '#urql/core';
import { useSession } from 'next-auth/client';
import * as React from 'react';
/**
* Get GraphQL Client in browser environments (frontend).
*
* If the user has an active session, it will add an accessToken to all requests
*/
const useClient = (options?: RequestInit) => {
const [session] = useSession();
const token = session?.accessToken;
// const handleError = useErrorHandler();
return React.useMemo(() => {
const client = createClient({
url: process.env.NEXT_PUBLIC_API_URL,
fetchOptions: () => {
return {
headers: {
Authorization: token ? `Bearer ${token}` : '',
...(options?.headers ? options.headers : {}),
},
};
},
});
return client;
}, [options, token]);
};
export default useClient;
then create the urql provider, passing in the client that you get from useClient hook:
/graphql/provider.tsx
import React from 'react';
import { Provider } from 'urql';
import useClient from './client';
interface GraphqlProviderProps {}
const GraphqlProvider: React.FC<GraphqlProviderProps> = ({ children }) => {
const client = useClient();
return <Provider value={client}>{children}</Provider>;
};
export default GraphqlProvider;
And then use this as child to AuthProvider in _app.tsx
import { Provider as AuthProvider } from 'next-auth/client';
import type { AppProps } from 'next/app';
import GraphqlProvider from '../graphql/provider';
import '../styles/index.css';
function App({ Component, pageProps }: AppProps) {
const { session } = pageProps;
return (
<AuthProvider session={session}>
<GraphqlProvider>
<Component {...pageProps} />
</GraphqlProvider>
</AuthProvider>
);
}
export default App;
Finally, you can pause all useQuery requests until session is initialised:
/pages/index.tsx
import { signIn, signOut, useSession } from 'next-auth/client';
import React from 'react';
import { useQuery } from 'urql';
interface HomeProps {}
const Home: React.FC<HomeProps> = ({}) => {
const [session, loading] = useSession();
const QUERY = `
query {
users {
id
name
email
}
}
`;
const request = useQuery({ query: QUERY, pause: !session });
console.log(request);
return (
<div>
{!session && (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
</>
)}
{session && (
<>
Signed in as {session.user.email} <br />
<button onClick={() => signOut()}>Sign out</button>
</>
)}
</div>
);
};
export default Home;
And it works! You can see the auth header with valid jwt token in the network requests :-)
I'm trying to use next.js with apollo graphql for server-side rendering. I know that to do that i need to run the necessary queries inside getServerSideProps(), which then will pass the props into the main component, where i will be able to render the results.
I created a provider to make sure all components in the tree get the same client object.
import withApollo from "next-with-apollo";
import { ApolloClient, InMemoryCache } from "#apollo/client";
import { ApolloProvider } from "#apollo/react-hooks";
export default withApollo(
() => {
return new ApolloClient({
ssrMode: true,
uri: "https://my.api/graphql",
cache: new InMemoryCache()
});
},
{
render: ({ Page, props }) => {
return (
<ApolloProvider client={props.apollo}>
<Page {...props} />
</ApolloProvider>
);
}
}
);
but how can i get this client inside the getServerSideProps() function if it's not being wrapped by withApollo()?
import gql from "graphql-tag";
import { useQuery } from "#apollo/react-hooks";
import { ApolloClient } from "#apollo/client";
import withApollo from "next-with-apollo";
const MY_QUERY = gql`
query MyQuery {
myQuery {
name
}
}
`;
function MyComponent(props) {
return (
<div className="landing-section__topcontainer ph-lg-8 ph-3">
<div className="overflow-list-container">
<div className="landing-horizontal-list">
{props.res.map(q => {
return (
<div className="tag-tile__title">{q.name}</div>
);
})}
</div>
</div>
</div>
);
}
export async function getServerSideProps() {
// Fetch data from external API
const apolloClient = getApolloClient();
const { data } = await apolloClient.query({
query: MY_QUERY
});
const res = data.myQuery;
return { props: { res } };
}
export default withApollo(MyComponent);
I'm making an application in which the user has the ability to decide if his creations are active or inactive, and the API route responsible for that is
(I'm using NextJs API routes)
import { NextApiRequest, NextApiResponse } from "next";
import { decryptCookie } from "../../../lib/cookie";
import { prisma } from "../../../lib/prisma";
interface User {
email: string;
issuer: string;
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== "PUT") return res.status(405).end;
let userFromCookie: User;
try {
userFromCookie = await decryptCookie(req.cookies.auth);
if (!userFromCookie.email) {
throw new Error("Cannot find user. Unable to proceed with creation.");
}
const userEmail = userFromCookie.email;
const active = JSON.parse(req.body);
const userInDb = await prisma.user.findOne({
where: {
email: userEmail,
},
});
const response = await prisma.brainstorm.update({
data: {
active,
},
where: {
id: userInDb.id,
},
});
res.status(201).json({ response });
} catch (error) {
return res.status(500).end(error.message);
}
};
the components that contain this action receives it's data as props from a map method in a parent component
I'll put in here the whole component, but you guys can worry about the Switch that indicates the activeness and the function responsible for the change.
import React, { useState, useEffect } from "react";
import Switch from "react-switch";
import {
Container,
BrainstormInfo,
BrainstormTitle,
Active,
Group,
StormPieces,
} from "./styles";
import { Brainstorm } from "../../pages/user-dashboard";
import useFormatDate from "../../hooks/useFormatDate";
import produce from "immer";
interface Props {
brainstormData: Brainstorm;
}
const UserBrainstormCard: React.FC<Props> = ({ brainstormData }) => {
if (!brainstormData) return <h1>Loading...</h1>;
const [active, setActive] = useState(brainstormData.active);
const formatedDate = useFormatDate(
(brainstormData.createdAt as unknown) as string
);
async function handleActiveness() {
setActive(!active);
const response = await fetch("/api/brainstorm/update", {
method: "PUT",
body: JSON.stringify(active),
});
const data = await response.json();
setActive(data.response.active);
}
return (
<Container>
<BrainstormInfo>
<p>Brainstorm</p>
<p>{formatedDate}</p>
</BrainstormInfo>
<BrainstormTitle>
<h3>{brainstormData.title}</h3>
</BrainstormTitle>
<Active>
<Group>
<p>Active:</p>
<Switch
offHandleColor="#eee"
onHandleColor="#eee"
draggable={false}
onChange={handleActiveness}
checked={active}
checkedIcon={false}
uncheckedIcon={false}
height={15}
width={30}
handleDiameter={20}
offColor="#f13030"
onColor="#2dea8f"
/>
</Group>
<StormPieces>
<p>
{brainstormData.stormPieces.length}
{` `}Stormpieces
</p>
</StormPieces>
</Active>
</Container>
);
};
export default UserBrainstormCard;
The call to the API happens, but when I update the page it all goes back to what the value it was initially.
I'm pretty sure that the problem has to do with state, and that I should find a way to insert this values in the state. But I don't know a clear path on how to do it