admin管理员组

文章数量:1398840

I have written an app in Vue 3 that allows a user to book a charging point in the office car park, however I am having some issues writing the data back to my GraphQL API.

My main.js looks like this;

import { createApp, h, provide } from 'vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
import { registerPlugins } from '@/plugins'
import App from './App.vue'

// HTTP connection to the API
const httpLink = createHttpLink({
  uri: 'http://localhost:4000/graphql', // Your GraphQL endpoint
})

// Cache implementation
const cache = new InMemoryCache()

// Apollo Client instance
const apolloClient = new ApolloClient({
  link: httpLink,
  cache,
})

const app = createApp({
  setup() {
    provide(DefaultApolloClient, apolloClient)
  },
  render: () => h(App),
})

registerPlugins(app)
app.mount('#app')

The App.vue is looking something like this

<template>
  <v-app>
    <Header />
    <v-main>
      <v-container>
        <Chargers />
      </v-container>
    </v-main>
  </v-app>
</template>

<script>
import Chargers from '@/components/Chargers.vue';
import Header from '@/components/Header.vue';  // Import Header.vue

export default {
  name: 'App',
  components: {
    Chargers,   // Add the Chargers component
    Header,     // Add the Header component here
  }
};
</script>

<style>
body {
  font-family: 'Roboto', sans-serif;
}
</style>

The Chargers.vue looks like this

<template>
  <v-container>
    <v-row v-for="station in stations" :key="station.id" class="mb-4">
      <v-col cols="12">
        <h2 class="text-h5 font-weight-bold">{{ station.name }}</h2>
      </v-col>

      <v-col
        v-for="child in station.children"
        :key="child.id"
        cols="12"
        sm="6"
        md="4"
      >
        <v-card class="pa-4 left-align-card clickable" @click="openBookingDialog(child)">
          <h3 class="text-h5 font-weight-bold">{{ child.name }}</h3>
          <p class="status">
            ⚡ <strong>{{ child.state.toUpperCase() }}</strong>
          </p>
          <p class="check-in">Check-in</p>
        </v-card>
      </v-col>
    </v-row>

    <v-dialog v-model="dialogVisible" max-width="500px">
      <v-card>
        <v-card-title>
          <span class="text-h6">Booking for {{ selectedChargerName }}</span>
        </v-card-title>
        <v-card-text>
          <BookingForm
            :charger="selectedChargerName"
            :charger-id="selectedChargerId"
            :stations="stations"
            :refetchStations="refetchStations"
            @check-in="updateChildState"
            @closeDialog="dialogVisible = false"
          />
        </v-card-text>
      </v-card>
    </v-dialog>
  </v-container>
</template>

<script>
import { ref, watchEffect } from "vue";
import { useQuery } from "@vue/apollo-composable";
import { GET_STATIONS } from "@/graphql/queries";
import BookingForm from "./BookingForm.vue";

export default {
  components: {
    BookingForm,
  },
  setup() {
    const { result, refetch } = useQuery(GET_STATIONS);
    const stations = ref([]);
    const dialogVisible = ref(false);
    const selectedChargerName = ref("");
    const selectedChargerId = ref("");

    // Open the booking dialog for the selected charger
    const openBookingDialog = (child) => {
      selectedChargerName.value = child.name;
      selectedChargerId.value = child.id;  // Ensure child.id is stored here
      console.log("Opening booking dialog for charger:", child.id);  // Log the child.id for verification
      dialogVisible.value = true;
    };

    // Watch and update stations when the data is fetched
    watchEffect(() => {
      if (result.value) {
        stations.value = result.value.stations;
        console.log("Stations data fetched:", stations.value);
      }
    });

    // Refetch the stations
    const refetchStations = async () => {
      console.log("Refetching stations...");
      await refetch({ fetchPolicy: "network-only" });

      if (result.value) {
        console.log("Updated stations:", result.value.stations);
      } else {
        console.warn("Stations data not updated.");
      }
    };

    // Update the updateChildState method
    const updateChildState = (bookingData) => {
      console.log("Booking data received:", bookingData);

      // Find the station containing the booked charger
      const stationIndex = stations.value.findIndex((station) =>
        station.children.some((child) => child.name === bookingData.chargerName)
      );

      if (stationIndex === -1) {
        console.error("Station not found for charger name:", bookingData.chargerName);
        return;
      }

      const station = stations.value[stationIndex];

      // Find the charger index within the station
      const childIndex = station.children.findIndex((child) => child.name === bookingData.chargerName);
      if (childIndex === -1) {
        console.error("Charger not found in the station list for charger name:", bookingData.chargerName);
        return;
      }

      console.log("Found charger:", station.children[childIndex].name);

      const updatedStation = {
        ...station,
        children: station.children.map((child, index) =>
          index === childIndex
            ? { ...child, state: "Charging", userEmail: bookingData.userEmail, registrationNumber: bookingData.registrationNumber, vehicleType: bookingData.vehicleType }
            : child
        )
      };

      stations.value = [
        ...stations.value.slice(0, stationIndex),
        updatedStation,
        ...stations.value.slice(stationIndex + 1),
      ];
    };

    return {
      stations,
      refetchStations,
      dialogVisible,
      selectedChargerName,
      selectedChargerId,
      openBookingDialog,
      updateChildState,
    };
  },
};
</script>

