Apollo `useMutation()` stuck loading inside Next.js component - reactjs

The following form component in Next.js submits without errors but never completes the mutation. Instead it get's stuck in the loading state.
import React, { useState } from "react";
import { gql, useMutation } from "#apollo/client";
import { ApolloClient, InMemoryCache } from "#apollo/client";
const client = new ApolloClient({
ssrMode: true,
uri: process.env.API_URL,
cache: new InMemoryCache(),
});
const CREATE_COMMENT = gql`
mutation CreateComment(
$username: String!
$comment: String!
) {
createComment(
data: { username: $username, comment: $comment }
) {
id
username
comment
}
}
`;
export default function SocialForm() {
const [commentName, updateCommentName] = useState("");
const [commentDescription, updateCommentDescription] = useState("");
const [createComment, { data, error, loading }] = useMutation(
CREATE_COMMENT,
{ client }
);
const handleFormSubmit = async () => {
await createComment({
variables: {
name: commentName,
comment: commentDescription
},
});
};
if (loading) return "loading...";
if (error) return <p>error text :(</p>;
return (
<>
<form
onSubmit={async (e) => {
e.preventDefault();
await handleFormSubmit();
}}
className="social-form"
>
<input
required
onChange={(e) => updateCommentName(e.target.value)}
type="text"
placeholder="Full Name"
className=""
/>
<textarea
required
maxLength="280"
onChange={(e) => updateCommentDescription(e.target.value)}
className="w-full"
name="comment"
rows="5"
placeholder="Leave your thoughts and images at the moment"
/>
</form>
</>
);
}
Also the server side mutation with this exact same schema runs correctly inside Apollo playground and I'm using Keystone.js to auto generate the schema.

There's an error in your example:
const handleFormSubmit = async () => {
await createComment({
variables: {
name: commentName, // <-- Shouldn't this be `username: commentName,`?
comment: commentDescription
},
});
};
Otherwise, when you say the form "submits without errors but never completes", I assume you mean, from your browser? Can you see the GraphQL request being made in the browsers developer tools? I don't know which one you're in but there should be a way to see the requests being made and their responses. If the GraphQL server is returning an error, you'll see it there.
Also, what is Keystone logging on the command line during all this? If you add some console.log() statements to your custom mutation, do they get called?

Related

Getting 404 Error Response in google chrome extension in React js frontend and express backend

I am trying to create a simple form handling experiment where it will put all fetched data from database and display it to user.
I am using 2 separate projects for frontend and backend.
Frontend is in react and backend is in express js.
Here is my express.js code
const express = require("express");
const monggose = require("mongoose");
const bodyParser = require("body-parser");
const { default: mongoose } = require("mongoose");
const cors = require("cors");
const { Router } = require("express");
const app = express();
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
// ================= Connectivity ========
monggose.set("strictQuery", false);
mongoose.connect("mongodb://127.0.0.1:27017/players");
const cricketerSchema = monggose.Schema({
rank: Number,
name: String,
country: String,
dob: Date,
totalRun: Number,
});
const Cricketer = monggose.model("cricketers", cricketerSchema);
// === GET ,POST, DELETE ========
app
.route("/cricketers")
.get(function (request, response) {
Cricketer.find(function (error, foundCricketers) {
if (!error) {
response.send(foundCricketers);
} else {
response.send(error);
}
});
})
.post(function (request, response) {
const c1 = new Cricketer({
// ==== here Rank is the name of the attribute of form field
rank: request.body.Rank,
// ==== here Nank is the name of the attribute of form field
name: request.body.Name,
country: request.body.Country,
dob: request.body.DOB,
totalRun: request.body.TotalRun,
});
c1.save(function (error) {
if (!error) {
console.log("Data inserted successfully");
response.send("Data added successfully...");
} else {
console.log(error);
response.send(error);
}
});
})
.delete(function (request, response) {
Cricketer.deleteMany(function (error) {
if (!error) {
response.send("All Data Deleted");
} else {
response.send(error);
}
});
});
app.listen(3010, function () {
console.log("Server is running at http://localhost:3010");
});
and here is my frontend code.
import { useEffect, useState } from "react";
import axios from "axios";
function App() {
const [name, setName] = useState("zain");
const [rank, setRank] = useState("5");
const [country, setCountry] = useState("india");
const [dob, setDOB] = useState("2023-02-21");
const [totalrun, setRun] = useState("5000");
const [cricketers, setCricketers] = useState([]);
const baseURL = "http://localhost:3010/cricketers";
useEffect(() => {
axios.get(baseURL).then((response) => {
const x = response.data;
setCricketers(x);
});
}, []);
function handlesubmit() {
const collection1 = {
Name: name,
Country: country,
DOB: dob,
TotalRun: totalrun,
Rank: rank,
};
console.log(collection1);
useEffect(() => {
axios
.post(baseURL, collection1)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
});
}
return (
<div>
<label>Cricketer Rank:</label>
<input
type="number"
name="Rank"
value={rank}
onChange={(e) => setRank(e.target.value)}
/>
<br />
<label>Cricketer Name:</label>
<input
type="text"
name="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<br />
<label>Cricketer Country:</label>
<input
type="text"
name="Country"
value={country}
onChange={(e) => setCountry(e.target.value)}
/>
<br />
<label>Cricketer DOB:</label>
<input
type="date"
name="DOB"
value={dob}
onChange={(e) => setDOB(e.target.value)}
/>
<br />
<label>Cricketer TotalRun:</label>
<input
type="number"
name="TotalRun"
value={totalrun}
onChange={(e) => setRun(e.target.value)}
/>
<br />
<button onClick={handlesubmit}>Submit</button>
<ul>
{cricketers.map((i) => (
<li key={i._id}>
<p>
{i.name}
{i.country}
{i.rank}
{i.dob}
{i.totalrun}
</p>
</li>
))}
</ul>
</div>
);
}
export default App;
when page is loaded, it's fetching data from database and showing it into react page component.
For post method getting following error.
Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
at Object.throwInvalidHookError (react-dom.development.js:16227:9)
at useEffect (react.development.js:1634:21)
at handlesubmit (App.jsx:29:5)
My need is, I want to use functional component only and I have tried with all different solutions,
like fetch and axios.
It's not working.
When I am calling POST method from POSTMAN api it's working perfectly.
You are calling useEffect inside handleSubmit function. Hooks cannot be called inside a function. They can only be called at top level of a functional component. Refactor the handleSubmit function.
Please remove useEffect in your handleSubmit function. GET request must be put inside useEffect, but POST, PUT, PATCH,... don't. It all has to do with when the request is called. You can read further about useEffect to understand this.

Conditional Routing in React based on API calls

So I'm trying to create a React web app with multiple pages and connecting it to Flask to fetch data using the fetch API. Here is what I want to achieve:
If the user submits a Form, React does a POST request to the Flask API which returns a JSON object, which is received by React and I render the predict route. This is handled using the Forms.jsx component, which has the following code:
const Form = () => {
const [title, setTitle] = useState("");
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
const movie_submit = {title};
console.log(movie_submit);
fetch('/predict', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(movie_submit)
}).then(() => {
(navigate("/predict"));
})
}
return (
<div className='form_container'>
<form className='form' onSubmit={handleSubmit}>
<input type='text' placeholder='Movie Name' autoFocus
autoComplete='off' value={title} onChange={(e)=>setTitle(e.target.value)}/>
<button className='button'>Recommend!</button>
</form>
</div>
)
}
export default Form
Now I want to perform a GET request to the Flask API to get what should be put into the Predict.js page (/predict route), and the show it.
Predict.js is as:
const Predict = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
fetch('/predict').then(response =>
response.json().then(data =>
{
setMovies(Object.values(data));
}))
}, []);
const movie_name = movies.map((movie) => <p key={movie.toString()}>{movie}</p>);
return (
<div>
<Navbar />
<h1>Predictions</h1>
<br />
<h2><Movies movie={movie_name}/></h2>
</div>
)
}
export default Predict
But I want this to be such that if the user hasn't submitted the form, then it navigates to /apology route, and if the FLASK API GET request returns an empty object, even then it navigates to /apology route. How do I do this? I understand this is conditional routing of some sort, but I havent been able to quite achieve where I should do this. Here <Movies /> is simply a component that helps in showing the movie names
You can pass a data to the state prop of the location object.
fetch('/predict', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(movie_submit)
}).then(() => {
(navigate('/predict', { state: { wasFetched: true } }));
})
then in your Predict Component:
const { state } = useLocation();
const { wasFetched } = state;
useEffect(() => {
if (wasFetched) {
// user submited the form
} else {
// user hasn't submited the form
}
}, [wasFetched]);

