0
votes

I’m refactoring a Google Books app from a Restful API to GraphQL, and I am stuck on a mutation not behaving the way I expect.

When a user fills out the form found on Signup.js the Mutation ADD_USER should create a user within Mongoose, this user should have a JWT token assigned to them, and user should be logged in upon successful execution of the Mutation.

Actions observed: • Mutation is being fired off from the front end. When I open developer tools in the browser I can see the Username, Email and Password being passed as variables. • I have tried console logging the token, and keep getting an undefined return • When I try to run the mutation in the GraphQL sandbox I get a null value returned. • When I console log the args in resolvers.js no value appears on the console, which tells me the request is not reaching the resolver.

SignupForm.js (React FE Page)

import React, { useState } from "react";
import { Form, Button, Alert } from "react-bootstrap";
import { useMutation } from "@apollo/client";
import { ADD_USER } from "../utils/mutations";
import Auth from "../utils/auth";

const SignupForm = () => {
  // set initial form state
  const [userFormData, setUserFormData] = useState({
    username: "",
    email: "",
    password: "",
  });
  // set state for form validation
  const [validated] = useState(false);
  // set state for alert
  const [showAlert, setShowAlert] = useState(false);

  const [addUser] = useMutation(ADD_USER);

  const handleInputChange = (event) => {
    const { name, value } = event.target;
    setUserFormData({ ...userFormData, [name]: value });
  };

  const handleFormSubmit = async (event) => {
    event.preventDefault();

    // check if form has everything (as per react-bootstrap docs)
    const form = event.currentTarget;
    if (form.checkValidity() === false) {
      event.preventDefault();
      event.stopPropagation();
    }

    try {
      ///Add user is not returning data. payload is being passed as an object

      const response = await addUser({
        variables: { ...userFormData },
      });

      if (!response.ok) {
        throw new Error("OH NO!SOMETHING WENT WRONG!");
      }

      const { token, user } = await response.json();
      console.log(user);
      Auth.login(token);
    } catch (err) {
      console.error(err);
      setShowAlert(true);
    }

    setUserFormData({
      username: "",
      email: "",
      password: "",
    });
  };

Mutation.js

export const ADD_USER = gql`
  mutation addUser($username: String!, $email: String!, $password: String!) {
    addUser(username: $username, email: $email, password: $password) {
      token
      user {
        username
        email
      }
    }
  }
`;

typeDefs.js

const { gql } = require("apollo-server-express");

const typeDefs = gql`
  input SavedBooks {
    authors: [String]
    description: String
    bookId: String
    image: String
    link: String
    title: String
  }
  type Books {
    authors: [String]
    description: String
    bookId: ID
    image: String
    link: String
    title: String
  }
  type User {
    _id: ID
    username: String
    email: String
    password: String
    savedBooks: [Books]
  }
  type Auth {
    token: ID!
    user: User
  }
  type Query {
    me: User
  }
  type Mutation {
    ##creates a user profile through the Auth type, that way we can pass a token upon creation
    addUser(username: String!, email: String!, password: String!): Auth

    login(email: String!, password: String!): Auth

    saveBook(bookData: SavedBooks): User

    deleteBook(bookId: ID!): User
  }
`;
module.exports = typeDefs;

resolvers.js

const { User, Book } = require("../models");
const { AuthenticationError } = require("apollo-server-express");
const { signToken } = require("../utils/auth");


const resolvers = {
  Query: {
    me: async (parent, args, context) => {
      if (context.user) {
        return User.findOne({ _id: context.user._id }).populate("books");
      }

      throw new AuthenticationError("You need to log in");
    },
  },
};
Mutation: {
  //try refactoring as a .then
  addUser: async (parent, args) => {
    //create user profile
    await console.log("resolver test");
    console.log(args);
    const user = await User.create({ username, email, password });
    //assign token to user
    const token = signToken(user);
    return { token, user };
  };

  login: async (parent, { email, password }) => {
    const user = User.findOne({ email });
    if (!user) {
      throw new AuthenticationError("Invalid Login Credentials");
    }
    const correctPw = await profile.isCorrectPassword(password);
    if (!correctPw) {
      throw new AuthenticationError("Invalid Login Credentials");
    }
    const token = signToken(user);
    return { token, user };
  };

  saveBook: async (parent, { bookData }, context) => {
    if (context.user) {
      return User.findOneAndUpdate(
        { _id: context.user._id },
        { $addToSet: { savedBooks: bookData } },
        { new: true }
      );
    }
    throw new AuthenticationError("You need to log in");
  };

  deleteBook: async (parent, { bookId }, context) => {
    if (context.user) {
      return User.findOneAndUpdate(
        { _id: contex.user._id },
        //remove selected books from the savedBooks Array
        { $pull: { savedBooks: context.bookId } },
        { new: true }
      );
    }
    throw new AuthenticationError("You need to log in");
  };
}
module.exports = resolvers;

auth.js

const jwt = require("jsonwebtoken");

// set token secret and expiration date
const secret = "mysecretsshhhhh";
const expiration = "2h";

module.exports = {
  // function for our authenticated routes
  authMiddleware: function ({ req }) {
    // allows token to be sent via  req.query or headers
    let token = req.query.token || req.headers.authorization || req.body.token;

    // ["Bearer", "<tokenvalue>"]
    if (req.headers.authorization) {
      token = token.split(" ").pop().trim();
    }

    if (!token) {
      return req;
    }

    // verify token and get user data out of it
    try {
      const { data } = jwt.verify(token, secret, { maxAge: expiration });
      req.user = data;
    } catch {
      console.log("Invalid token");
      return res.status(400).json({ message: "invalid token!" });
    }

    // send to next endpoint
    return req;
  },
  signToken: function ({ username, email, _id }) {
    const payload = { username, email, _id };

    return jwt.sign({ data: payload }, secret, { expiresIn: expiration });
  },
};

Basically, I have combed from front to back end looking for where I introduced this bug, and am stuck. Any suggestions or feedback is greatly appreciated.

1
make it working in the playground (using query variables) before FE codingxadm
I'll focus more of my efforts on the playground, do you see anything in the mutation I am running (mutation.js) that would cause this to be returned? { "data": { "addUser": null } } brand new developer, even newer at GraphQLMichael Souter
to be sure ... API/BE logging you're testing using server console, not on browser/FE ... do you see any BE messages (from auth/middleware, starting server, other resolver args) ?xadm
correct, I'm using the server console to try and verify what arguments are received on BE once the user submits the form. I receive no message. On resolvers.js and within addUser Mutation I've tried console.log("resolver test) and console.log(args) and get no message on the server console.Michael Souter
don't repeat yourself, we know that from question/description ... I suggested a few other things/testsxadm

1 Answers

0
votes

I was able to figure out the issue. First, a syntax error on resolver.js was preventing my mutations from being read.

Next, I made the following adjustment to handleFormSubmit on SignupForm.js

  try {
      ///Add user is not returning data. payload is being passed as an object

      const {data} = await addUser({
        variables: { ...userFormData },
      });
     console.log(data)
      console.log(userFormData)

  
      **Auth.login(data.addUser.token);**
    } catch (err) {
      console.error(err);
      setShowAlert(true);
    }

That way my FE was properly accounting for what my Auth Middleware was passing back after successful user creation. Thanks for your help xadm, being able to talk this out got me thinking about where else to attack the bug.