<style>
.status strong {
  color: rgb(91, 181, 19);
}

.check-in {
  max-height: 60px;
  max-width: 100px;
  display: flex;
  align-items: center;
  overflow: hidden;
  overflow-wrap: break-word;
  text-transform: uppercase;
  font-size: 12px;
  cursor: pointer;
  color: rgb(0, 62, 122);
  border: 5px;
  padding: 15px 0 0;
  transition: background 0.8s;
}
</style>

And the form I use to add the details to the API is called BookingForm.vue

<template>
  <v-form @submit.prevent="handleSubmit">
    <v-row>
      <!-- Registration Number -->
      <v-col cols="12">
        <v-text-field
          v-model="registrationNumber"
          label="Vehicle Registration Number"
          required
        ></v-text-field>
      </v-col>

      <!-- Vehicle Type -->
      <v-col cols="12">
        <v-select
          v-model="vehicleType"
          :items="vehicleTypes"
          label="Vehicle Type"
          required
        ></v-select>
      </v-col>

      <!-- Email -->
      <v-col cols="12">
        <v-text-field
          v-model="userEmail"
          label="Email Address"
          type="email"
          required
        ></v-text-field>
      </v-col>

      <!-- Error Message -->
      <v-col cols="12" v-if="error">
        <v-alert type="error">
          {{ error.message }}
        </v-alert>
      </v-col>

      <v-col cols="12">
        <v-btn
          type="submit"
          color="primary"
          :disabled="!isValid || isCheckedIn || loading"
        >
          {{ loading ? "Checking in..." : "Check-In" }}
        </v-btn>
      </v-col>
    </v-row>
  </v-form>
</template>

<script>
import { ref, computed, watchEffect } from "vue";
import { useMutation } from "@vue/apollo-composable";
import { BOOK_CHARGER } from "@/graphql/mutations";

export default {
  props: {
    charger: String, // Charger Name passed from parent
    dialogModel: Boolean, // Controls the visibility of the dialog
    stations: Array, // Accepting stations as a prop
    refetchStations: Function, // Function to refetch stations passed from the parent
  },
  setup(props) {
    const registrationNumber = ref("");
    const vehicleType = ref("");
    const userEmail = ref("");
    const isCheckedIn = ref(false);
    const vehicleTypes = ["Plug-in Hybrid", "Full Electric"];

    // Ensure all fields are filled before enabling submit
    const isValid = computed(() =>
      props.charger &&
      registrationNumber.value &&
      vehicleType.value &&
      userEmail.value
    );

    // Debugging: Watch for changes in charger prop
    watchEffect(() => {
      console.log("Updated charger prop:", props.charger);
    });

    // Mutation for booking the charger
    const { mutate: bookCharger, loading, error, data } = useMutation(BOOK_CHARGER);

    const resetForm = () => {
      registrationNumber.value = "";
      vehicleType.value = "";
      userEmail.value = "";
    };

    // Handle Check-In and mutation call
    const handleSubmit = async () => {
      try {
        const response = await bookCharger({
          variables: {
            chargerName: props.charger,
            registrationNumber: registrationNumber.value,
            vehicleType: vehicleType.value,
            userEmail: userEmail.value,
          },
        });

        console.log("Booking response:", response?.data);

      } catch (err) {
        console.error("Error during booking:", err);
      }
    };

    return {
      registrationNumber,
      vehicleType,
      userEmail,
      isCheckedIn,
      vehicleTypes,
      handleSubmit,
      loading,
      error,
      data,
      isValid,
    };
  },
};
</script>

and finally my mutations.js

import { gql } from "graphql-tag";

