0
votes

I'm trying tok create a simple demo CRUD app using React and Graphql

I have the backend all set and working.

On the front end I'm using React with useQuery and useMutation.

I have the create working with useMutation to create the data and the read with useQuery to show the data.

I now stuck with the delete. I have a button the post and I can get the id of the post to pass to the useMutation function to delete the post.

I'm just stuck getting it to work if anyon can help.

App.tsx

import React, { useState } from 'react';
import './App.css';

import { RecipeData } from '../generated/RecipeData';
import { GET_ALL_RECIPES, ADD_RECIPE, DELETE_RECIPE } from '../queries';
import { useQuery, useMutation } from 'react-apollo-hooks';


const App: React.FC = () => {

  const [name, setName] = useState<string>('')
  const [description, setDes] = useState<string>('')
  const [id, setId] = useState<string>('')

  const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setName(e.target.value)
  }

  const handleDesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setDes(e.target.value)
  }

  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()
    createRecipe()
  };

  const handelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
    setId(e.target.parentElement.getAttribute("data-id"))
    deleteRecipe()
  }

  const [deleteRecipe] = useMutation(DELETE_RECIPE, {
    variables: { id }, refetchQueries: ['RecipeData']
  })

  const [createRecipe, { error }] = useMutation(ADD_RECIPE, {
    variables: { name, description }, refetchQueries: ['RecipeData']
  })
  if (error) {
    console.error('erroring : ', error)
  }

  const { data, loading } = useQuery<RecipeData | null>(GET_ALL_RECIPES, {
    suspend: false
  })

  if (loading || !data) return <div>Loading</div>

  return (
    <div className="App">
      <h1>Graphql</h1>
      <ul>
        {
          data.recipe !== null && data.recipe.map((recipe, i) => (
            <li key={recipe._id} data-id={recipe._id}>
              {recipe.name}
              <button onClick={handelDelete}>X</button>
            </li>
          ))
        }
      </ul>

      <form>
        <div>
          <label>Name</label>
          <input
            type="text"
            value={name}
            onChange={handleNameChange}
          />
        </div>
        <div>
          <label>Description</label>
          <input
            type="text"
            value={description}
            onChange={handleDesChange}
          />
        </div>
        <button onClick={handleClick}>Add Recipe</button>
      </form>
    </div>
  );
}

export default App;

queries/index.tsx

import { gql } from 'apollo-boost';

export const GET_ALL_RECIPES = gql`
  query RecipeData{
    recipe{
      _id
      name
      description
    }
  }
`
export const ADD_RECIPE = gql`
  mutation addRecipe($name: String!, $description:String){
    addRecipe(name: $name, description: $description){
      _id
      name
      description
    }
  }
`

export const DELETE_RECIPE = gql`
  mutation deleteRecipe($id: Int!){
    deleteRecipe(_id: $id){
      _id
      name
      description
    }
  }
`   

Server side

schema.js

exports.typeDefs = `

  type Recipe{
    _id: ID
    name: String
    description: String
  }

  type Query{
    recipe:[Recipe]
  }

  type Mutation{
    addRecipe(name: String!, description: String):Recipe
    deleteRecipe(_id: Int):Recipe
  }

`   

resolvers.js

exports.resolvers = {

  Query:{
    recipe: async(obj, args, {Recipe}, info) => {
      const allRecipes = await Recipe.find()
      return allRecipes
    }
  },
  Mutation:{
    addRecipe: async(obj, {name, description}, {Recipe}, info) => {
      const newRecipe = await new Recipe({
        name,
        description
      }).save()
      return newRecipe
    },
    deleteRecipe: async(obj, args, {Recipe}, info, {_id}) => {
      delete Recipe[_id];
    }
  }

}   

I don't have a live demo sorry but this outputs a list of recipe name with an input field to add new recipes, this part works.

I also have a delete button after each recipe displayed. I wanted to click this button and have that recipe be removed from the list.

At the moment when I click this delete button I get the following error message in the network tab of the dev tools

message: "Variable "$id" got invalid value ""; Expected type Int. Int cannot represent non-integer value: """

1
First thing I can see is that your mutation requires an integer (ID) but you're passing a string to it. const [id, setId] = useState<string>('') eventually ends up at mutation deleteRecipe($id: Int!){...Andy Mardell
Saying you're stuck isn't particularly helpful. What's the expected behavior -- and what behavior are you seeing instead? Are there any errors in the console when the unexpected behavior occurs-- if so, what are they? If you look in dev tools, is your request actually being sent -- if so, is it coming back with the expected response?Daniel Rearden
Sorry, I have updated the question with more information nowcdmt

1 Answers

0
votes
const handelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
  setId(e.target.parentElement.getAttribute("data-id"))
  deleteRecipe()
}

setID() is needed for something (other than passing parameter) ?

id is not updated immediately by setID() (async updates like setState - see docs) - value can't be used in following deleteRecipe()

IMHO there should be:

const handelDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
  const delID = e.target.parentElement.getAttribute("data-id")
  deleteRecipe( delID )
}

and deleting with direct parameter passing:

const deleteRecipe = (id) =>  useMutation(DELETE_RECIPE, { 
  variables: { id }, refetchQueries: ['RecipeData']
})