Here's my React code in posting:
const [nickname, setNickname] = useState('')
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [agree, setAgree] = useState(false)
const [thepost, setThePost] = useState({})
const DoSubmit = async () => {
setHasAlert(false)
if (nickname == '' || nickname == null) {
setAlertContent("Please enter a Nickname!")
setHasAlert(true)
return
}
if (title == '' || title == null) {
setAlertContent("Please enter a Title!")
setHasAlert(true)
return
}
if (content == '' || content == null) {
setAlertContent("Cannot post an empty content!")
setHasAlert(true)
return
}
if (agree == false) {
setAlertContent("Please agree to the post rules!")
setHasAlert(true)
return
}
const data = {
title: title,
nickname: nickname,
content: content
}
setThePost(data)
await fetch('http://localhost:3001/posts', {
method: "POST",
body: thepost
})
.then(res => {
setIsError(false)
setAlertContent("Posted Successfully!")
setHasAlert(true)
console.log(res)
})
.catch(err => {
setAlertContent(err.message)
setHasAlert(true)
})
}
And for my Node/Express backend:
router.post('/', async(req,res) => {
const post = new Post({
nickname: req.body.nickname,
title: req.body.title,
content: req.body.content
})
console.log(req.body)
try {
const savedPost = await post.save()
res.json(savedPost)
console.log(savedPost)
}
catch(err){
res.json({message:err})
}
})
However, req.body is empty on my backend. And on React, it says "Successfully Posted!" and its status is 200 (so there were no errors). It just doesn't save and it doesn't see the data from that was sent from React as well.
Any ideas? Thank you!
BTW: On React, all the fields (nickname, title, content) are completely working on the frontend.
UPDATE
I'm using /posts in react but / in node since I already used a middleware on node's main app.js like this:
const postsRoute = require('./routers/posts')
app.use('/posts', postsRoute)
I have a React page getting all the posts:
const fetchData = async () => {
const data = await fetch('http://localhost:3001/posts')
const dataPost = await data.json()
setAllPosts(dataPost)
}
And it's all working fine.
This appears to be failing for three separate reasons, each of which would independently cause the symptoms you are describing.
You are passing the wrong variable
You collect the data in data
You call setThePost(data) to update the state
You pass thepost to the body
The problem is that thepost is the previous value of the state. You won't get the new value until the component is re-rendered and a new DoSubmit function created, but which time it is too late — you're working with the existing DoSubmit function.
Don't use the state for this. Just use the data immediately.
You are passing fetch an object
Look at the MDN docs:
Any body that you want to add to your request: this can be a Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object
A plain object is none of those things.
You should create a suitable object with either URLSearchParams or FormData depending on what format the server is expecting the data in. You might also want to encode it as JSON (and set the right Content-Type header).
The server has no body parsing middleware
See the express docs:
By default, it is undefined, and is populated when you use body-parsing middleware such as express.json() or express.urlencoded().
You need to include body parsing middleware that matches the type of data you picked when you replaced the object with something suitable in the previous section of this answer.
The combination of URLSearchParams and express.urlencoded() would probably be most suitable.
You are fetching from url : http://localhost:3001/posts in your react code but in node js you are using router.post('/', async(req,res) but you should use router.post('/post', async(req,res) instead.
See in router.post you should add posts same as you are using for fetching.
Also after correction try to do hard reload the page or try to test on incognito mode on getting empty req.body.
Related
I am currently working on social media mern stack react app. I am using node js and express as my backend services , also using mongoose to store my data and axios and redux thunk which connect the backend to the front end. Till now I had no issue recieving and sending data to the server. Right now I am trying to create search post get request ,base on a keyword the user entered. The issue with it, that when I am sending the keyword to the server instead of recieving the string it gets undefined value, like redux thunk not sending anything. I will be very thankful if someone could help me with that. I am watching the code over and over again and can't find out the reason for that.
My post controller class(I copied only the relevant function):
import express from "express";
const app = express();
import Post from "../model/PostModel.js";
import ErrorHandlng from "../utilities/ErrorHandling.js";
import bodyParser from "body-parser";
import catchAsync from "../utilities/CatchAsync.js";
import User from "../model/UserModel.js";
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
export const getPosts = catchAsync(async (req, res, next) => {
const data = req.body.keyword;
const page = parseInt(req.query.page || "0");
const PAGE_SIZE = 20;
const query = new RegExp(data, "i");
const total = await Post.countDocuments({});
const posts = await Post.find({ $or: [{ title: query }, { content: query }] })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * page);
if (!posts) {
return next(new ErrorHandlng("No posts were found", 400));
}
res.status(200).json({
status: "success",
data: {
totalPages: Math.ceil(total / PAGE_SIZE),
posts,
},
});
});
My api class(front end,copied only the calling for that specific get request):
import axios from "axios";
const baseURL = "http://localhost:8000";
axios.defaults.withCredentials = true;
const API = axios.create({
baseURL,
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export const getPostsByKeyword = (keyword, page) =>
API.get(`/post/getPostsByKey?page=${page}`, keyword);
Post slice class:
export const fetchPostsByKeyWord = createAsyncThunk(
"post/getKeyword",
async ({ keyword, page }, { fulfillWithValue, rejectWithValue }) => {
try {
const response = await api.getPostsByKeyword(keyword, page);
if (response.statusCode === "400") {
throw new Error("There are no available posts");
}
const fetchData = await response.data.data.posts;
const totalPages = await response.data.data.totalPages;
return fulfillWithValue({ fetchData, totalPages });
} catch (err) {
console.log(err.response.message);
}
}
);
const initialState = { status: "undefined" };
const PostSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {},
});
export const postActions = PostSlice.actions;
export default PostSlice;
Calling the backend:
dispatch(fetchPostsByKeyWord({ keyword, page }))
.unwrap()
.then((originalPromiseResults) => {
console.log("thte " + " " + originalPromiseResults.totalPages);
console.log("The data is" + originalPromiseResults.fetchData);
setTotalPages(originalPromiseResults.totalPages);
})
.catch((err) => {
console.log(err.message);
});
As you can see I have not copied the whole code, I copied only the parts that are relevants for the question.
Browsers cannot currently send GET requests with a request body. XMLHttpRequest (which Axios uses) will ignore it and fetch() will trigger an error.
See also HTTP GET with request body for extra discussion on why trying this might be a bad idea.
You should instead pass everything required in the query string, preferably via the params option so it is correctly encoded...
export const getPostsByKeyword = (keyword, page) =>
API.get("/post/getPostsByKey", { params: { page, keyword } });
and grab the data via req.query server-side.
const { page, keyword } = req.query;
With vanilla JS, you can use URLSearchParams to construct the query string...
const params = new URLSearchParams({ page, keyword });
// XHR
const xhr = new XMLHttpRequest();
xhr.open("GET", `/post/getPostsByKey?${params}`);
// Fetch
fetch(`/post/getPostsByKey?${params}`); // GET is the default method
Your Axios instance creation could also be a lot simpler...
Axios is usually quite good at setting the correct content-type header, you don't have to
Your Express app isn't doing any content-negotiation so you don't need to set the accept header
Unless you're actually using cookies (which it doesn't look like), you don't need credential support
const API = axios.create({ baseURL });
So i am creating a javascript searchbar that uses a fetch command to fetch data from a server. This server is not my own and i just used it as a template. This script works fine.But what i want to do is replace the fetch api with my own
Demonstation of code
const userCardTemplate = document.querySelector("[data-user-template]")
const categoriesSearch = document.querySelector("[data-user-cards-container]")
const searchInput = document.querySelector("[data-search]")
let users = []
searchInput.addEventListener("input", e => {
const value = e.target.value.toLowerCase()
users.forEach(user => {
const isVisible =
user.name.toLowerCase().includes(value) ||
user.email.toLowerCase().includes(value)
user.element.classList.toggle("hide", !isVisible)
})
})
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(data => {
users = data.map(user => {
const card = userCardTemplate.content.cloneNode(true).children[0]
const header = card.querySelector("[data-header]")
const body = card.querySelector("[data-body]")
header.textContent = user.name
body.textContent = user.email
categoriesSearch.append(card)
return { name: user.name, email: user.email, element: card}
})
})
I think ive made my own fetch api via github which follows.
https://gist.github.com/UllestReal/09ca3d968dda94535e8fc25b998a6ce5#file-gistfile1-txt
But when i replace the first template api with my own, it wont fetch the code. Can someone tell me what im doing wrong?
I just wrote everything in one go it was alot easier. Just look above for my entire problem
What you have there is just a json file. But whats missing is a server that would serve the file upon a request.
Go to the link you pasted
Click Raw button
Copy the url
const url = 'https://gist.githubusercontent.com/UllestReal/09ca3d968dda94535e8fc25b998a6ce5/raw/705e7b335cd24e382c15e851ea8888fbdc9cdae4/gistfile1.txt'
fetch(url).then(res => res.json()).then(console.log)
I'm currently working on a search functionality in React Native using axios.
When implementing search functionality i'm using debounce from lodash to limit the amount of requests sent.
However, since request responses are not received in same order there is a possibility of displaying incorrect search results.
For example when the user input 'Home deco' in input field there will be two requests.
One request with 'Home' and next with 'Home deco' as search query text.
If request with 'Home' takes more time to return than second request we will end up displaying results for 'Home' query text not 'Home deco'
Both results should be displayed to the user sequentially, if responses are returned in order but if 'Home' request is returned after 'Home deco' request then 'Home' response should be ignored.
Following is a example code
function Search (){
const [results, setResults] = useState([]);
const [searchText, setSearchText] = useState('');
useEffect(() => {
getSearchResultsDebounce(searchText);
}, [searchText]);
const getSearchResultsDebounce = useCallback(
_.debounce(searchText => {
getSearchResults(searchText)
}, 1000),
[]
);
function getSearchResults(searchText) {
const urlWithParams = getUrlWithParams(url, searchText);
axios.get(urlWithParams, { headers: config.headers })
.then(response => {
if (response.status === 200 && response.data)
{
setResults(response.data);
} else{
//Handle error
}
})
.catch(error => {
//Handle error
});
}
return (
<View>
<SearchComponent onTextChange={setSearchText}/>
<SearchResults results={results}/>
</View>
)
}
What is the best approach to resolve above issue?
If you want to avoid using external libraries to reduce package size, like axios-hooks, I think you would be best off using the CancelToken feature included in axios.
Using the CancelToken feature properly will also prevent any warnings from react about failing to cancel async tasks.
Axios has an excellent page explaining how to use the CancelToken feature here. I would recommend reading if you would like a better understanding of how it works and why it is useful.
Here is how I would implement the CancelToken feature in the example you gave:
OP clarified in the replies that they do not want to implement a cancelation feature, in that case I would go with a timestamp system like the following:
function Search () {
//change results to be a object with 2 properties, timestamp and value, timestamp being the time the request was issued, and value the most recent results
const [results, setResults] = useState({
timeStamp: 0,
value: [],
});
const [searchText, setSearchText] = useState('');
//create a ref which will be used to store the cancel token
const cancelToken = useRef();
//create a setSearchTextDebounced callback to debounce the search query
const setSearchTextDebounced = useCallback(
_.debounce((text) => {
setSearchText(text)
), [setSearchText]
);
//put the request inside of a useEffect hook with searchText as a dep
useEffect(() => {
//generate a timestamp at the time the request will be made
const requestTimeStamp = new Date().valueOf();
//create a new cancel token for this request, and store it inside the cancelToken ref
cancelToken.current = CancelToken.source();
//make the request
const urlWithParams = getUrlWithParams(url, searchText);
axios.get(urlWithParams, {
headers: config.headers,
//provide the cancel token in the axios request config
cancelToken: source.token
}).then(response => {
if (response.status === 200 && response.data) {
//when updating the results compare time stamps to check if this request's data is too old
setResults(currentState => {
//check if the currentState's timeStamp is newer, if so then dont update the state
if (currentState.timeStamp > requestTimeStamp) return currentState;
//if it is older then update the state
return {
timeStamp: requestTimeStamp,
value: request.data,
};
});
} else{
//Handle error
}
}).catch(error => {
//Handle error
});
//add a cleanup function which will cancel requests when the component unmounts
return () => {
if (cancelToken.current) cancelToken.current.cancel("Component Unmounted!");
};
}, [searchText]);
return (
<View>
{/* Use the setSearchTextDebounced function here instead of setSearchText. */}
<SearchComponent onTextChange={setSearchTextDebounced}/>
<SearchResults results={results.value}/>
</View>
);
}
As you can see, I also changed how the search itself gets debounced. I changed it where the searchText value itself is debounced and a useEffect hook with the search request is run when the searchText value changes. This way we can cancel previous request, run the new request, and cleanup on unmount in the same hook.
I modified my response to hopefully achieve what OP would like to happen while also including proper response cancelation on component unmount.
We can do something like this to achieve latest api response.
function search() {
...
const [timeStamp, setTimeStamp] = "";
...
function getSearchResults(searchText) {
//local variable will always have the timestamp when it was called
const reqTimeStamp = new Date().getTime();
//timestamp will update everytime the new function call has been made for searching. so will always have latest timestampe of last api call
setTimeStamp(reqTimeStamp)
axios.get(...)
.then(response => {
// so will compare reqTimeStamp with timeStamp(which is of latest api call) if matched then we have got latest api call response
if(reqTimeStamp === timeStamp) {
return result; // or do whatever you want with data
} else {
// timestamp did not match
return ;
}
})
}
}
I'm calling a page withRouter(Page) and expect the variable for the page (the page is called [category].js) to be present on initial page load. Query itself is there, the key is there, but the value is 'undefined.' There seem to be a few calls to getInitialProps on the server side with 2/3 being undefined.
The react component has a constructor, etc. it's not a functional component.
This is my current getInitialProps:
Category.getInitialProps = async ({ req, query }) => {
let authUser = req && req.session && req.session.authUser
let categoryData = {}
let categoryItemData = {}
let category = query.category
if(category){
let res = await fetch(url1,
{
method: 'POST',
credentials: 'include',
})
categoryData = await res.json();
let categoryItemsRes = await fetch(url2,
{
method: 'POST',
credentials: 'include',
})
categoryItemData = await categoryItemsRes.json();
}
return { query, authUser, categoryData, categoryItemData }
}
This might be redundant at this point, but I ran into this as well and found the docs explain this here
During prerendering, the router's query object will be empty since we do not have query information to provide during this phase. After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.
You might try this instead:
export async function getServerSideProps(ctx) {
const { id } = ctx.query;
return {
props: {
id,
},
};
}
This way it gets the query params when rendering server side, so they're instantly available.
For others who use express custom server, to fix the undefined params, we have to set the dynamic route at server.js as follow:
# server.js
...
app.prepare().then(() => {
const server = express();
....
server.get('/product/:category', (req, res) => {
const { category } = req.params;
return app.render(req, res, `/product/${category}`, req.query)
})
...
}
And then, as Valentijn answers, we can get the category params.
# pages/product/[category].js
....
export async function getServerSideProps(ctx) {
const {category} = ctx.params;
return {
props: {
category
},
};
};
...
The key is dynamic path /product/${category}. Don't use /product/:category
I'm using his logic on the frontend, but I'm having some trouble actually receiving that data on the backend. I'm using the Sails.js framework. Any suggestions?
handleSubmit = () => {
// Gathering together the data you want to send to API
const payload = {
subject: this.state.subject,
message: this.state.message,
};
this.handleAjaxRequest(payload);
};
// Method to send data to the backend
// Making the req -I'm using Axios here.
handleAjaxRequest = (payload) => {
let request = axios({
method: 'post',
url: '/api/',
data: payload,
headers: 'Content-Type: application/json'
});
// Do stuff with the response from your backend.
request.then(response => {
console.debug(response.data);
})
.catch(error => {
console.error(error);
})
};
I used to do this using Express and didn't have these problems.
Any help, method, a suggestion is more than welcome :)
Please forgive my ignorance, I'm just here to learn.
Okay, so the first thing I had to do is generate a new restful API using the command sails generate api data. In the package.json file I set up a proxy that includes the backends endpoint, like this "proxy": "http://localhost:1337" - I mean, you don't need to do this, but if you don't then you have to include this URL part on every request. Because it doesn't change, it's pretty convenient to do so.
On the frontend, I made a function sendData() that takes the necessary data from my previous component (depending on what the user selected) and send that data using axios to the backend -->
sendData = () => {
const { relYear } = this.props.history.location.state.dev;
const { relMonth } = this.props.history.location.state.dev;
const selectedMonth = moment().month(relMonth).format("MM");
const finalSelect = parseInt(relYear + selectedMonth, 10);
axios.post('/data', { 'selectedDate' : finalSelect })
.then(res => console.log('Data send'))
.catch(err => console.error(err));
}
On the backend I fetched the data, did the calculations and send back the result to the frontend of my app. -->
getApiData = () => {
let apiData = [];
axios.get('/data')
.then(res => {
let first = Object.values(res.data.pop()).shift(); // Getting the relevant 'selectedDate'
apiData.push(first);
}).catch(err => console.error(err));
return apiData;
}