Map query to props with Apollo.js in React - node.js

I have just set up graphql on my server and wanting to connect Apollo to my frontend.
I was able to integrate Apollo with Redux (How to pass initial state to reducer) but now want to map state to props via redux connect.
My current Home component looks like this:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { graphql } from 'react-apollo';
import './Home.css'
class Home extends Component {
render() {
return (
<section className='home-container'>
<h1>Site Name</h1>
</section>
)
}
}
const mapStateToProps = state => ({
curPage: state.curPage
})
export default connect(
mapStateToProps,
)(Home)
I essentially want to replace mapStateToProps with mapQueryToProps however I am unsure how this works.
Does this make a request to my /graphql endpoint and map the response to curPage?
Will this happen before the first render? As in will this.props.curPage be available within componentWillMount()? (I need this for seo purposes to make sure all the content is rendered server side).
Where do I configure my graphql endpoint? I see some examples configuring it within their store. My store is split into store.js and index.js slightly but is as follows:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './containers/App/App';
import initialState from './initialState';
import configureStore from './store';
import { ApolloClient, ApolloProvider } from 'react-apollo';
const client = new ApolloClient();
// Let the reducers handle initial state
initState() // eslint-disable-line
// if we are in production mode, we get the initial state from the window object, otherwise, when we are in dev env we get it from a static file
const preloadedState = window.__INITIAL_STATE__ === '{{__PRELOADEDSTATE__}}' ? initialState : window.__INITIAL_STATE__
const store = configureStore(preloadedState)
ReactDOM.render(
<ApolloProvider store={store} client={client}>
<BrowserRouter>
<App />
</BrowserRouter>
</ApolloProvider>,
document.getElementById('root')
)
// store.js
import { createStore, applyMiddleware, compose } from 'redux'
import { createLogger } from 'redux-logger'
import reducers from './reducers'
import { ApolloClient } from 'react-apollo';
const logger = createLogger()
const client = new ApolloClient();
export default function configureStore(initialState = {}) {
// Create the store with two middlewares
const middlewares = [
// sagaMiddleware
logger,
client.middleware()
]
const enhancers = [
applyMiddleware(...middlewares)
]
const store = createStore(
reducers,
initialState,
compose(...enhancers)
)
// Extensions
store.asyncReducers = {} // Async reducer registry
return store
}
Update
I am now importing my client from my store so that I only have one instance of my client. I am also using /graphql as my endpoint so I shouldn't need to configure the endpoint.
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './containers/App/App';
import initialState from './initialState';
import {configureStore, client} from './store';
import { ApolloClient, ApolloProvider } from 'react-apollo';
initState() // eslint-disable-line
// if we are in production mode, we get the initial state from the window object, otherwise, when we are in dev env we get it from a static file
const preloadedState = window.__INITIAL_STATE__ === '{{__PRELOADEDSTATE__}}' ? initialState : window.__INITIAL_STATE__
const store = configureStore(preloadedState)
ReactDOM.render(
<ApolloProvider store={store} client={client}>
<BrowserRouter>
<App />
</BrowserRouter>
</ApolloProvider>,
document.getElementById('root')
)
Still a bit confused about graphql(MY_QUERY, { props: mapQueryToProps })(Home)
I have a query to get curPage which works in graphiql: (I'm guessing MY_QUERY should be equal to this?)
query {
findResource(filter: {resource: "pages", slug: "about"}) {
id
title
slug
path
sections
}
}
and returns:
{
"data": {
"findResource": [
{
"id": "5",
"title": "About",
"slug": "about",
"path": "/about",
"sections": [
{
"type": "masthead",
"mh_title": "",
"video": false,
"image": [],
"showCta": false,
"cta": []
},
{
"type": "text_image",
"alignment": "left",
"text_image": [
{
"type": "text",
"title": "",
"content": "",
"call_to_action": false,
"cta": []
}
]
}
]
}
]
}
}
Would that mean my
const mapQueryToProps = ({data: { getPage: { page } }, ownProps}) => ({ curPage: page })
should look like:
const mapQueryToProps = ({data: { findResource: { page } }, ownProps}) => ({ curPage: page })

Couple of points:
You're creating two instances of ApolloClient -- one in index.js and one in store.js -- you could pass the client to configureStore and that way you're utilizing the same instance.
If your graphQL endpoint is anywhere other than /graphql, you'll have to utilize a custom network interface. Example from the docs:
import { ApolloClient, createNetworkInterface } from 'react-apollo';
const networkInterface = createNetworkInterface({
uri: 'http://api.example.com/graphql'
});
const client = new ApolloClient({ networkInterface });
With just the minimal setup, the data from your query will not be available immediately -- the component will mount and rerender once the data is available. However, the documentation provides a couple of ways to get around that.
As far as how the query data gets mapped to props, it's important to note that Apollo does not utilize connect, but rather has its own HOC. You'll use it in a similar manner:
graphql(MY_QUERY, { props: mapQueryToProps })(Home)
The function you assign to props will be passed a single object that has two properties, data and ownProps. Data includes the result of your query and ownProps refers to any existing props on the component. So your mapQueryToProps might look something like:
const mapQueryToProps = ({data: { getPage: { page } }, ownProps}) =>
({ curPage: page })
You can also omit specifying props altogether, and you'll just get the whole data object as a prop. The above is just a way of fine-tuning what actually gets assigned as props to your component.
If there are still parts of your state you want to keep using redux for, there's a section in the docs for integrating redux and Apollo. It's pretty clean if you use compose or recompose.
Apollo's documentation for react is pretty clear and to-the-point. If you haven't already, you may want to just go through the Setup and options and Usage sections, and maybe even through any applicable Recipes before proceeding any further.
EDIT: The query you pass in to the HOC will need to be a string. The easiest thing to do is to import and use the gql tag. Based on the query you provided, it would look like this:
const MY_QUERY = gql`
query {
findResource(filter: {resource: "pages", slug: "about"}) {
id
title
slug
path
sections
}
}`

Related

Getting TRPCClientError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON when trying to call a back end api with Trcp

I am planning on building an app using SST and tRPC. I have never used either so I am going through the docs and quick start to better understand the material. I came across an issue where the call is not rendering on the front end. Im not sure if I have the router wrong or something else in the backend. Everytime I make a request it will give this error TRPCClientError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON. But im not sure where its coming from.
Stacks
import { StackContext, Api, ViteStaticSite } from "#serverless-stack/resources";
export function MyStack({ stack }: StackContext) {
const api = new Api(stack, "api", {
routes: {
"GET /todo": "functions/todo.handler"
},
cors : true,
});
const site = new ViteStaticSite(stack, "site", {
path: "frontend",
buildCommand: "npm run build",
environment: {
REACT_APP_API_URL: api.url,
},
});
stack.addOutputs({
SITE: site.url,
});
}
router
import { initTRPC } from '#trpc/server';
import { z } from 'zod';
export const t = initTRPC.create();
const appRouter = t.router({
greeting: t.procedure
.input(
z.object({
name: z.string(),
})
)
.query(({input}) => {
return {
text: `Hello ${input?.name ?? 'world'}`
};
}),
});
export type AppRouter = typeof appRouter;
import { awsLambdaRequestHandler } from '#trpc/server/adapters/aws-lambda';
export const handler = awsLambdaRequestHandler({
router: appRouter
})
frontend
import React, { useState } from 'react';
import ReactDOM from 'react-dom/client'
import './index.css'
import { QueryClient, QueryClientProvider } from '#tanstack/react-query';
import { httpBatchLink } from '#trpc/client';
import { trpc } from './trpc';
const apiUrl = import.meta.env.REACT_APP_API_URL;
function App() {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: `${apiUrl}/todo`
}),
],
}),
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<Sample />
</QueryClientProvider>
</trpc.Provider>
);
}
function Sample(){
const result = trpc.greeting.useQuery({name: 'will'})
return (
<div>
<div>{result.isLoading ? "Loading..." : result.data?.text}</div>
</div>
)
}