export const BOOK_CHARGER = gql`
  mutation BookCharger(
    $chargerName: String!,
    $registrationNumber: String!,
    $vehicleType: String!,
    $userEmail: String!
  ) {
    bookCharger(
      chargerName: $chargerName,
      registrationNumber: $registrationNumber,
      vehicleType: $vehicleType,
      userEmail: $userEmail
    ) {
      success
      message
    }
  }
`;

I am currently powering all this via a locally created GraphQL API with Apollo.

I can see that the form is capturing the data, however I can't see what is happening to that data, and why it isn't being sent to the GraphQL API

Can anyone help?

I have tried a lot of googling and even asked ChatGPT, however that resulted in the form generating this error in the console

Opening booking dialog for charger: 1A BookingForm.vue:82 Updated charger prop: 1A BookingForm.vue:109 Error during booking: ApolloError: Variable "$chargerName" of required type "String!" was not provided. Variable "$registrationNumber" of required type "String!" was not provided. Variable "$vehicleType" of required type "String!" was not provided. Variable "$userEmail" of required type "String!" was not provided.

the GraphQL API itself isn't actually showing an error, but here is my Schema for reference.

export const typeDefs = `#graphql
  type Station {
    id: ID!
    name: String!
    children: [Child!]!
  }

  type Child {
    id: ID!
    name: String!
    state: String!
    userEmail: String
    registrationNumber: String
    vehicleType: String
    countdown: String
  }

  # Define the response for booking a charger
  type BookChargerResponse {
    success: Boolean
    message: String
  }

  type Mutation {
    # Define the mutation to book a charger
    bookCharger(
      registrationNumber: String!,
      vehicleType: String!,
      userEmail: String!,
      chargerName: String!
    ): BookChargerResponse
  }

  type Query {
    stations: [Station!]!
  }
`;

I have written an app in Vue 3 that allows a user to book a charging point in the office car park, however I am having some issues writing the data back to my GraphQL API.

My main.js looks like this;

import { createApp, h, provide } from 'vue'
import { DefaultApolloClient } from '@vue/apollo-composable'
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
import { registerPlugins } from '@/plugins'
import App from './App.vue'

// HTTP connection to the API
const httpLink = createHttpLink({
  uri: 'http://localhost:4000/graphql', // Your GraphQL endpoint
})

// Cache implementation
const cache = new InMemoryCache()

// Apollo Client instance
const apolloClient = new ApolloClient({
  link: httpLink,
  cache,
})

const app = createApp({
  setup() {
    provide(DefaultApolloClient, apolloClient)
  },
  render: () => h(App),
})

registerPlugins(app)
app.mount('#app')

The App.vue is looking something like this

<template>
  <v-app>
    <Header />
    <v-main>
      <v-container>
        <Chargers />
      </v-container>
    </v-main>
  </v-app>
</template>

<script>
import Chargers from '@/components/Chargers.vue';
import Header from '@/components/Header.vue';  // Import Header.vue

export default {
  name: 'App',
  components: {
    Chargers,   // Add the Chargers component
    Header,     // Add the Header component here
  }
};
</script>

<style>
body {
  font-family: 'Roboto', sans-serif;
}
</style>

The Chargers.vue looks like this

<template>
  <v-container>
    <v-row v-for="station in stations" :key="station.id" class="mb-4">
      <v-col cols="12">
        <h2 class="text-h5 font-weight-bold">{{ station.name }}</h2>
      </v-col>

      <v-col
        v-for="child in station.children"
        :key="child.id"
        cols="12"
        sm="6"
        md="4"
      >
        <v-card class="pa-4 left-align-card clickable" @click="openBookingDialog(child)">
          <h3 class="text-h5 font-weight-bold">{{ child.name }}</h3>
          <p class="status">
            ⚡ <strong>{{ child.state.toUpperCase() }}</strong>
          </p>
          <p class="check-in">Check-in</p>
        </v-card>
      </v-col>
    </v-row>

    <v-dialog v-model="dialogVisible" max-width="500px">
      <v-card>
        <v-card-title>
          <span class="text-h6">Booking for {{ selectedChargerName }}</span>
        </v-card-title>
        <v-card-text>
          <BookingForm
            :charger="selectedChargerName"
            :charger-id="selectedChargerId"
            :stations="stations"
            :refetchStations="refetchStations"
            @check-in="updateChildState"
            @closeDialog="dialogVisible = false"
          />
        </v-card-text>
      </v-card>
    </v-dialog>
  </v-container>
</template>

