I am making a Reddit Clone. I have gotten to the point of implementation of SQL, so I am using graphQL and to make this easier Stepzen for endpoints. The following code is my Post Box for the reddit site along with graphQL, Mutations.ts and Queries.ts.
I have added Apollo Client and Toaster as well.
The problem seems to be occurring in my PostBox.tsx, It is coming from within my onSubmit function. As the Try/Catch runs, it triggers the error and catches it.
In the console my formData is a success and the console Log immediately inside the try works, but nothing else.
PostBox.tsx
import { useSession } from 'next-auth/react';
import React, { useState } from 'react';
import Avatar from './Avatar';
import { LinkIcon, PhotographIcon } from '#heroicons/react/outline';
import { useForm } from 'react-hook-form';
import { useMutation } from '#apollo/client';
import { ADD_POST, ADD_SUBREDDIT } from '../graphql/mutations';
import client from '../apollo-client';
import { GET_SUBREDDIT_BY_TOPIC } from '../graphql/queries';
import toast from 'react-hot-toast';
type FormData = {
postTitle: string;
postBody: string;
postImage: string;
subreddit: string;
};
function PostBox() {
const { data: session } = useSession();
// console.log(session);
//
const [addPost] = useMutation(ADD_POST);
const [addSubreddit] = useMutation(ADD_SUBREDDIT);
//
const [imageBoxOpen, setImageBoxOpen] = useState<boolean>(false);
const {
register,
setValue,
handleSubmit,
watch,
formState: { errors },
} = useForm<FormData>();
const onSubmit = handleSubmit(async (formData) => {
console.log(formData);
console.log('form data');
const notification = toast.loading('Creating new post...');
console.log(notification, 'notification');
try {
// query for subreddit topic
console.log('Test 1 Success');
//
// Error below: CL Test in line 42 is a success
const {
data: { getSubredditListByTopic },
} = await client.query({
query: GET_SUBREDDIT_BY_TOPIC,
variables: {
topic: formData.subreddit,
},
});
const subredditExists = getSubredditListByTopic.length > 0;
console.log('Test 2 Failed');
if (!subredditExists) {
// create subreddit
console.log('subreddit is new creating a new subreddit!');
console.log('Test 3 Failed');
const {
data: { insertSubreddit: newSubreddit },
} = await addSubreddit({
variables: {
topic: formData.subreddit,
},
});
console.log('Creating post...', formData);
const image = formData.postImage || '';
const {
data: { insertPost: newPost },
} = await addPost({
variables: {
body: formData.postBody,
image: image,
subreddit_id: newSubreddit.id,
title: formData.postTitle,
username: session?.user?.name,
},
});
console.log('New post added:', newPost);
} else {
// use existing
const image = formData.postImage || '';
const {
data: { insertPost: newPost },
} = await addPost({
variables: {
body: formData.postBody,
image: image,
subreddit_id: getSubredditListByTopic[0].id,
title: formData.postTitle,
username: session?.user?.name,
},
});
console.log('New post added', newPost);
}
setValue('postBody', '');
setValue('postImage', '');
setValue('postTitle', '');
setValue('subreddit', '');
toast.success('New Post Created!', {
id: notification,
});
} catch (error) {
toast.error('Whoops something went wrong!', {
id: notification,
});
}
});
queries.ts
import { gql } from '#apollo/client';
export const GET_SUBREDDIT_BY_TOPIC = gql`
query MyQuery($topic: String!) {
getSubredditListByTopic(topic: $topic) {
id
topic
created_at
}
}
`;
Mutations.ts
import { gql } from '#apollo/client';
export const ADD_POST = gql`
mutation MyMutation(
$body: String!
$image: String!
$subreddit_id: ID!
$title: String!
$username: String!
) {
insertPost(
body: $body
image: $image
subreddit_id: $subreddit_id
title: $title
username: $username
) {
body
created_at
id
image
subreddit_id
title
username
}
}
`;
export const ADD_SUBREDDIT = gql`
mutation MyMutation($topic: String!) {
insertSubreddit(topic: $topic) {
id
topic
created_at
}
}
`;
index.graphql ->There is more to this file but these are the only two I have changed, Supabase automatically built the rest
getSubredditList: [Subreddit]
#dbquery(
type: "postgresql"
schema: "public"
table: "subreddit"
configuration: "postgresql_config"
)
getSubredditListByTopic(topic: String!): [Subreddit]
#dbquery(
type: "postgresql"
schema: "public"
query: """
select * from "subreddit" where "topic" = $1
"""
configuration: "postgresql_config"
)
This is really upsetting me as I am not sure what is wrong. Thank you all for taking a look!
Related
I'm trying to setup a Apollo 3 local state with react. For an unknown reason my query does not work when I try to read a custom type (isAuthenticated query works well). Can you please help me to understand I literaly explored all the documentation and the link on internet about local state management.
Thank you
cache.js
import { InMemoryCache, makeVar, gql } from '#apollo/client'
export const isAuthenticatedVar = makeVar(!!localStorage.getItem('token'))
export const userVar = makeVar("")
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
isAuthenticated: {
read() {
return isAuthenticatedVar()
}
},
user: {
read() {
return userVar()
},
__typename: 'User',
}
}
}
}
})
export const IS_AUTHENTICATED = gql`
query IS_AUTHENTICATED {
isAuthenticated #client
}
`
export const GET_LOCAL_USER = gql`
query GET_LOCAL_USER {
user #client
}
`
typedej.js
import { gql } from '#apollo/client'
export const typeDef = gql`
extend type User {
id: ID!
name: String!
surname: String!
email: String!
password: String!
phone: String!
adress: Adress!
isAdmin: Boolean!
isCoach: Boolean!
isManager: Boolean!
}
extend type Query {
isAuthenticated: Boolean!
user: User!
}
`
export default typeDef
test.js
const login = (email, password) => {
const emailLower = email.toLowerCase()
client.query({
query: LOGIN,
variables: { email: emailLower, password: password }
})
.then(result => {
console.log(result)
localStorage.setItem('token', result.data.login.token)
localStorage.setItem('userId', result.data.login.user.id)
isAuthenticatedVar(true)
userVar({ ...result.data.login.user})
console.log('USER VAR SETUP')
///Console.log show that userVar contains an object with all User data
console.log(userVar())
var test
try{
//test return NULL object
test = client.readQuery({ query: GET_LOCAL_USER}).user
}catch(error) {
console.log(error)
}
console.log('FETCH QUERY userVar')
console.log(test)
navigate("/")
})
.catch(error => {
console.log(error)
})
}
Tried several ways but can't find how to resolve this. I don't understand why documentation don't explain how to use with Object Type
just wanna have my custom credential provider which authenticate the entered username and password with Firebase Authentication on sign in page
pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"
import { getDatabase } from "firebase/database"
import { DB } from "../../../constants/firebase"
import { FirebaseAdapter } from "#next-auth/firebase-adapter"
import * as firestoreFunctions from "firebase/firestore"
import CredentialsProvider from "next-auth/providers/credentials"
export default NextAuth({
session: {
strategy: "database",
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: {
label: "Username",
type: "text",
placeholder: "somebody#gmail.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const database = getDatabase()
console.log(database)
const user = {
id: 1,
usename: "j",
password: "123456789",
}
if (
credentials?.username === user.usename &&
credentials.password === "123456789"
) {
return user
}
return null
},
}),
],
adapter: FirebaseAdapter({
db: DB,
...firestoreFunctions,
}),
// pages: {
// signIn: "/auth/signin",
// signOut: "/auth/signout",
// error: "/auth/error", // Error code passed in query string as ?error=
// verifyRequest: "/auth/verify-request", // (used for check email message)
// newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest)
// },
callbacks: {
async jwt({ token, user }) {
if (user) {
token.email = user.email
}
return token
},
async session({ session, token, user }) {
if (token) {
session.user!.email = token.email
}
return session
},
redirect({ url, baseUrl }) {
if (url.startsWith(baseUrl)) return url
else if (url.startsWith("/"))
return new URL(url, baseUrl).toString()
return baseUrl
},
},
})
firebase.ts
import { initializeApp, getApp, getApps } from "firebase/app"
import { getAnalytics } from "firebase/analytics"
import { getFirestore } from "#firebase/firestore"
import { getStorage } from "#firebase/storage"
import getFirebaseObject from "./firebaseConfig"
const app = !getApps.length ? initializeApp(getFirebaseObject()) : getApp()
const DB = getFirestore(app)
const storages = getStorage()
const analytics = getAnalytics(app)
export { app, DB, analytics, storages }
as you see
const user = {
id: 1,
usename: "j",
password: "123456789",
}
in fact except of these static data wanna search and get right user info from the Firebase
I know there are a some other way of doing this but I like working with next-auth for last change wanna make sure there's a spot of light in this was ;)
i found this public repository where the author does something similar to what you want to achieve, which is create a custom token with your database credentials.
May be this repository can help you. It has a few errors, but it gave me a general idea about what to do, as I had a similar case.
try {
if (user !== null) {
await customTokenSignIn(user.id, user.email);
(await getUser(user.id)) ??
(await createUser(toReqUser(user, account)));
const data = await getUser(user.id);
setResUser(user, data as ResUser);
return true;
}
return false;
} catch (e) {
console.error(e);
return false;
}
const customTokenSignIn = async (id: string, email: string) => {
const hash = toHash(id);
const customToken = await adminAuth.createCustomToken(hash);
await auth.signInWithCustomToken(customToken).then((res) => {
res.user?.updateEmail(email);
});
await adminAuth.setCustomUserClaims(hash, { sid: id });
await createUserToken({ id: id, firebaseUid: hash });
};
In the mutation below I'm updating the avatarId for a person and I would like the new imageUrl to propagate everywhere it's needed, namely the <UserAvatar /> component which can be in a lot of components on the same page.
I know this new URL before ever hitting the UpdatePersonMutation so I'm not sure what I'm doing wrong.
Do I need to involve the store and / or use optimisticUpdater, and / or use subscriptions to get a new avatar image to show up everywhere instantly?
import { commitMutation } from 'react-relay';
import graphql from 'babel-plugin-relay/macro';
import modernEnvironment from '../Environment';
const mutation = graphql`
mutation UpdatePersonMutation($input: UpdatePersonInput!) {
updatePerson(input: $input) {
imageByAvatarId {
id
imageUrl // I know this before I ever reach this mutation
}
person {
id
avatarId
}
}
}
`;
const commit = (payload, callback) => {
const input = {
id: payload.id,
personPatch: {
avatarId: payload.avatarId,
},
};
return commitMutation(modernEnvironment, {
mutation,
variables: {
input,
},
optimisticResponse: {
updatePerson: {
imageByAvatarId: {
id: payload.imageId,
imageUrl: payload.imageUrl,
},
person: {
id: payload.id,
avatarId: payload.avatarId,
},
},
},
onCompleted: response => {
callback(response);
},
onError: error => {
console.log(error);
},
});
};
export default commit;
what changes need to perform the code:
const data = client.mutate({
mutation: (Query)
});
let Query = gql`
mutation{
create(input:{
department:"Tester",
code:"Tester"
}
)
{
code,
details,
description,
}
}`
console.log(data, "data")
how to pass input dynamically? I have my input as:
var department = {
department:"Tester",
code:"Tester"
}
I haven't tested it, but it will work.
in react apollo-client
import { gql, useMutation } from '#apollo/client';
const Query = gql`
mutation Create($department: String!, $code: String!) {
create(department: $department, code: $code) {
code
details
description
}
}
`;
const [create] = useMutation(Query);
create({ variables: { department: department_value, code: code_value } });
other way
const departmentValue = "Tester"
const codeValue = "Tester"
client.mutate({
variables: { department: departmentValue, code:codeValue },
mutation: gql`
mutation Create($department: String!, $code: String!){
create(department: $department, code: $code){
code
details
description
}
}
`,
})
.then(result => { console.log(result) })
.catch(error => { console.log(error) });
I am trying to make a Social app using MongoDB, Express, React, Node, Graphql with Apollo , I am following a video from freecodecamp : Link to the video
I am using pubsub to achieve a realtime functionality, like when ever a new post has been added it should show up in the home page.
I might have missed the code of some file that's why
I am dropping a link to my github repo containing the whole project : Link to the github repo
Home.js:
import React, { useContext } from 'react';
import { useQuery } from '#apollo/react-hooks';
import { Grid, Transition } from 'semantic-ui-react';
import { AuthContext } from '../context/auth';
import PostCard from '../components/PostCard';
import PostForm from '../components/PostForm';
import { FETCH_POSTS_QUERY } from '../util/graphql';
function Home() {
const { user } = useContext(AuthContext);
const { data, loading, error } = useQuery(FETCH_POSTS_QUERY);
if(data) {
const { posts } = data || [];
return (
<Grid columns={3}>
<Grid.Row className="page-title">
<h1>Recent Posts</h1>
</Grid.Row>
<Grid.Row>
{user && (
<Grid.Column>
<PostForm />
</Grid.Column>
)}
{loading && <h1>Loading posts..</h1>}
{data && (
<Transition.Group>
{posts &&
posts.map((post) => (
<Grid.Column key={post.id} style={{ marginBottom: 20 }}>
<PostCard post={post} />
</Grid.Column>
))}
</Transition.Group>
)}
</Grid.Row>
</Grid>
);
}
if(error) {
return error.message;
}
}
export default Home;
I render all the posts in Home.js
Resolvers => posts.js :
const { AuthenticationError, UserInputError } = require('apollo-server');
const Post = require('../../models/Post');
const checkAuth = require('../../util/check-auth');
module.exports = {
Query: {
async getPosts() {
try {
const posts = await Post.find().sort({ createdAt: -1 });
return posts;
} catch (err) {
throw new Error(err);
}
},
async getPost(_, { postId }) {
try {
const post = await Post.findById(postId);
if (post) {
return post;
} else {
throw new Error('Post not found');
}
} catch (err) {
throw new Error(err);
}
}
},
Mutation: {
async createPost(_, { body }, context) {
const user = checkAuth(context);
if (body.trim() === '') {
throw new Error('Post body must not be empty');
}
const newPost = new Post({
body,
user: user.id,
username: user.username,
createdAt: new Date().toISOString()
});
const post = await newPost.save();
context.pubsub.publish('NEW_POST', {
newPost: post,
}).then(()=>{
console.log("working")
});
return post;
},
async deletePost(_, { postId }, context) {
const user = checkAuth(context);
try {
const post = await Post.findById(postId);
if (user.username === post.username) {
await post.delete();
return 'Post deleted successfully';
} else {
throw new AuthenticationError('Action not allowed');
}
} catch (err) {
throw new Error(err);
}
},
async likePost(_, { postId }, context) {
const { username } = checkAuth(context);
const post = await Post.findById(postId);
if (post) {
if (post.likes.find((like) => like.username === username)) {
// Post already likes, unlike it
post.likes = post.likes.filter((like) => like.username !== username);
} else {
// Not liked, like post
post.likes.push({
username,
createdAt: new Date().toISOString()
});
}
await post.save();
return post;
} else throw new UserInputError('Post not found');
}
},
Subscription: {
newPost: {
subscribe: (_, __, { pubsub }) => pubsub.asyncIterator('NEW_POST')
}
}
};
Resolvers => index.js :
const postsResolvers = require('./posts');
const usersResolvers = require('./users');
const commentsResolvers = require('./comments');
module.exports = {
Post: {
likeCount: (parent) => parent.likes.length,
commentCount: (parent) => parent.comments.length
},
Query: {
...postsResolvers.Query
},
Mutation: {
...usersResolvers.Mutation,
...postsResolvers.Mutation,
...commentsResolvers.Mutation
},
Subscription: {
...postsResolvers.Subscription
}
};
tyDefs.js :
const { gql } = require('apollo-server');
module.exports = gql`
type Post {
id: ID!
body: String!
createdAt: String!
username: String!
comments: [Comment]!
likes: [Like]!
likeCount: Int!
commentCount: Int!
}
type Comment {
id: ID!
createdAt: String!
username: String!
body: String!
}
type Like {
id: ID!
createdAt: String!
username: String!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: String!
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getPosts: [Post]
getPost(postId: ID!): Post
}
type Mutation {
register(registerInput: RegisterInput): User!
login(username: String!, password: String!): User!
createPost(body: String!): Post!
deletePost(postId: ID!): String!
createComment(postId: String!, body: String!): Post!
deleteComment(postId: ID!, commentId: ID!): Post!
likePost(postId: ID!): Post!
}
type Subscription {
newPost(
id: ID!,
body: String!,
createdAt: String!,
username: String!,
likeCount: Int!,
commentCount: Int!): Post!
}
`;
This is the index.js file of my server (backend) :
const { ApolloServer, PubSub } = require('apollo-server');
const mongoose = require('mongoose');
const typeDefs = require('./graphql/typeDefs');
const resolvers = require('./graphql/resolvers');
const { MONGODB } = require('./config.js');
const pubsub = new PubSub();
const PORT = process.env.port || 5000;
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req, pubsub })
});
mongoose
.connect(MONGODB, { useUnifiedTopology: true , useNewUrlParser: true })
.then(() => {
console.log('MongoDB Connected');
return server.listen({ port: PORT });
})
.then((res) => {
console.log(`Server running at ${res.url}`);
})
.catch(err => {
console.error(err)
})
I am intialising the pubsub in the index.js file only,
This is the error I am getting :
TypeError: data.getPosts is not iterable 0.chunk.js:124321
Screen shot of the error :
This error is one problem the other is that whenever I click submit the same error occurs, but the post appears when I run the subscription in Graphql Playground not in the client page but when I refresh the page the post appears there.
In case you're using Apollo v3, and you're having some issues with caching while adding posts:
const [createPost, { error }] = useMutation(CREATE_POST_MUTATION, {
variables: values,
update(proxy, result) {
const data = proxy.readQuery({
query: FETCH_POSTS_QUERY,
});
proxy.writeQuery({
query: FETCH_POSTS_QUERY,
data: {
getPosts: [result.data.createPost, ...data.getPosts],
},
});
values.body = "";
},
onError(err) {
<== also add this so the page doesn't break
return err;
},
});