How to use externally generated swagger.json in NestJS?

I have generated swagger.yaml/swagger.json from external documentation library. Now, I wanted to import and host that as API documentation on my API platform which is running on NestJS. I know, we can generate and export swagger from NestJS code, but for me the need is reverse, we have the swagger.json, we need to render it in the NestJS platform, like https://example.com/docs
If you already have a swagger.json file you can ignore the SwaggerModule.createDocument and the DocumentBuilder and just pass the parsed JSON file to SwaggerModule.setup()
import { NestFactory } from '#nestjs/core';
import { SwaggerModule } from '#nestjs/swagger';
import { readFile } from 'fs/promises';
import { path } from 'join';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// read the JSON file to string and parse the string to object literal
const document = JSON.parse(
(await readFile(join(process.cwd(), 'swagger.json'))).toString('utf-8')
)
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();

React context API when wrapped around the main app component in index.js file it's display nothing

I am learning about context API. reducer.js:
export const initialState = {
user: null,
playlist: [],
playing: false,
item: null
};
const reducer = (state, action) => {
console.log(action);
switch(action.type) {
case 'SET_USER':
return {
...state,
user: action.user
}
default:
return state;
}
}
export default reducer;
DataLayer.js:
import React, {createContext, useContext, useReducer} from "react";
export const DataLayerContext = createContext();
export const DataLayer = ({initialState, reducer, children}) => {
<DataLayerContext.Provider value={useReducer(reducer, initialState)}>
{children}
</DataLayerContext.Provider>
};
now I am wrapping my App component around it in the index.js file
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { DataLayer } from './DataLayer';
import reducer, { initialState } from './reducer';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
// <React.StrictMode>
<DataLayer initialState={initialState} reducer={reducer}>
<App />
</DataLayer>
// </React.StrictMode>
);
reportWebVitals();
but after doing this on the browser it display nothing no error not a single component that I have created is displayed but when I unwrapped the component it works fine
I also encountered the same issue and this is what worked for me …
The component (DataLayer.js) is not returning anything, so you have to change it from this:
export const DataLayer = ({initialState, reducer, children}) => {
<DataLayerContext.Provider value={useReducer(reducer, initialState)}>
{children}
</DataLayerContext.Provider>
};
to this:
export const DataLayer = ({ initialState, reducer, children }) => {
return (
<DataLayerContext.Provider value={useReducer(reducer, initialState)}>
{children}
</DataLayerContext.Provider>
)
};

