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 || ''
});
...
Related
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
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;
Hi i am new developer testing platform. I have a problem but I did not find a solution or work it with correct way. I am trying to login component test with to parameter by Inputs. Firstly I filled these are userEvent.type. After I am clicking my button. And when I was waiting my method that call by onSubmitForTest in one time , I am facing an error like fallowing image.
What is the reason of this ? How can I solve my problem ? Thanks for your helps.
My Login.tsx component:
import React, { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import Input from "../../components/Input";
import InputPassword from "../../components/Input/InputPassword";
import ButtonLoading from "../../components/Button/ButtonLoading";
import { GetLoginInfo, ILoginRequest } from "../../store/actions/loginActions";
interface ILoginState {
emailorUsername: string;
password: string;
}
const initialState = {
emailorUsername: "",
password: "",
};
interface IProps {
onSubmitForTest: (items: any) => void
}
const Login: FC<IProps> = ({ onSubmitForTest }) => {
const { t } = useTranslation();
const [state, setstate] = useState<ILoginState>(initialState);
const onChange = (key: string, value: string | number) => {
setstate({ ...state, [key]: value });
};
const handleLogin = async () => {
const loginRequest: ILoginRequest = {
emailOrUsername: state.emailorUsername,
password: state.password,
returnUrl: "",
};
const response = await GetLoginInfo(loginRequest);
if (response.isSucceed) { } else { }
};
const renderLoginPart = () => {
return (
<div className="flex">
<Input
name="emailorUsername"
label={t("emailorUsername")}
value={state.emailorUsername}
onChange={(val: any) => onChange("emailorUsername", val)}
/>
<InputPassword
name="password"
label={t("password")}
value={state.password}
onChange={(val: any) => onChange("password", val)}
/>
<ButtonLoading
text={t("login")}
variant="contained"
onClick={() => {
if (onSubmitForTest) {
const loginRequestItemForTest = {
emailOrUsername: "testUsername",
password: "testPassword",
};
onSubmitForTest(loginRequestItemForTest)
}
handleLogin()
}}
dataTestid={"login-button-element"}
/>
</div>
);
};
return <div className="">{renderLoginPart()}</div>;
};
export default Login;
My index.test.js :
import React from 'react'
import { render, screen, waitFor } from "#testing-library/react"
import LoginPage from "../index"
import userEvent from "#testing-library/user-event"
const onSubmit = jest.fn()
beforeEach(()=>{
const { } = render(<LoginPage />)
onSubmit.mockClear()
})
test('Login form parametre olarak doğru data gönderme testi', async () => {
const eMail = screen.getByTestId('text-input-element')
const password = screen.getByTestId('password-input-element')
userEvent.type(eMail, "fillWithTestUsername")
userEvent.type(password, "fillWithTestPassword")
userEvent.click(screen.getByTestId('login-button-element'))
await waitFor(()=>{
expect(onSubmit).toHaveBeenCalledTimes(1)
})
})
beforeEach(()=>{
render(<LoginPage onSubmitForTest={onSubmit} />)
})
Please try doing this in beforeEach. If this still doesn't work you can try replacing toHaveBeenCalledTimes with toBeCalledTimes like below
await waitFor(()=>{
expect(onSubmit).toBeCalledTimes(1)
})
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
I have the mutation below in a React Component. Im going to need the same mutation in multiple components and on different pages.
How can I reuse my mutation code without repeating it?
This example isn't that complex but some queries use optimistic UI and write to the store.
import React from 'react';
import { graphql, compose } from 'react-apollo';
import { gql } from 'apollo-boost';
const JoinLocation = props => {
if (props.ME.loading) return null;
const { locationMachineName } = props;
const me = props.ME.me;
const submit = () => {
props
.JOIN_LOCATION({
variables: {
userId: me.id,
locationMachine: locationMachineName,
},
})
.catch(err => {
console.error(err);
});
};
return <button onClick={() => submit()}>Join location</button>;
};
const ME = gql`
query me {
me {
id
}
}
`;
const JOIN_LOCATION = gql`
mutation joinLocation($userId: ID!, $locationId: ID!) {
joinLocation(userId: $userId, locationId: $locationId) {
id
}
}
`;
export default compose(
graphql(JOIN_LOCATION, { name: 'JOIN_LOCATION' }),
graphql(ME, { name: 'ME' }),
)(JoinLocation);
Create a higher-order component (HOC) for the mutation/query that contains the gql options and optimistic UI logic:
const JOIN_LOCATION = gql`
mutation joinLocation($userId: ID!, $locationId: ID!) {
joinLocation(userId: $userId, locationId: $locationId) {
id
}
}
`;
export const withJoinLocation = component => graphql(JOIN_LOCATION, { name: 'JOIN_LOCATION' })(component);
Then wrap your different components with it.
export default withJoinLocation(JoinLocation);
UPDATE: Based on your below comment, if you want to encapsulate the whole submit logic and not just the mutation as stated in your question, you can use a render prop like so:
import React from 'react';
import { graphql, compose } from 'react-apollo';
import { gql } from 'apollo-boost';
const JoinLocation = props => {
if (props.ME.loading) return null;
const { locationMachineName } = props;
const me = props.ME.me;
const submit = () => {
props
.JOIN_LOCATION({
variables: {
userId: me.id,
locationMachine: locationMachineName,
},
})
.catch(err => {
console.error(err);
});
};
return props.render(submit);
};
const ME = gql`
query me {
me {
id
}
}
`;
const JOIN_LOCATION = gql`
mutation joinLocation($userId: ID!, $locationId: ID!) {
joinLocation(userId: $userId, locationId: $locationId) {
id
}
}
`;
export default compose(
graphql(JOIN_LOCATION, { name: 'JOIN_LOCATION' }),
graphql(ME, { name: 'ME' }),
)(JoinLocation);
Now any component can consume the reusable submit logic. Assume you name the above component JoinLocation.js:
import JoinLocation from './JoinLocation';
const Comp = () => {
return <JoinLocation render={submit => <button onClick={() => submit()}>Join location</button>}/>
}