import { GraphQLClient, gql } from "graphql-request";
import { RichText } from "#graphcms/rich-text-react-renderer";
import { useEffect } from "react";
import Prism from "prismjs";
import "prismjs/plugins/line-numbers/prism-line-numbers";
import "prismjs/themes/prism-tomorrow.css";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
export default function Demo({ posts }) {
useEffect(() => {
Prism.highlightAll();
}, []);
return (
<section className="prose m-auto">
<h1>{posts.title}</h1>
<RichText
content={posts.content.json}
renderers={{
code_block: ({ children }) => {
return (
<pre className="line-numbers language-none">
<code>{children}</code>
</pre>
);
},
}}
/>
</section>
);
}
export const getServerSideProps = async (context) => {
const endPoint =
"https://randomenpoint.com/api";
const slug = "random slug";
const query = gql`
query ($slug: String!) {
posts(where: { slug: $slug }) {
id
publishedAt
createdAt
slug
title
updatedAt
content {
json
}
}
}
`;
const client = new GraphQLClient(endPoint);
const { posts } = await client.request(query, { slug });
return {
props: {
posts,
},
};
};
On my above code, when I'm adding the line numbers className inside pre tag I'm getting hydration error, what's wrong here? But without extra className inside pre tag, my code works fine. But it returns a normal code block in the browser. I want to use prismjs for styling. I'm using Nextjs,GraphCMS and tailwindCSS
Related
I'm following Javascript Everywhere tutorial. Building note app using reactjs and graphQL. Now I'm working with CRUD. Query and Create Note (mutation) works perfectly, but when i'm doing update mutation, it's return 400 error Bad request. Here's my code.
editNote pages
editNote.js
import React from 'react';
import { useMutation, useQuery } from '#apollo/client';
// import the NoteForm component
import NoteForm from '../components/NoteForm';
import { GET_NOTE, GET_ME } from '../gql/query';
import { EDIT_NOTE } from '../gql/mutation';
const EditNote = props => {
// store the id found in the url as a variable
const id = props.match.params.id;
// define our note query
const { loading, error, data } = useQuery(GET_NOTE, { variables: { id } });
// fetch the current user's data
const { data: userdata } = useQuery(GET_ME);
// define our mutation
const [editNote] = useMutation(EDIT_NOTE, {
variables: {
id
},
onCompleted: () => {
props.history.push(`/note/${id}`);
}
});
// if the data is loading, display a loading message
if (loading) return 'Loading...';
// if there is an error fetching the data, display an error message
if (error) return <p>Error!</p>;
// if the current user and the author of the note do not match
if (userdata.me.id !== data.note.author.id) {
return <p>You do not have access to edit this note</p>;
}
// pass the data and mutation to the form component
return <NoteForm content={data.note.content} action={editNote} />;
};
export default EditNote;
NoteForm for updating note
NoteForm.js
import React, { useState } from 'react';
import styled from 'styled-components';
import Button from './Button';
const Wrapper = styled.div`
height: 100%;
`;
const Form = styled.form`
height: 100%;
`;
const TextArea = styled.textarea`
width: 100%;
height: 90%;
`;
const NoteForm = props => {
// set the default state of the form
const [value, setValue] = useState({ content: props.content || '' });
// update the state when a user types in the form
const onChange = event => {
setValue({
...value,
[event.target.name]: event.target.value
});
};
return (
<Wrapper>
<Form
onSubmit={e => {
e.preventDefault();
props.action({
variables: {
...value
}
});
}}
>
<TextArea
required
type="text"
name="content"
placeholder="Note content"
value={value.content}
onChange={onChange}
/>
<Button type="submit">Save</Button>
</Form>
</Wrapper>
);
};
export default NoteForm;
and here is my mutation updateNote.
mutation.js
const EDIT_NOTE = gql`
mutation updateNote($id: ID!, $content: String!) {
updateNote(id: $id, content: $content) {
id
content
createdAt
favoriteCount
favoritedBy {
id
username
}
author {
username
id
}
}
}
`;
Error information.
I'm just beginner using apollo client and graphql, i don't have any idea. Any help will be appreciated. Thankyou.
NoteForm.js
...
const onChange = event => {
setValue({
...value,
[event.target.name]: event.target.value /// problem here
});
};
...
you should pass correct variables, in gql file I see you declare $id and $content, make sure you pass "id" to mutation
example:
editNote.js
...
return <NoteForm id={id} content={data.note.content} action={editNote} />;
...
NoteForm.js
...
const [value, setValue] = useState({
id: props.id,
content: props.content || ''
});
...
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
I'm working on react shopping cart using react-redux and My add to cart Functionality is not working. I've tried a lot with multiple approaches but those did not work. I'm unable to get data into my CartItem. Did stack over flow tried multiple things but still not worthy.
this is my CartActions.js
import React from "react";
import axios from "axios";
import { ADD_CART_ITEM } from "../constant/cartConstant";
const addToCart = (id, qty) => async (dispatch, getState) => {
const { data } = await axios.get(`/http://127.0.0.1:8000/products/${id}`);
console.log(data, "<<<<<<DATA");
dispatch({
type: ADD_CART_ITEM,
payload: {
product: data.id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
},
});
localStorage.setItem(
"cartItems",
JSON.stringify(getState().CartReducer.cartItems)
);
};
export default addToCart;
this is my Cart.js
import React, { useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { useLocation } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux'
import addToCart from '../actions/CartActions'
function Cart() {
const { id } = useParams();
const { search } = useLocation();
const ProductID = id;
const qty = search ? search.split("=") : 1
console.log(qty, ">>>QTY");
const dispatch = useDispatch()
console.log(ProductID, ">>>>PROduct ID");
useEffect(() => {
if (ProductID) {
dispatch(addToCart(ProductID, qty))
}
console.log("Hello world");
}, [dispatch, ProductID, qty])
return (
<div>Cart</div>
)
}
export default Cart
this is my CartReducer.js
import React from "react";
import { ADD_CART_ITEM } from "../constant/cartConstant";
function cartReducer(state = { cartItems: [] }, actions) {
switch (actions.type) {
case ADD_CART_ITEM:
const Item = actions.payload;
const exitsItem = state.cartItems((x) => x.product === Item.product);
if (exitsItem) {
return {
...state,
cartItems: state.cartItems.map((x) =>
x.product === exitsItem.product ? Item : x
),
};
} else {
return {
...state,
cartItems: [...state.cartItems, Item],
};
}
default:
return state;
}
}
export default cartReducer;
I have tried multiple approaches but those did not help me
Your API call URL has an unexpected forward-slash ('/') in the beginning.
Correct Pattern.
const { data } = await axios.get(`http://127.0.0.1:8000/products/${id}`);
Or if you want to use /products/${id} this pattern then you can add proxy into package.json like this.
//package.json
...
"proxy": "http://127.0.0.1:8000", // add this as proxy
...
After adding this you can use URLs patterns without using the hostname
http://127.0.0.1:8000. in all over the app.
e.g:
const { data } = await axios.get(`/products/${id}`);
This should work now. If still, it gives an error 404 then might be an issue with your URLs patterns in the urls.py in the Django URLs.
I would like to create a category page containing all tags added to articles. When clicking on a tag it should show a page with all articles containing that specific tag.
I'm using Next.js, SSG, and fetching the articles from Contentful with the following GraphQL query:
export async function getArticles() {
const articlesQuery = gql`
{
articleCollection {
items {
title
slug
excerpt
date
contentfulMetadata {
tags {
name
id
}
}
featuredImage {
title
url
width
height
}
}
}
}
`;
return graphQLClient.request(articlesQuery);
}
The contentfulMetadata is where the tags come from:
contentfulMetadata {
tags {
name
id
}
}
I've then created a CategorySection component:
import styled from "styled-components";
import { getArticles } from "../../utils/contentful";
import Link from "next/link";
export async function getStaticProps() {
const categories = await getArticles();
return {
props: {
categories: categories.articleCollection.items,
},
};
}
export default function CategorySection({ categories }) {
return (
<Wrapper>
<ContentWrapper>
<CategoryWrapper>
{categories.map((category) => {
return (
<Link href={`/articles/categories/${category.tags.name}`}>
<Categories key={category.tags.id}>
{category.tags.name}
</Categories>
</Link>
);
})}
</CategoryWrapper>
</ContentWrapper>
</Wrapper>
);
}
The CategorySection component gives me the following error message:
TypeError: Cannot read property 'map' of undefined"
Below is my /pages/articles/categories/[slug].jsx file:
import styled from "styled-components";
import { getArticles, getArticle } from "../../utils/contentful";
export async function getStaticPaths() {
const data = await getArticles();
return {
paths: data.articleCollection.items.map((article) => ({
params: { slug: article.contentfulMetadata.tags.id },
})),
fallback: false,
};
}
export async function getStaticProps(context) {
const data = await getArticle(context.params.slug);
return {
props: { article: data.articleCollection.items[0] },
};
}
export default function Category({ article }) {
return <h1>{article.contentfulMetadata.tags.name}</h1>;
}
I'm getting the error below:
Error: A required parameter (slug) was not provided as a string in
getStaticPaths for /articles/categories/[slug]
Can you help me understand how I create dynamic pages from my categories (tags)?
getStaticProps can only be used in page components, so in your case it'll be completely ignored in your CategorySection component. You'll need to fetch the data at the page level and pass it to the component where you want to use it.
One possible solution is to simply pass the data as a prop down to the desired component.
// pages/article
import { getArticles } from "../../utils/contentful";
export async function getStaticProps() {
const categories = await getArticles();
return {
props: {
categories: categories.articleCollection.items
}
};
}
export default function ArticlePage({ categories }) {
return (
<CategorySection categories={categories} />
);
}
I am getting loading state only and data as undefined in testing. I don't know why I am following everything in the given example. Please help.
Testing file. When i am waiting thsi line toexecute await wait(() => getByTestId('edit-category'));. It is giving response data of query as undefined.
Error: TypeError: Cannot read property 'getCategory' of undefined
Line 34 on editConatinerCategory.tsx => category={data!.getCategory!}
import React from 'react';
import gql from 'graphql-tag';
import { cleanup, wait } from 'react-testing-library';
import { customRender } from '../../../test-utils/customRender';
import { EditCategoryContainer } from './Container';
afterEach(() => {
cleanup();
console.error;
});
console.error = jest.fn();
const getCategoryMock = {
request: {
query: gql`
query getCategory($id: Int!) {
getCategory(id: $id) {
id
name
active
position
}
}
`,
variables: {
id: 1
}
},
result: {
data: {
getCategory: {
id: 1,
name: 'category',
active: true,
position: 1
}
}
}
};
describe('create edit category module', () => {
test('Rendering correct', async () => {
const { container, debug, getByTestId } = customRender(<EditCategoryContainer />, [
getCategoryMock
]);
await wait(() => getByTestId('edit-category'));
await wait(() => expect(container).toMatchSnapshot());
//Getting this TypeError: Cannot read property 'getCategory' of undefined. Because i am data as undefined from my query response
});
});
CustomRender.tsx
import React from 'react';
import { render } from 'react-testing-library';
import { MockedProvider, MockedResponse } from 'react-apollo/test-utils';
import { Router, Switch } from 'react-router-dom';
import { createMemoryHistory } from 'history';
export const customRender = (
node: JSX.Element | null,
mocks?: MockedResponse[],
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] })
} = {}
) => {
return {
history,
...render(
<MockedProvider mocks={mocks} addTypename={false}>
<Router history={history}>
<Switch>{node}</Switch>
</Router>
</MockedProvider>
)
};
};
EditCategoryContainer.tsx
import React from 'react';
import { withRouter } from 'react-router';
import { Spin } from 'antd';
import {
AddCategoryComponent,
GetCategoryComponent
} from '../../../generated/graphql';
import { EditCategory } from './Edit';
import { LoadingComponent } from '../../../components/LoadingComponent';
export const EditCategoryContainer = withRouter(({ history, match }) => {
const id: number = parseInt(match.params.id, 10);
return (
<GetCategoryComponent
variables={{
id
}}
>
{({ data, loading: getCategoryLoading }) => {
console.log(getCategoryLoading, 'getCategoryLoading');
if (getCategoryLoading) {
return <LoadingComponent />;
}
if (data && !data.getCategory) {
return <div>Category not found!</div>;
}
console.log(data);
return (
<AddCategoryComponent>
{(addCategory, { loading, error }) => {
return (
<EditCategory
data-testid="edit-category"
category={data!.getCategory!}
loading={loading || getCategoryLoading}
onSubmit={values => {
addCategory({ variables: values }).then(() => {
history.push('/dashboard/categories');
});
}}
/>
);
}}
</AddCategoryComponent>
);
}}
</GetCategoryComponent>
);
});
Edit:
I tried #mikaelrs solution which is passed match. But it is not working. I also tried to pass id:1 as fixed. But it is still giving error.
<GetCategoryComponent
variables={{
id:1
}}
>
...rest of code.
</GetCategoryComponent>
This is not working. My query without veriable is working fine. Mutation is also working fine. I am having only problem with this. When i have to pass like varible like this.
What I do to wait for the loading state of the MockedProvider to pass is to use the wait function from waait. This is actually what Apollo recommends as well.
So in your test you would do:
import React from 'react';
import gql from 'graphql-tag';
import { cleanup } from 'react-testing-library';
import wait from 'waait'
import { customRender } from '../../../test-utils/customRender';
import { EditCategoryContainer } from './Container';
afterEach(() => {
cleanup();
});
const getCategoryMock = {
request: {
query: gql`
query getCategory($id: Int!) {
getCategory(id: $id) {
id
name
active
position
}
}
`,
variables: {
id: 1
}
},
result: {
data: {
getCategory: {
id: 1,
name: 'category',
active: true,
position: 1
}
}
}
};
describe('create edit category module', () => {
test('Rendering correct', async () => {
const { container, debug } = customRender(<EditCategoryContainer />, [
getCategoryMock
]);
await wait(0);
// Your loading state should be false after this, and your component should
// get it's data from apollo for you to do any assertion you would like to
// after this point. To see that the component is rendered with data invoke
// the debug function from react-testing-library after this point
debug();
expect(container).toMatchSnapshot()
});
});
Another solution is to use react-testing-librarys wait function to wait for an element that would be present after the loading state switches to true.
For instance
describe('create edit category module', () => {
test('Rendering correct', async () => {
const { container, debug, queryByText } = customRender(<EditCategoryContainer />, [
getCategoryMock
]);
await wait(()=> queryByText("Some Data"));
// Your loading state should be false after this, and your component should
// get it's data from apollo for you to do any assertion you would like to
// after this point
expect(container).toMatchSnapshot()
});
});
I faced a similar issue. Here is how I resolved my issue.
First, wait for the query to resolve, as recommended by #mikaelrs and the docs:
await new Promise(resolve => setTimeout(resolve, 0));
After doing that, the loading property was false, but data was still undefined. I discovered that my mock result object was missing a property. Once I added that missing property to the mock result, the data was populated as expected.