Passing props / arguments to HOCs

I am trying to pass props (or an argument) to this HOC. I would like to be able to set the uri here dynamically:
import withApollo from 'next-with-apollo';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { ApolloProvider } from '#apollo/react-hooks';
export default withApollo(
({ initialState }) => {
return new ApolloClient({
uri: 'https://example.com/graphql',
cache: new InMemoryCache().restore(initialState || {})
});
},
{
render: ({ Page, props }) => {
return (
<ApolloProvider client={props.apollo}>
<Page {...props} />
</ApolloProvider>
);
}
}
);
I don't really use HOCs normally, so not sure the best way to go about this. If I pass the variable to as a Page prop. How can I access it in the ApolloClient object? Or if that's not possible how would I set an argument here and then access it in the HOC?
export default withApollo(HomePage);
Thanks for any help offered!

the results of my api (back end part) is not displayed in the front end part using reactjs

I am a beginner in nodejs and reactjs and I am developing a simple application. I am now in the front part, I installed all the necessary packages and modules. I launched my front part but nothing is displayed here is a screenshot of the result obtained. thank you in advance
And here a part of my code in reactjs
import React from 'react';
import {connect} from 'react-redux';
import {fetchVideos} from '../actions/videoActions';
var listVideos
class videos extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() { this.props.fetchVideos(); }
render() {
if (this.props.data) {
listVideos = this.props.videos.map(video =>
<li key={video._id}>
{video.titre}
</li>
);
}
return (
<div>
<center><h1>All videos </h1></center>
{listVideos}
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
clients : state.clients,
};
}
const mapDispatchToProps = (dispatch) => {
return {
fetchVideos :()=> dispatch(fetchVideos())
};
}
export default connect(mapStateToProps, mapDispatchToProps)(videos);
If you are using redux, probably you will need to map the videos in the mapStateToProps method
// ...
componentDidMount() { this.props.fetchVideos(); }
// ...
const mapStateToProps = (state, ownProps) => {
return {
clients : state.clients,
videos: state.videos // HERE
};
}
Note the state.videos param is got from the reducer.
In your component, you are trying to read videos from its props. However, as Luiz mentioned, your mapStateToProps is only mapping the clients state variable to your component. mapStateToProps is the logic that binds redux state with your component props. So, assuming that you have set the videos object on your state (via reducers) and adding the videos mapping on the mapStateToProps you should get said data on your component props.

Resources