I'm creating a comment section for my website and in my front end, I want to create a function that displays the comment that the user posted in the database.
But when I created a function that retrieves the comment and display it, the comment won't show up. I don't know what I am doing wrong, did I place it in the wrong place or did I program it correctly?
this is my backend program
app.post('/forum', function (req, res){
const queryString = "INSERT INTO comments (Comments) VALUES (?)"
console.log (req.body)
con.query(queryString, [req.body.comment], function (err, result){
if (err) {
throw err;
}
if (result.length != 1) {
return res.send("Posted Comment")
}
else {
res.send('Comment failed to post')
};
})
})
app.get('/forum', function (req, res){
const queryString = 'SELECT * FROM comments WHERE Comments = ?'
console.log (req.body)
con.query(queryString, [req.body.insert], function (err, result){
if (err) {throw err;}
var commenting = JSON.stringify(result)
res.send(commenting);
})
})
the api call that retrieves the response
static async forum(comm){
const response = await axios
.post('http://localhost:8080/forum', {
comment: comm,
})
.then ()
return response.data;
}
static async comment(comm){
const response = await axios
.get('http://localhost:8080/forum', {
comment: comm
})
.then ()
return response.data;
}
and my frontend where the comment would show up
function Forum() {
const [comment, inputComment] = useState ('')
/*user posts the comment*/
const onPost = (event) => {
event.preventDefault()
apiClient.forum(comment) .then( (response) => {
console.log(response)
})
}
/*users comment gets displayed*/
apiClient.comment() .then((response) =>{
document.getElementById("comment-section").innerHTML = response
})
return (
<section class = "index-banner" >
<div>
<label>Comment</label>
<textarea name="comment" id= "comment" rows="10" tabIndex = "4"onChange = {e => {inputComment(e.target.value)}}></textarea>
<button onClick={onPost}>Post</button>
</div>
<div>
<body id = "comment-section" ></body>
</div>
</section>
)
}
Is the request definitely coming through? You can verify in the browser DevTools on the network tab.
A few recommendations I could make about a better way to use React.
When the response returns, add it to the component using useState
The component will automatically re-render when there is a state update
(here you don't need the document.getElementById
You could also use the useRef approach
the favoured "React way" of doing document.getElementById
1. with useState:
function Forum() {
const [comment, setComment] = useState ('')
const [postedComment, setPostedComment] = useState ('')
const onPost = (event) => {
event.preventDefault()
apiClient
.forum(comment)
.then( (response) => {
console.log(response.data)
// this should trigger a rerender, in the <div>{postedComment}</div>
// assuming your response contains the posted comment:
setPostedComment(response.data)
})
.catch( error => console.log(error) /* handle Exception here */)
}
return <div>
<label>Comment</label>
<textarea
name="comment"
id="comment"
rows="10"
tabIndex ="4"
onChange={e => {inputComment(e.target.value)}}>
</textarea>
<button onClick={onPost}>Post</button>
<div>{ postedComment }</div>
</div>
}
2. with useRef:
const commentRef = useRef(null)
// ...
const onPost = (event) => {
event.preventDefault()
apiClient
.forum(comment)
.then( (response) => {
console.log(response)
// assuming your response contains the posted comment:
commentRef.current.innerHTML = response
})
.catch( error => console.log(error) /* handle Exception here */)
}
return <div>
{/* ... */}
<div ref={commentRef}>
</div>
</div>
NOTES:
I would avoid using the body element except for the entire application!
To address some of the comments:
fetch uses response.json()
axios uses response.data
Related
I have my react frontend which is very basic right now as i just want to retrieve some data from my backend api written in node js which calls and external api
the data is fetched properly to the backend as I have tested it by printing the data. The issue is that my promise on the frontend is never resolved meaning the data is never fetched
frontend code so far:
import "./App.css";
import axios from "axios";
function App() {
const getDataPromise = async () => {
const response = await axios.get(
"http://localhost:8800/api/auth/data"
);
console.log("ACTIVITY RESPONSE = ", response);
//return data;
};
const getActivities = async () => {
const promiseData = await getDataPromise()
// NEVER RESOLVES
console.log("Promise data = ", promiseData);
//getDataPromise().then((res) => console.log("RES = ", res));
//}
};
return (
<div className="App">
<button onClick={getActivities}>Get All Data</button>
</div>
);
}
export default App;
backend section of code with api link replaced with text:
async function getDataPromise() {
const link = `externalAPI`;
const response = await axios.get(link);
console.log("Response = ", response.data[0]);
return response.data[0];
}
router.get("/data", async (req, res) => {
const data = await getDataPromise();
console.log("data = ", data);
return data;
});
Does anyone see my issue why my frontend promise when the getData button is clicked never resolves so the promiseData value is eventually printed
It's on backend side
router.get("/data", async (req, res) => {
const data = await getDataPromise();
console.log("data = ", data);
return data;
});
Try to replace
return data
with
res(data)
You are not calling your async function the right way, try this:
<button onClick={async () => { await getActivities() }}>Get All Data</button>
Hope it helps!
Client/App.js:
import React, { useState, useEffect } from "react";
import Axios from "axios";
const App = () => {
const [movieName, setmovieName] = useState("");
const [movieReview, setmovieReview] = useState("");
const [getReview, setgetReview] = useState([]);
useEffect(() => {
Axios.get("http://localhost:3001/api/get", (result) => {
console.log(result.data);
setgetReview(result.data);
});
}, []);
const submitReview = () => {
Axios.post("http://localhost:3001/api/insert", {
movieName: movieName,
movieReview: movieReview
})
.then(() => {
alert("Success");
})
.catch((e) => alert(e));
};
return (
<div className="index">
<h2>movie name</h2>{" "}
<input type="text" onChange={(e) => setmovieName(e.target.value)} />
<h2>movie rating</h2>{" "}
<input type="text" onChange={(e) => setmovieReview(e.target.value)} />
<button onClick={submitReview}>submit</button>
{getReview.map((val) => {
return (
<h1>
Movie name : {val.movieName} Movie review: {val.movieReview}
</h1>
);
})}
</div>
);
};
export default App;
Server/index.js:
const express = require("express");
const mysql = require("mysql");
const cors = require("cors");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(express.json());
const db = mysql.createConnection({
host: "localhost",
root: "root",
password: "",
database: "crudatabase"
});
db.connect((err) => {
if (err) throw err;
});
app.get("/api/get", (req, res) => {
const selectStmt = "SELECT movieName,movieReview FROM movie_review;";
db.query(selectStmt, (err, result) => {
res.send(result);
});
});
app.post("/api/insert", (req, res) => {
const movieName = req.body.movieName;
const movieReview = req.body.movieReview;
const insertStmt =
"INSERT INTO movie_review (movieName,movieReview) VALUES (?,?);";
db.query(insertStmt, [movieName, movieReview], (err, result) => {
console.log(err);
});
});
app.listen(3001, () => {
console.log("Server running on 3001");
});
In the above react and express code I am able to insert the data in the database but after inserting then() part in client is not working. Also the useEffect is not working. I tried many ways but not able to get the reason. I would be glad if someone can solve me the error and all the dependencies are already installed.
In your useEffect, you're passing a callback to Axios.get - this is not consistent with the Axios API (you even do it correctly in the submitReview function!):
useEffect(() => {
// change to Promise.then() chain
Axios.get("http://localhost:3001/api/get").then((result) => {
console.log(result.data);
setgetReview(result.data);
});
}, []);
Your then() chain is not working because your POST response handler never returns a status or a response! Just like in your GET handler, your POST handler needs to let the client know that a request has been successful. e.g. res.send(/*...*/) or even just res.sendStatus(200).
As you are dealing with the promise and have used the thenable syntax while submitting the values but you are not using it while getting the values. try using the below code and check whether this resolves your problem. One more concise method to deal with promises is to use async/await try to use the below code hopes this resolves your problem.
useEffect(() => {
const getMovies = async () => {
try {
let { data } = await Axios.get("http://localhost:3001/api/get");
console.log(data);
setgetReview(data);
} catch (error) {
console.log(error);
}
};
getMovies();
}, []);
Your useEffect is returning a promise try to use async await or .then on your code.
Try to change it from:
useEffect(() => {
Axios.get("http://localhost:3001/api/get", (result) => {
console.log(result.data);
setgetReview(result.data);
});
}, []);
To:
useEffect(() => {
const get_data = async () => {
try {
const result = await Axios.get("http://localhost:3001/api/get")
console.log(result.data)
setgetReview(result.data)
} catch (e) {
console.log(e)
}
}
get_data()
}, []);
I'm starting to use the msw (mock service worker) after watching this example of how to use it for testing API calls in React applications.
Is there any way that we can spy on the mock service worker?
For example:
import React from 'react'
import { render, act, await } from '#testing-library/react'
import userEvent from '#testing-library/user-event'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import SearchBox from '.'
const fakeServer = setupServer(
rest.get(
'https://api.flickr.com/services/rest/?method=flickr.photos.search',
(req, res, ctx) => res(ctx.status(200), ctx.json({ data: { photos: { photo: [] },},}))
)
)
beforeAll(() => {fakeServer.listen()})
afterEach(() => {fakeServer.resetHandlers()})
afterAll(() => fakeServer.close())
test('it calls Flickr REST request when submitting search term', async () => {
const { getByLabelText } = render(<SearchBox />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input,'Finding Wally')
await userEvent.click(submitButton)
})
await wait()
// TODO: assert that the fakeServer was called once and with the correct URL
})
The component to test looks like this:
import React, { useState } from 'react'
import axios from 'axios'
import './index.css'
function SearchBox({ setPhotos }) {
const [searchTerm, setSearchTerm] = useState('')
const handleTyping = (event) => {
event.preventDefault()
setSearchTerm(event.currentTarget.value)
}
const handleSubmit = async (event) => {
event.preventDefault()
try {
const restURL = `https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${
process.env.REACT_APP_API_KEY
}&per_page=10&format=json&nojsoncallback=1'&text=${encodeURIComponent(
searchTerm
)}`
const { data } = await axios.get(restURL)
const fetchedPhotos = data.photos.photo
setPhotos(fetchedPhotos)
} catch (error) {
console.error(error)
}
}
return (
<section style={styles.container}>
<form action="" method="" style={styles.form}>
<input
aria-label="Search Flickr"
style={styles.input}
value={searchTerm}
onChange={handleTyping}
/>
<button
aria-label="Submit search"
style={styles.button}
onClick={handleSubmit}
>
SEARCH
</button>
</form>
</section>
)
}
I have got a working test, but I feel it leans towards an implementation test since it uses a spy on the setPhotos
test('it calls Flickr REST request when submitting search term', async () => {
const fakeSetPhotos = jest.fn(() => {})
const { getByLabelText } = render(<SearchBox setPhotos={fakeSetPhotos} />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input, 'Finding Walley')
await userEvent.click(submitButton)
})
await wait()
expect(fakeSetPhotos).toHaveBeenCalledWith([1, 2, 3])
})
The devs at mswjs are really nice and helpful. They took their time to advice me on how to approach it.
TLDR;
The current working test I got is fine - just suggested an alternative to jest.fn() - I do like the readability of the their suggestion:
test('...', async () => {
let photos
// Create an actual callback function
function setPhotos(data) {
// which does an action of propagating given data
// to the `photos` variable.
photos = data
}
// Pass that callback function as a value to the `setPhotos` prop
const { getByLabelText } = render(<SearchBox setPhotos={setPhotos} />)
// Perform actions:
// click buttons, submit forms
// Assert result
expect(photos).toEqual([1, 2, 3])
})
Another thing I wanted to test was that it actually calls a valid REST URL.
You can reflect an invalid query parameter in the response resolver.
If the query parameter is missing/invalid your real server would not
produce the expected data, right? So with MSW your "real server" is
your response resolver. Check the presence or value of that query
parameter and raise an error in case that parameter is invalid.
rest.get('https://api.flickr.com/services/rest/?method=flickr.photos.search',
(req, res, ctx) => { const method = req.url.searchParams.get('method')
if (!method) {
// Consider a missing `method` query parameter as a bad request.
return res(ctx.status(400)) }
// Depending on your logic, you can also check if the value of the `method` // parameter equals to "flickr.photos.search".
return res(ctx.json({ successful: 'response' })) })
Now, if your app misses the method query parameter in the request URL, it would get a 400 response, and shouldn't call the setPhotos callback in case of such unsuccessful response.
If you want to avoid mocking you could spy on axios.get and assert that it was called correctly.
test('it calls Flickr REST request when submitting search term', async () => {
const getSpy = jest.spyOn(axios, 'get');
const { getByLabelText } = render(<SearchBox />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input,'Finding Wally')
await userEvent.click(submitButton)
})
await wait()
expect(getSpy).toHaveBeenCalledTimes(1)
})
I hope you all are good. I am trying to create an update function in Mern, when I tried it on Postman it works fine but when I 'am trying to implement same with React it does not give me any result and also not giving me any error. I 'am unable to figure out What I'am doing wrong. If anyone here can figure out by looking on my code, what is wrong with it, It will be great help. Thanks a lot in advance.
import { getANews, updateNews } from "../../actions/news";
const Edit = ({ router }) => {
const [websiteCategory, setWebsiteCategory] = useState("");
useEffect(() => {
initNews();
}, [router]);
// get a single news to make changes on it
const initNews = () => {
if (router.query._id) {
getANews(router.query._id).then((data) => {
if (data.error) {
console.log(data.error);
} else {
setWebsiteCategory(data.websiteCategory);
}
});
}
};
const onChangeWebsiteCategory = (event) => {
setWebsiteCategory( event.target.value);
};
// update the category field in this particular news
const editNews = (event) => {
event.preventDefault();
updateNews(router.query.id, websiteCategory).then((data) => {
if (data.error) {
console.log("error");
} else {
console.log(websiteCategory);
console.log(data);
}
});
};
return (
<div>
<h3>Edit News</h3>
<form onSubmit={editNews}>
<div className="form-group">
<label>Website-Category </label>
<input
type="text"
className="form-control"
defaultValue={websiteCategory}
onChange={onChangeWebsiteCategory}
/>
</div>
<div className="form-group">
<input type="submit" value="Edit News" className="btn btn-primary" />
</div>
</form>
</div>
);
};
export default withRouter(Edit);
Edit: Thanks for the reply, below is the complete code. Hope this helps you helping me :)
This is my action/news file
import fetch from "isomorphic-fetch";
import { API } from "../config";
// API = http://localhost:3000/
export const getANews = (id) => {
return fetch(`${API}/news/${id}`, {
method: "GET",
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
export const updateNews = (id) => {
return fetch(`${API}/news/${id}`, {
method: "POST",
headers: {
Accept: "application/json",
},
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
your code looks fine. I do not have your ....action/news implementation. So can't run your code.
may I suggest your line 25-26 be switched to
setWebsiteCategory(event.target.value);
you code in my IDE
I looked at your code have repeated your problem. useEffect 2nd parameter should be an empty array. so the initialization is called once in the begining. (read "Preventing Endless Callbacks using Dependencies" section of https://blog.bitsrc.io/fetching-data-in-react-using-hooks-c6fdd71cb24a
see https://codepen.io/lukelee10/pen/VweGByb
is what i have jested up. the getANews and updateNews from line 46 - 53 are my stubbed out call to the API (notice they are both async function(meaning they return promise).
SO, your take away is line 54 - 97.
Finally I found the solution, earlier I was not sending category in my update function. So I removed my update function from action file and fetch the data in my function and send the updated data back in the body.
const editNews = () => {
return fetch(`${API}/news/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(websiteCategory),
})
.then((response) => {
return response.json();
})
.catch((err) => console.log(err));
};
I have an express server set up like so:
app.get('/', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
fetch('https://api.adzuna.com:443/v1/api/property/gb/search/1?app_id=1cd4129d&app_key=key&where=se16bx&distance=5&category=for-sale')
.then(response => response.json())
.then(data => {
res.send(data) // Prints result from `response.json()` in getRequest
})
.catch(error => console.error(error))
});
I then have a Next.js page like so:
class About extends Component {
state = {postcode: null}
handleOnSubmit = e => {
// calling the api passing the postcode
}
render() {
return(
<form>
<input name="postcode" />
<button onClick={this.handleOnSubmit}></button>
</form>
)
}
}
About.getInitialProps = async function() {
const res = await fetch('http://localhost:3001/');
const data = await res.json();
return {
results: data
}
}
the API call in the Express server has a hard coded postcode in it.
where=se16bx
How can I pass that value in a dynamic way based on what the user will put into the form?
Thanks
Yes you can create dynamic API call, for complete documentation you can see from here (Check /posts API).
In your case, you just need to add slug into your endpoint, then pass it to your fetcher function:
app.get('/:id*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
fetch(`https://api.adzuna.com:443/v1/api/property/gb/search/1?app_id=1cd4129d&app_key=key&where=${req.params.id}&distance=5&category=for-sale`)
.then(response => response.json())
.then(data => {
res.send(data) // Prints result from `response.json()` in getRequest
})
.catch(error => console.error(error))
});