<script>
import { ref, watchEffect } from "vue";
import { useQuery } from "@vue/apollo-composable";
import { GET_STATIONS } from "@/graphql/queries";
import BookingForm from "./BookingForm.vue";

export default {
  components: {
    BookingForm,
  },
  setup() {
    const { result, refetch } = useQuery(GET_STATIONS);
    const stations = ref([]);
    const dialogVisible = ref(false);
    const selectedChargerName = ref("");
    const selectedChargerId = ref("");

    // Open the booking dialog for the selected charger
    const openBookingDialog = (child) => {
      selectedChargerName.value = child.name;
      selectedChargerId.value = child.id;  // Ensure child.id is stored here
      console.log("Opening booking dialog for charger:", child.id);  // Log the child.id for verification
      dialogVisible.value = true;
    };

    // Watch and update stations when the data is fetched
    watchEffect(() => {
      if (result.value) {
        stations.value = result.value.stations;
        console.log("Stations data fetched:", stations.value);
      }
    });

    // Refetch the stations
    const refetchStations = async () => {
      console.log("Refetching stations...");
      await refetch({ fetchPolicy: "network-only" });

      if (result.value) {
        console.log("Updated stations:", result.value.stations);
      } else {
        console.warn("Stations data not updated.");
      }
    };

    // Update the updateChildState method
    const updateChildState = (bookingData) => {
      console.log("Booking data received:", bookingData);

      // Find the station containing the booked charger
      const stationIndex = stations.value.findIndex((station) =>
        station.children.some((child) => child.name === bookingData.chargerName)
      );

      if (stationIndex === -1) {
        console.error("Station not found for charger name:", bookingData.chargerName);
        return;
      }

      const station = stations.value[stationIndex];

      // Find the charger index within the station
      const childIndex = station.children.findIndex((child) => child.name === bookingData.chargerName);
      if (childIndex === -1) {
        console.error("Charger not found in the station list for charger name:", bookingData.chargerName);
        return;
      }

      console.log("Found charger:", station.children[childIndex].name);

      const updatedStation = {
        ...station,
        children: station.children.map((child, index) =>
          index === childIndex
            ? { ...child, state: "Charging", userEmail: bookingData.userEmail, registrationNumber: bookingData.registrationNumber, vehicleType: bookingData.vehicleType }
            : child
        )
      };

      stations.value = [
        ...stations.value.slice(0, stationIndex),
        updatedStation,
        ...stations.value.slice(stationIndex + 1),
      ];
    };

    return {
      stations,
      refetchStations,
      dialogVisible,
      selectedChargerName,
      selectedChargerId,
      openBookingDialog,
      updateChildState,
    };
  },
};
</script>

<style>
.status strong {
  color: rgb(91, 181, 19);
}

.check-in {
  max-height: 60px;
  max-width: 100px;
  display: flex;
  align-items: center;
  overflow: hidden;
  overflow-wrap: break-word;
  text-transform: uppercase;
  font-size: 12px;
  cursor: pointer;
  color: rgb(0, 62, 122);
  border: 5px;
  padding: 15px 0 0;
  transition: background 0.8s;
}
</style>

And the form I use to add the details to the API is called BookingForm.vue

<template>
  <v-form @submit.prevent="handleSubmit">
    <v-row>
      <!-- Registration Number -->
      <v-col cols="12">
        <v-text-field
          v-model="registrationNumber"
          label="Vehicle Registration Number"
          required
        ></v-text-field>
      </v-col>

      <!-- Vehicle Type -->
      <v-col cols="12">
        <v-select
          v-model="vehicleType"
          :items="vehicleTypes"
          label="Vehicle Type"
          required
        ></v-select>
      </v-col>

      <!-- Email -->
      <v-col cols="12">
        <v-text-field
          v-model="userEmail"
          label="Email Address"
          type="email"
          required
        ></v-text-field>
      </v-col>

      <!-- Error Message -->
      <v-col cols="12" v-if="error">
        <v-alert type="error">
          {{ error.message }}
        </v-alert>
      </v-col>

      <v-col cols="12">
        <v-btn
          type="submit"
          color="primary"
          :disabled="!isValid || isCheckedIn || loading"
        >
          {{ loading ? "Checking in..." : "Check-In" }}
        </v-btn>
      </v-col>
    </v-row>
  </v-form>
</template>

<script>
import { ref, computed, watchEffect } from "vue";
import { useMutation } from "@vue/apollo-composable";
import { BOOK_CHARGER } from "@/graphql/mutations";