React and Websocket messaging to server

So I'm having issues with a single component that displays a list pulled from a resource server. Then it uses Stompjs to establish a websocket and send messages. When I load the client, the Dev Console shows logs that it tries to call onConnected method() twice as my logs show two newUser messages sent from a single load of the component.
When I try to call the submitBid() method it throws a type error saying that
"TypeError: Cannot read properties of undefined (reading 'send')
at submitBid (AuctionList.js:76:1)"
Which I'm not sure why it would be undefined on that line when it's defined and running fine in the function on line 36 which runs before the method that fails. I've been stuck on this for several days so hopefully someone can tell me what I've got wrong in the code... Here is the component code....
import React from 'react'
import Stomp from 'stompjs';
import SockJS from 'sockjs-client';
import {useState, useEffect } from 'react';
function AuctionList({user, authCredentials, token}) {
const [auctionItems, setAuctionItems] = useState([]);
const [userData, setUserData] = useState({
email: user.email,
name: user.name,
message: ''
});
const [bid, setBid] = useState(0.00);
let stompClient;
let socket;
const connect = async () => {
socket = new SockJS('http://localhost:8080/ws')
stompClient = Stomp.over(socket)
stompClient.connect({}, onConnected, onError)
}
const onConnected = async () => {
stompClient.subscribe('/topic/bids', onMessageReceived)
stompClient.send("/app/socket.newUser",
{},
JSON.stringify({
sender: user.name,
type: 'NEW_USER',
time: Date.now()
})
)
}
const onError = async (err) => {
console.log(err);
}
const handleChange = async (e) =>{
setBid(e.target.value);
}
const submitBid = async (item) => {
let newMessage = {
type: "BID",
newBid: {
itemId: item.id,
email: user.email,
bidPrice: bid,
bidTime: new Date().getTime()
},
sender: userData.email,
time: new Date().getTime()
};
try {
stompClient.send("/socket.send", {}, JSON.stringify(newMessage));
} catch(err){
console.log(err); }
}
const onMessageReceived = async (payload)=>{
console.log("onMessageReceived")
console.log(payload)
}
const getAuctionList = async () => {
const url = "http://localhost:8080/auctionlist";
const init = {
method: "GET",
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`, // notice the Bearer before your token
},
};
fetch(url, init)
.then(response => response.json())
.then(response => {setAuctionItems(response)})
};
useEffect( () => {
getAuctionList();
connect();
}, []);
return (
<ul>
{auctionItems.map( item => {
return( <div key={item.id} className = "auctionItemComponent">
<h3>{item.name}</h3>
<span>{item.desc}</span>
<span>Current Bid: ${item.itemStartingPrice}</span>
<span>Minimum Bid: {item.itemMinBid}</span>
<span>Time left</span>
<input type="number" id="bidInput_" name="bidInput" onChange={handleChange} ></input>
<button type='submit' onClick={submitBid}>Submit bid</button>
</div>)
})}
</ul>
)
}
export default AuctionList
Also I realize I have a bunch of async functions that don't have any awaits. I tried adding those in, but it was no change.
The issue here is not with stompjs but with the scoping. You have stompClient inside React Component but the one from submitBid is different. You can do it in different ways.
Put stompjs in global stage as in example here: https://playcode.io/972045
You can use useRef to have the client inside the React Component and have React do the tracking of any modifications.
I personally think something like a "connection" should stay away from inside a React Component. You should have the connection configs in a different file and import an instance to the JSX file.

React-gatsby login authentioncation failed

I am using Gatsby for my app. I have created one api from Mock api. My api looks like this. I have made one post-request for login, when user will put his/her email and password, if it does not match then it will alert "failed login" or if it is success it will alert ("successfully login") and navigate to successful page. But what ever email and password I am putting it always shows me I login successfully which is wrong logic. The email should be like my api's email: alak#gmail.com" and password: test123 . I think my logic right but still I am making the mistake. I share my code in Codesandbox.
PS: Codesandbox is based on react. but logic is same as my below code
Here is my code:
import React, { ReactElement, useState } from 'react';
import { PageProps, navigate } from 'gatsby';
import styled from 'styled-components';
import MainTemplate from '../templates/index';
import { TextInput } from '../components/textInput';
import { Button } from '../components/buttons';
import { API_URLS } from '../utilities';
interface Props extends PageProps {
}
export default function SignIn({ }: Props): ReactElement {
const [state, setState] = useState({
"email": ``,
"password": ``,
"loading": false
});
const { loading } = state;
const signInValue = (e) => {
setState({
...state,
[e.target.id]: e.target.value
});
};
const onSubmit = async (e) => {
e.preventDefault();
console.log(state);
setState({
"loading": true,
...state
});
const response = await fetch(`https://run.mocky.io/v3/beec46b8-8536-4cb1-9304-48e96d341461`, {
"method": `POST`,
"headers": {
"Accept": `application/json`,
'Content-Type': `application/json`
},
"body": { state }
});
if (response.ok) {
alert(`you have suceefully login`);
navigate(`/success`, { "state": { email } });
} else {
alert(`login failed`);
}
};
return (
<MainTemplate>
<TextInput
type="text"
value={state.email}
onChange={signInValue}
id="email"
required
/>
<TextInput
type="password"
value={state.password}
onChange={signInValue}
id="password"
required
/>
<Button
type="submit"
name="action"
onClick={onSubmit}
disabled={loading}
> {loading ? `loading...` : `save`}
</Button>
</MainTemplate>
);
}
According to the fetch specification:
The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.
So your api might be returning an error but response.ok might still be true. You should check response.status !== 200 instead.
Update: works as shown below based on your sandbox:

