RTK Query in Redux-Toolkit is returning data of undefined, when I console.log the data it appears in console - node.js

I was trying to display an array of data fetched from my custom server with RTK Query using Next.js (React framework). And this is my first time using RTK Query. Whenever I console.log the data, it appears in the browser console. But whenever I try to map the data to render it in the browser, it keeps throwing an error saying Cannot read properties of undefined (reading 'map').
I figured Next.js always throws an error if an initial state is undefined or null even if the state change. This link talked about solving the problem using useMemo hook https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics
But I didn't understand it well. Please kindly help me out with displaying the data.
Here is the BaseQuery function example I followed, it was derived from redux toolkit docmentation https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#axios-basequery
import axios from "axios";
const axiosBaseQuery =
({ baseUrl } = { baseUrl: "" }) =>
async ({ url, method, data }) => {
try {
const result = await axios({ url: baseUrl + url, method, data });
return { data: result.data };
} catch (axiosError) {
let err = axiosError;
return {
error: { status: err.response?.status, data: err.response?.data },
};
}
};
export default axiosBaseQuery;
I make the GET request here
import { createApi } from "#reduxjs/toolkit/query/react";
import axiosBaseQuery from "./axiosBaseQuery";
export const getAllCarsApi = createApi({
reducerPath: "getAllCarsApi",
baseQuery: axiosBaseQuery({
baseUrl: "http://localhost:5000/",
}),
endpoints(build) {
return {
getAllCars: build.query({
query: () => ({ url: "all-cars", method: "get" }),
}),
};
},
});
export const { useGetAllCarsQuery } = getAllCarsApi;
This is my redux store
import { configureStore } from "#reduxjs/toolkit";
import { getAllCarsApi } from "./getAllCarsApi";
import { setupListeners } from "#reduxjs/toolkit/dist/query";
const store = configureStore({
reducer: { [getAllCarsApi.reducerPath]: getAllCarsApi.reducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(getAllCarsApi.middleware),
});
setupListeners(store.dispatch);
export default store;
I provide the store to the _app.js file.
import "../styles/globals.css";
import axios from "axios";
import { MyContextProvider } from "#/store/MyContext";
import { Provider } from "react-redux";
import store from "#/store/ReduxStore/index";
axios.defaults.withCredentials = true;
function MyApp({ Component, pageProps }) {
return (
<MyContextProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</MyContextProvider>
);
}
export default MyApp;
I get the data here in my frontend.
import { useGetAllCarsQuery } from "#/store/ReduxStore/getAllCarsApi";
const theTest = () => {
const { data, isLoading, error } = useGetAllCarsQuery();
return (
<div>
{data.map((theData, i) => (
<h1 key={i}>{theData}</h1>
))}
<h1>Hello</h1>
</div>
);
};
export default theTest;

This is a timing thing.
Your component will always render immediately and it will not defer rendering until data is there. That means it will also render before your data has been fetched. So while the data is still loading, data is undefined - and you try to map over that.
You could do things like just checking if data is there to deal with that:
const theTest = () => {
const { data, isLoading, error } = useGetAllCarsQuery();
return (
<div>
{data && data.map((theData, i) => (
<h1 key={i}>{theData}</h1>
))}
<h1>Hello</h1>
</div>
);
};

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

NextJs getServerSideProps() never calls api

I have created a nextjs page and inside the page, there is getServerSideProps that calls API to fetch data.
The problem is that getServerSideProps never calls API and returns a response error 403 Unauthorized
nextjs is serving data from .next folder and returns a JSON response and instead of calling API it calls this URL http://localhost:3000/_next/data/development/contacts.json
This is my code
import React, { useState, useEffect } from "react"
import {
Card,
CardHeader,
CardFooter,
Container,
Row,
Button
} from "reactstrap"
import AdminLayout from "../components/layouts/AdminLayout"
import PropTypes from "prop-types"
import TablePagination from "../components/common/TablePagination"
import { fetchData } from "../api"
import { toast } from "react-toastify"
import { useSession } from "../context/SessionContext"
import ProtectPage from "../hocs/ProtectPage"
import { getContacts } from "../api/contacts"
import ContactsTable from "../components/contacts/ContactsTable"
const Contacts = ({ initialContacts, initialPagination, error }) => {
const [contacts, setContacts] = useState(initialContacts)
const [pagination, setPagination] = useState(initialPagination)
const session = useSession()
const handlePagination = async (page) => {
fetchData({
apiMethod: () => getContacts({ page }),
callback: (res, error) => {
if (error || !res) {
toast("Error loading Contacts")
} else {
setContacts(res.data)
setPagination(res.pagination)
}
session.loadingQueue.dequeue()
}
})
}
useEffect(() => {
setContacts(initialContacts)
setPagination(initialPagination)
}, [initialContacts, initialPagination])
return (
<AdminLayout>
{/* Page content */}
<Container className="mt--7" fluid>
<Row>
<div className="col">
<Card className="shadow">
<CardHeader className="border-0">
<Row className="justify-content-between w-100 m-0">
<h3 className="mb-0">Contacts</h3>
</Row>
</CardHeader>
<ContactsTable contacts={contacts ?? []} />
<CardFooter className="py-4">
<TablePagination
{...pagination}
handlePagination={handlePagination}
/>
</CardFooter>
</Card>
</div>
</Row>
</Container>
</AdminLayout>
)
}
Contacts.propTypes = {
initialContacts: PropTypes.array.isRequired,
initialPagination: PropTypes.object.isRequired
}
export async function getServerSideProps(ctx) {
try {
const contactsRes = await getContacts({})
const { data: initialContacts, pagination: initialPagination } =
contactsRes.data
return {
props: {
initialContacts,
initialPagination
}
}
} catch (error) {
return {
props: {
initialContacts: [],
initialPagination: {},
error: true
}
}
}
}
export default ProtectPage(Contacts)
I tried to disable cache and nothing worked. still serving JSON files.
console.log(contactsRes) in getServerSideProps
Or
call your api from getServerSideProps (await fetch(your_path))

I am trying to display data from an external API, how can I fix the issue in my code?

This is the error message I am getting
TypeError: undefined is not an object (evaluating 'drinkList.drinks[0]')
How can I fix the error so that I can use the app to fetch data from the external api?
This is my drink.js code:
import React, { useEffect, useState } from "react";
import axios from "axios";
import Drinks from "./Drinks";
function Home() {
const [drinkName, setDrinkName]= useState([]);
const [drinkList, setDrinkList] = useState([]);
const drinksURL = `https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${drinkName}`;
const handleChangeDrink= e => {
setDrinkName(e.target.value);
}
const getDrink = () => {
axios
.get(drinksURL)
.then(function (response) {
setDrinkList(response.data);
console.log(drinksURL);
})
.catch(function (error) {
console.warn(error);
});
};
return (
<main className="App">
<section className="drinks-section">
<input
type="text"
placeholder="Name of drink (e.g. margarita)"
onChange={handleChangeDrink}
/>
<button onClick={getDrink}>Get a Drink Recipe</button>
<Drinks drinkList={drinkList} />
</section>
</main>
);
}
export default Home;
This is my Drink.js code:
import React from "react";
function Drinks({ drinkList }) {
if (!drinkList) return <></>;
return (
<section className="drinkCard">
<h1>{drinkList.drinks[0].strDrink}</h1>
</section>
);
}
export default Drinks;
Couple of problems here...
drinkName is initialised as an array but it appears to be expecting a string
drinkList is initialised as an array but the data from the API is an object. You may want to assign the drinks array from the response instead
drinksUrl is never updated
An empty array is still truthy
Some easy fixes
const [drinkName, setDrinkName]= useState(null) // initialise as null
const [drinkList, setDrinkList] = useState([])
// don't include the "s" parameter here
const drinksURL = "https://www.thecocktaildb.com/api/json/v1/1/search.php"
const getDrink = () => {
// pass drinkName as a param
axios.get(drinksURL, {
params: { s: drinkName }
}).then(res => {
// note that we're extracting `drinks`
setDrinkList(res.data.drinks)
}).catch(console.warn)
}
and in Drink.js
function Drinks({ drinkList }) {
// check the array length
return drinkList.length && (
<section className="drinkCard">
<h1>{drinkList[0].strDrink}</h1>
</section>
)
}

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!

NextJS: TypeError: Cannot read property 'headers' of undefined

I am receiving this error in getInitialProps function attached to my page:
const getInitialProps = async ({ req, query }: NextPageContext) => {
let isValidReferenceQuery = true;
const { path } = absoluteUrl(req as IncomingMessage);
try {
await policyService.getPolicyByReference(
path,
query.referenceQuery as string
);
} catch (error) {
isValidReferenceQuery = false;
}
return {
referenceQuery: (query.referenceQuery as string) ?? null,
isValidReferenceQuery,
};
};
Page.getInitialProps = getInitialProps;
The function absoluteUrl determines if the API call was made server-side or client-side and adjusts the path accordingly:
export default function absoluteUrl(
req: IncomingMessage
): Record<string, string> {
const protocol = req.headers.referer?.split('//')[0] ?? 'http:';
const host = req
? req.headers['x-forwarded-host'] || req.headers.host
: window.location.host;
const path =
host === 'localhost:3000'
? `${protocol}//localhost:8080`
: `${protocol}//${host}${axios.defaults.baseURL}`;
return {
path,
};
}
I have updated my _document.tsx & _app.tsx to include getInitialProps as described to opt-out of automatic static generation in development mode so req should be defined. We also need all pages to be SSR for localisation:
import React from 'react';
import App, { AppContext } from 'next/app';
import { ReactQueryCacheProvider, QueryCache } from 'react-query';
import { Hydrate } from 'react-query/hydration';
import { appWithTranslation } from '#/localisation/i18n';
import { AppPageProps } from '#/models/page';
const TextMiningApp = ({ Component, pageProps }: AppPageProps) => {
const queryCache = new QueryCache();
const Layout = Component.Layout ? Component.Layout : React.Fragment;
return (
<ReactQueryCacheProvider queryCache={queryCache}>
<Hydrate state={pageProps.dehydratedState}>
<Layout>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<Component {...pageProps} />
</Layout>
</Hydrate>
</ReactQueryCacheProvider>
);
};
TextMiningApp.getInitialProps = async (appContext: AppContext) => ({
...(await App.getInitialProps(appContext)),
});
export default appWithTranslation(TextMiningApp);
I cannot see why if i have opted out of automatic static generation that req is undefined?
Not sure if this is still needed, but currently getInitialProps in _app.js actually works a little differently than pages. Instead of directly passing context as an argument, it passes an object with ctx and Component properties. So you would need to do something like this:
TextMiningApp.getInitialProps = async( { ctx, Component } ) => {
// This should be working now.
console.log( ctx.req );
}
One catch is that App.getInitialProps it needs to be passed all properties from the original argument:
App.getInitialProps( ctx, Component );
See https://github.com/vercel/next.js/discussions/14913

Resources