I am making a simple next js blog type of application using graphql as a data fetching backend to render text. Using getStaticPaths, I'm running into the following error when I try to fetch data for my page.
ReferenceError: Cannot access 'getAllPostIds' before initialization
Here is my code:
pages/posts/[id].tsx
import { getAllPostIds } from '../../../lib/posts'
const Post = ({ postData }) => {
... code.....
}
export const getStaticPaths = async () => {
const paths = getAllPostIds('aws');
return {
paths,
fallback: false
}
}
export default Post;
And here is my posts.ts where I use graphql to fetch data.
import { useQuery } from "react-query";
import { GraphQLClient } from "graphql-request";
const GET_POST_IDS = gql`
query($folder: String!) {
repository(owner: "assembleinc", name: "documentation") {
object(expression: $folder) {
... on Tree {
entries {
name
}
}
}
}
}`
;
const graphQLClient = new GraphQLClient('https://api.github.com/graphql', {
headers: {
Authorization: `Bearer ${process.env.GITHUB_ACCESS_TOKEN}`
}
});
export const getAllPostIds = (folder: String) => {
return useQuery(folder, async () => {
... fetch data ...
});
}
Essentially, before I can even get the data through graphql, next js is complaining that getAllPostIds can't be initialized even though I import it at the top. Is there some next.js magic that I am not seeing?
Related
I'm using next.js for a project where axios fetch in getStaticProps doesnot seem to work even though the URL is serialised in configuration.I tried serializing again by passing the response to JSON.parse but still cant find a solution.
import axios from "axios";
import Qs from "qs";
My axios config code below:
const axiosTmdbApi = axios.create({
baseURL: "https://api.themoviedb.org/3",
headers: { "content-Type": "application/json/" },
paramsSerializer: {
serialize: (params) =>
Qs.stringify({ ...params, api_key: apiKey }, { arrayFormat: "brackets" }),
},
});```
**My category which is passed as a parameter to invoke getTvList or getMovieList data below:**
import axiosTmdbApi from "./axiosTmdbApi";
export const category = {
movie: "movie",
tv: "tv",
};
export const type = {
top_rated: "top_rated",
popular: "popular",
};
const tmdbApi = {
getTvList: (tvType, params) => {
const url = "tv/" + type[tvType];
return axiosTmdbApi.get(url, params);
},
getMovielist: (movieType, params) => {
const url = "movie/" + type[movieType];
return axiosTmdbApi.get(url, params);
},
};
export default tmdbApi;```
Using getStaticProps to fetch my API
import tmdbApi from "../../api/tmdbApi";
import { type, category } from "../../api/tmdbApi";
const Movies = ({ data }) => {
console.log(data);
return (
<>
<h1 className="bg-success">Movies</h1>
</>
);
};
export default Movies;
export async function getStaticProps() {
let params = {};
let response;
response = await tmdbApi.getMovielist(type.popular, {
params,
});
const data = JSON.parse(JSON.stringify(response));
return {
props: { data },
};
}```
**Error :index.js?46cb:602 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'TLSSocket'
--- property '_httpMessage' closes the circle **
Try adding console.log and see what values are being handled at each stage. Instead of const data = JSON.parse(JSON.stringify(response)), you should be doing const data = response.data.
and change return statement to
return {
props: { data: data || [] },
};
I was using qraphql (JavaScript graphql-request library) in a project and ran into a typeError. Here's the code:
import { request, gql } from 'graphql-request'
const graphqlAPI = process.env.NEXT_PUBLIC_GRAPHCMS_ENDPOINT
export const getPosts = async () => {
const query = gql`
query MyQuery {
postsConnection {
edges {
node {
author {
bio
name
id
photo {
url
}
}
createdAt
slug
title
excerpt
featuredImage {
url
}
categories {
name
slug
}
}
}
}
}
`
const result = await request(graphqlAPI, query)
return result.postsConnection.edges
}
The error said there was a problem with the document parameter of the request.
Never Mind, its because the Next_endpoint wasnt defined, all good now!
2 Errors:
The method which you have used here to fetch the data is outdated.
The endpoint here is missing you will find it in the graphcms in your account : settings/access/api access /endpoints
Remove the command
const graphqlAPI = process.env.NEXT_PUBLIC_GRAPHCMS_ENDPOINT
and the .env file associated with it.
After that use the following code:
import { gql } from 'graphql-request';
import { GraphQLClient } from 'graphql-request';
export const getPosts = async () => {
// new endpoint
const graphQLClient = new GraphQLClient(
endpoint // here add your endpoint
);
const query = gql`
query MyQuery {
postsConnection {
edges {
node {
author {
bio
name
id
photo {
url
}
}
createdAt
slug
title
excerpt
featuredImage {
url
}
categories {
name
slug
}
}
}
}
}
`
const result = await graphQLClient.request(query)
return result.PostsConnection;
}
Hope you could help me out with the following. I am trying to upload an excel file of ≈3MB from the client side to the API by first converting the file to a DataURL where after I send it as a string. This is working for smaller files, but it somehow seems to be blocking my larger files.
When I upload the file, I get the following error.
POST body missing. Did you forget use body-parser middleware?
I have done my own research and found more people with the same problem, though I could not find a solution.
https://github.com/apollographql/apollo-server/issues/792
This is the code I am using on the server side.
import { ApolloServer, gql } from 'apollo-server-micro'
type Props = {
_id: string
file: string[]
}
const typeDefs = gql`
type Mutation {
uploadFile(file: [String!]!): Boolean!
}
type Query {
readUpload(_id: String!): Boolean!
}
`
const resolvers = {
Mutation: {
async uploadFile(_: any, { file }: Props) {
console.log(file)
return true
}
},
Query: {
async readUpload(_: any, { _id }: Props) {
}
}
}
const apolloServer = new ApolloServer({
typeDefs,
resolvers
})
export const config = {
api: {
bodyParser: false
}
}
// Ensure to put a slash as the first character to prevent errors.
export default apolloServer.createHandler({ path: '/api/uploads' })
This is the code I am using on the client side.
import { useRef } from 'react'
import { uploadFile } from '../graphql/fetchers/uploads'
import { UPLOAD_FILE_QUERY } from '../graphql/queries/uploads'
export default function Upload() {
const inputElement = useRef<HTMLInputElement>(null)
const submitForm = (event: any) => {
event.preventDefault()
const files = inputElement.current?.files
if (files) {
const fileReader = new FileReader()
fileReader.onload = async () => {
try {
const result = fileReader.result as string
try {
console.log(result)
await uploadFile(UPLOAD_FILE_QUERY, { file: result })
} catch(error) {
console.log(error)
}
} catch(error) {
console.log(error)
}
}
fileReader.readAsDataURL(files[0])
}
}
return (
<form>
<input ref={inputElement} type='file'></input>
<button onClick={(event) => submitForm(event)}>Submit</button>
</form>
)
}
export const config = {
api: {
bodyParser: false
}
}
set bodyParser to true
Set bodyParser size limit
export const config = {
api: {
bodyParser: {
sizeLimit: '4mb' // Set desired value here
}
}
}
you try to send file as string in json? I think you should use multipart/form data on client side and parse them with special middleware on server side
On client special link converts request to multipart/formdata
full example https://github.com/jaydenseric/apollo-upload-examples
import { useMemo } from "react"
import { ApolloClient, createHttpLink, InMemoryCache } from "#apollo/client"
import { setContext } from "#apollo/client/link/context"
import { getUserTokenFromLocalStorage } from "../utils/utils"
import { createUploadLink } from "apollo-upload-client"
let apolloClient
const httpLink = createUploadLink({
uri: "/api/graphql",
headers: {
"keep-alive": "true",
},
})
const authLink = setContext((_, { headers }) => {
let token = getUserTokenFromLocalStorage()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
}
})
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema")
const { schema } = require("./schema")
return new SchemaLink({ schema })
} else {
return authLink.concat(httpLink)
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState)
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
I'm coming from React/Redux-land and am slowly getting acquainted to Svelte design patterns using stores.
Currently I'm curious to figure out if this is an acceptable pattern or if not, what is a better way to pursue this kind of communication. The basic premise is I want to be able to update multiple custom stores (which are using writable) from an adjacent store.
In the example below I have "loading.js" and "error.js" stores which would be used globally, commented out in the "session.js" store. I'd like to update these based on the result of an API request to create a session, in order to keep most of my heavy lifting out side of components.
My current thinking is that I'd pass each store needed through the "createSessionStore" function, but it feels a little clunky as it would highly depend on the declaration order of each store within "store.js"
The long term intention for wishing to do it this way is so I can add any kind of communication layer (such as web sockets) in to the mix and update the global loading or error store from any layer.
Thanks for the help.
Component.svelte
<script>
import { onMount } from "svelte";
import { error, loading, session } from "./store";
onMount(() => {
session.fetchSession();
});
</script>
{#if $loading}
<div>Loading...</div>
{/if}
{#if $error}
<div>Something went wrong: {$error}</div>
{/if}
store.js
import { createErrorStore } from "./error";
import { createLoadingStore } from "./loading";
import { createSessionStore } from "./session";
export const error = createErrorStore();
export const loading = createLoadingStore();
export const session = createSessionStore();
session.js
import { writable } from "svelte/store";
const INITIAL_STORE = {
token: null
};
export const createSessionStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const fetchSession = async () => {
// loading.set(true);
try {
const response = await fetch("MY_API_ENDPOINT/auth/token", {
method: "POST",
});
if (!response.ok) {
const err = new Error("Network response was not ok.");
// error.set(err);
// loading.set(false);
return;
}
const data = await response.json();
set(data.token);
// loading.set(false);
} catch (err) {
// error.set(err);
// loading.set(false);
}
};
const reset = () => {
set(initialStore);
};
return {
subscribe,
fetchSession,
reset
};
};
error.js
import { writable } from "svelte/store";
const INITIAL_STORE = false;
export const createErrorStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const reset = () => {
set(initialStore);
};
return {
subscribe,
set,
reset
};
};
loading.js
import { writable } from "svelte/store";
const INITIAL_STORE = false;
export const createLoadingStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const reset = () => {
set(initialStore);
};
return {
subscribe,
set,
reset
};
};
Interesting idea.
The problem here is that during the creation of the stores, not all of them exists yet. The only solution that I see for this is to add the references after creating them.
Here's my idea:
In the session.js:
import { writable } from "svelte/store";
const INITIAL_STORE = {
token: null
};
export const createSessionStore = (initialStore = INITIAL_STORE) => {
const { subscribe, set } = writable(initialStore);
const fetchSession = async () => {
// loading.set(true);
try {
otherStores.loading && otherStores.loading.set(true);
const response = await fetch("MY_API_ENDPOINT/auth/token", {
method: "POST",
});
if (!response.ok) {
const err = new Error("Network response was not ok.");
otherStores.error && otherStores.error.set(err);
otherStores.loading && otherStores.loading.set(false);
return;
}
const data = await response.json();
set(data.token);
} catch (err) {
otherStores.error && otherStores.error.set(err);
otherStores.loading && otherStores.loading.set(false);
}
};
const reset = () => {
set(initialStore);
};
let otherStores = {}
const setOtherStores = (stores) => {
otherStores=stores
};
return {
subscribe,
fetchSession,
reset,
setOtherStores
};
};
In the store.js:
import { createErrorStore } from "./error";
import { createLoadingStore } from "./loading";
import { createSessionStore } from "./session";
export const error = createErrorStore();
export const loading = createLoadingStore();
export const session = createSessionStore();
session.setOtherStores({error,loading})
You can use the same pattern for any of the other stores (if needed), and after creation pass them the references to the other stores.
im having this method in my component that makes an API call with axios, I checked the docs on how to test it but I cant seem to figure out how to do so. Any help would be appreciated.
loadContents() {
axios.get('/vue_api/content/' + this.slug).then(response => {
this.page_data = response.data.merchandising_page
}).catch(error => {
console.log(error)
})
},
You could use moxios or axios-mock-adapter to automatically mock Axios requests. I prefer the latter for developer ergonomics.
Consider this UserList component that uses Axios to fetch user data:
// UserList.vue
export default {
data() {
return {
users: []
};
},
methods: {
async loadUsers() {
const { data } = await axios.get("https://api/users");
this.users = data;
}
}
};
With axios-mock-adapter, the related test stubs the Axios GET requests to the API URL, returning mock data instead:
import axios from "axios";
const MockAdapter = require("axios-mock-adapter");
const mock = new MockAdapter(axios);
import { shallowMount } from "#vue/test-utils";
import UserList from "#/components/UserList";
describe("UserList", () => {
afterAll(() => mock.restore());
beforeEach(() => mock.reset());
it("loads users", async () => {
mock
.onGet("https://api/users")
.reply(200, [{ name: "foo" }, { name: "bar" }, { name: "baz" }]);
const wrapper = shallowMount(UserList);
await wrapper.vm.loadUsers();
const listItems = wrapper.findAll("li");
expect(listItems).toHaveLength(3);
});
});
demo