React axios post request does not send the data

I am using react for my app. I am learning post request. I found one dummy api site Mocky where I can test my post request. This is my api link .For post request I used axios. I don't know how the Mocky api works. I made post request. when I console log the input values I can the value.But when I console log the response it seems like it does not get the data. I don't see any where I am making mistake.
Here is my code:
import React, { useState } from 'react';
import { API_URLS } from '../utilities';
import axios from "axios";
export default function CreateAccount() {
const [state, setState] = useState({
"email": ``,
"password": ``,
"loading": false,
"error": ``
});
const onChangeStudent = (e) => {
setState({
...state,
[e.target.id]: e.target.value
});
};
const onSubmit = async (e) => {
e.preventDefault();
console.log(state);
const url = `https://run.mocky.io/v3/15c2b7ec-9f31-4a18-ae60-a7f41e1f39b2`;
const obj = {
"email": state.email,
"password": state.password
};
console.log(obj.email); //I can see the input value
console.log(obj.password);//I can see the input value
axios
.post(url, obj)
.then((res) => {
console.log(res.data); // it does not show the data
console.log(res);
})
.catch((error) => {
setState({
...state,
"error": error
});
});
};
return (
<div>
<form onSubmit={onSubmit}>
<input
type="text"
value={state.name}
onChange={onChangeStudent}
id="email"
required
/>
<input
type="password"
value={state.password}
onChange={onChangeStudent}
id="password"
required
/>
<button
className="btn waves-effect blue lighten-1"
type="submit"
name="action"
disabled={state.loading}
>
{state.loading ? `loading...` : `save`}
</button>
</form>
</div>
);
}
Hi can't seem to find anything wrong with what you are doing.
I tested the below and it worked for me. Try to change from .then to await. Hope this solves your problem. Check in your network tab if your request is successful and if you are sending the body.
try {
const response = await axios.post('https://run.mocky.io/v3/4b95050f-2bcc-4c78-b86e-6cac09372dce', data);
console.log("Response", response);
} catch(e) {
console.error(e);
}

Resources