export default {
  props: {
    charger: String, // Charger Name passed from parent
    dialogModel: Boolean, // Controls the visibility of the dialog
    stations: Array, // Accepting stations as a prop
    refetchStations: Function, // Function to refetch stations passed from the parent
  },
  setup(props) {
    const registrationNumber = ref("");
    const vehicleType = ref("");
    const userEmail = ref("");
    const isCheckedIn = ref(false);
    const vehicleTypes = ["Plug-in Hybrid", "Full Electric"];

    // Ensure all fields are filled before enabling submit
    const isValid = computed(() =>
      props.charger &&
      registrationNumber.value &&
      vehicleType.value &&
      userEmail.value
    );

    // Debugging: Watch for changes in charger prop
    watchEffect(() => {
      console.log("Updated charger prop:", props.charger);
    });

    // Mutation for booking the charger
    const { mutate: bookCharger, loading, error, data } = useMutation(BOOK_CHARGER);

    const resetForm = () => {
      registrationNumber.value = "";
      vehicleType.value = "";
      userEmail.value = "";
    };

    // Handle Check-In and mutation call
    const handleSubmit = async () => {
      try {
        const response = await bookCharger({
          variables: {
            chargerName: props.charger,
            registrationNumber: registrationNumber.value,
            vehicleType: vehicleType.value,
            userEmail: userEmail.value,
          },
        });

        console.log("Booking response:", response?.data);

      } catch (err) {
        console.error("Error during booking:", err);
      }
    };

    return {
      registrationNumber,
      vehicleType,
      userEmail,
      isCheckedIn,
      vehicleTypes,
      handleSubmit,
      loading,
      error,
      data,
      isValid,
    };
  },
};
</script>

and finally my mutations.js

import { gql } from "graphql-tag";

export const BOOK_CHARGER = gql`
  mutation BookCharger(
    $chargerName: String!,
    $registrationNumber: String!,
    $vehicleType: String!,
    $userEmail: String!
  ) {
    bookCharger(
      chargerName: $chargerName,
      registrationNumber: $registrationNumber,
      vehicleType: $vehicleType,
      userEmail: $userEmail
    ) {
      success
      message
    }
  }
`;

I am currently powering all this via a locally created GraphQL API with Apollo.

I can see that the form is capturing the data, however I can't see what is happening to that data, and why it isn't being sent to the GraphQL API

Can anyone help?

I have tried a lot of googling and even asked ChatGPT, however that resulted in the form generating this error in the console

Opening booking dialog for charger: 1A BookingForm.vue:82 Updated charger prop: 1A BookingForm.vue:109 Error during booking: ApolloError: Variable "$chargerName" of required type "String!" was not provided. Variable "$registrationNumber" of required type "String!" was not provided. Variable "$vehicleType" of required type "String!" was not provided. Variable "$userEmail" of required type "String!" was not provided.

the GraphQL API itself isn't actually showing an error, but here is my Schema for reference.

export const typeDefs = `#graphql
  type Station {
    id: ID!
    name: String!
    children: [Child!]!
  }

  type Child {
    id: ID!
    name: String!
    state: String!
    userEmail: String
    registrationNumber: String
    vehicleType: String
    countdown: String
  }

  # Define the response for booking a charger
  type BookChargerResponse {
    success: Boolean
    message: String
  }

  type Mutation {
    # Define the mutation to book a charger
    bookCharger(
      registrationNumber: String!,
      vehicleType: String!,
      userEmail: String!,
      chargerName: String!
    ): BookChargerResponse
  }

  type Query {
    stations: [Station!]!
  }
`;
Share Improve this question asked Mar 26 at 22:13 TakuhiiTakuhii 9672 gold badges9 silver badges29 bronze badges 1
  • That helps with the weird errors thank you, but I am struggling to get it to write back to the GraphQL API still :/ – Takuhii Commented Mar 27 at 8:08
Add a comment  | 

1 Answer 1

Reset to default 0

Your issue seems to be here:

const response = await bookCharger({
  variables: {
    chargerName: props.charger,
    registrationNumber: registrationNumber.value,
    vehicleType: vehicleType.value,
    userEmail: userEmail.value,
  },
});

The mutate function returned by useMutation expects your variables as the first parameter, but you seem to nest your variables into an object with a superfluous variables key. Change your code to:

const response = await bookCharger({
  chargerName: props.charger,
  registrationNumber: registrationNumber.value,
  vehicleType: vehicleType.value,
  userEmail: userEmail.value,
});

本文标签: vuejs3Writing Back to GraphQL APIStack Overflow