I'm doing an API using React, SQL and Sequelize. I'm already finished but now I was asked to do my API request using JavaScript oriented object. The problem is, I don't really know how to do it, and I don't really understand my errors.
This is the class I am trying to do:
API class
class Api {
constructor(hostName, token) {
this.hostName = hostName
this.token = token
}
async getPost() {
return await fetch('api/post/')
.then(res => {
if (!res.ok) {
throw Error(res.statusText + "-" + res.url);
}
return res;
})
.then(post => {
console.log(post)
return post
})
.catch(err => {
console.log("Page non trouvée")
console.log(err)
})
}
}
export const apiRequest = new Api();
Axios config
import axios from 'axios';
class AxiosConfig {
constructor() {
this.axios = axios.create();
this.axios.defaults.baseURL = `${process.env.REACT_APP_API_URL}`;
this.axios.defaults.headers = {
'Content-Type': 'application/json',
};
//All request will wait 2 seconds before timeout
this.axios.defaults.timeout = 2000;
this.axios.defaults.withCredentials = true;
}
GET = async (url) => {
return await this.axios.get(`/${url}`);
}
POST = async (url, payload) => {
return await this.axios.post(`/${url}`, payload);
}
PUT = async (url, payload) => {
return await this.axios.put(`/${url}`, payload);
}
DELETE = async (url) => {
return await this.axios.delete(`/${url}`);
}
}
export const axiosInstance = new AxiosConfig();
HandlePost() is the function I'm tring to put in oriented object
NewPostForm
import React, { useContext, useEffect, useState } from 'react';
import { NavLink } from 'react-router-dom';
import { UserContext } from '../../UserContext';
import { apiRequest } from '../../utils/api';
import { axiosInstance } from '../../utils/AxiosConfig'
import landscape from './../../assets/icons/landscape.svg'
const NewPostForm = () => {
const uid = useContext(UserContext)
const [userPicture, setUserPicture] = useState('')
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [message, setMessage] = useState('')
const [postPicture, setPostPicture] = useState('')
const [file, setFile] = useState('')
useEffect(() => {
const getUserInfo = async () => {
if (uid !== null) {
const userId = uid.userId
await axiosInstance.GET (`api/auth/${userId}`)
.then((res) => {
setFirstName(res.data.firstName)
setLastName(res.data.lastName)
setUserPicture(res.data.profile)
})
.catch((err) => {
console.log(err)
})
}
}
getUserInfo()
}, [uid, firstName, lastName])
console.log('parfait', axiosInstance.GET(`api/post`)) // old one, works perfectly
console.log('objectif', apiRequest.getPost()) // new one, not getting the object I need
// console.log(axiosInstance.GET(apiRequest.getPost`${uid.userId}`)) // error
const handlePost = async () => {
if (message || postPicture) {
const data = new FormData();
data.append("UserId", uid.userId);
data.append("content", message);
if (file) {
data.append("image", file);
}
try {
const res = await axiosInstance.POST(`api/post`, data);
console.log('File uploaded', res.data);
// window.location = '/'
} catch (err) {
console.error('Failed to upload file', err);
}
cancelPost()
} else {
alert("Veuillez entrer un message")
}
}
const handlePicture = (e) => {
setPostPicture(URL.createObjectURL(e.target.files[0]))
setFile(e.target.files[0])
}
const cancelPost = () => {
setMessage('')
setPostPicture('')
setFile('')
}
return (
<form className='post-container' >
<h2 className='h1'>Créer un post</h2>
<NavLink to="/profile">
<figure title='Profil utilisateur' className='new card-header'>
<img className='nav-profile' src={userPicture ? userPicture : "./images/img/profile.png"} width='50px' alt="profil de l'utilisateur" />
<h3 className='h2'>{firstName} {lastName}</h3>
</figure>
</NavLink>
<div className='post-form'>
<textarea
type="text"
name="message"
id="message"
cols="50"
rows="5"
placeholder="Quoi de neuf ?"
onChange={(e) => setMessage(e.target.value)}
value={message}
></textarea>
{postPicture && <img src={postPicture} alt="preview" className="img-preview" />}
</div>
<div className='footer-form'>
<div className='icon'>
<input
type="file"
id='file-upload'
name='file'
accept='.jpg, .jpeg, .png'
onChange={(e) => handlePicture(e)}
/>
<label className='file-input__label' htmlFor="file-upload">
<img className='svg' src={landscape} alt="upload icone paysage" />
Ajouter une l'image
</label>
</div>
<div className='new button-container'>
{message || postPicture ? (
<button className='new cancel-btn' onClick={(e) => cancelPost()}>Annuler</button>
) : null}
<button className='new validate-btn' onClick={(e) => handlePost()}>Envoyer</button>
</div>
</div>
</form>
);
};
export default NewPostForm;
My first console.log() returning the object that I need, when the second (supposed oriented object one) doesn't. I don't know what I'm doing wrong and the documentation doesn't help, I don't have the "good" questions.
Thanks
EDIT :
class Api {
constructor(hostName, token) {
this.hostName = hostName
this.token = token
}
async getPost() {
try {
const res = await fetch('api/post')
if (!res.ok) {
throw Error(res.statusText + "-" + res.url);
}
return await res.json();
} catch(err) {
console.log("Page non trouvée")
console.log(err)
}
}
}
export const apiRequest = new Api();
The problem is with the code that how you used fetch method. This is from the documentation.
The Response object, in turn, does not directly contain the actual JSON response body but is instead a representation of the entire HTTP response. So, to extract the JSON body content from the Response object, we use the json() method, which returns a second promise that resolves with the result of parsing the response body text as JSON.
You have to update your getPost method like below.
async getPost() {
return await fetch('api/post/').then(res => {
if (!res.ok) {
throw Error(res.statusText + "-" + res.url);
}
return res.json();
}).then(post => {
console.log(post)
return post
})
.catch(err => {
console.log("Page non trouvée")
console.log(err)
})
}
Check the documentation
You already using async and await so you don't need to use .then. The code can be updated like below.
async getPost() {
try {
const res = await fetch('api/post/')
if (!res.ok) {
throw Error(res.statusText + "-" + res.url);
}
return await res.json();
} catch(err) {
console.log("Page non trouvée")
console.log(err)
}
}
Related
I'm using ReactJS to build a blog app. I can use axios get, put, delete but NOT POST. Every time I post a new blog, it gives me server responded with a status of 500 (Internal Server Error).
I have been struggle with this issue for a week and couldn't figure out the reason. Thank you very much for your help! Let me know if you need additional information.
Here are my codes:
API
import axios from 'axios'
const baseUrl = `/api/blogs`
let token = null
const setToken = newToken => {
token = `bearer ${newToken}`
}
const getAll = () => {
const request = axios.get(baseUrl)
return request.then(response => response.data)
}
const create = async newBlog => {
const config = {headers: { Authorization: token }}
const response = await axios.post(baseUrl, newBlog, config)
return response.data
}
const update = async (id, newObject) => {
const request = axios.put(`${baseUrl}/${id}`, newObject)
const response = await request
return response.data
}
const remove= async (id) => {
const config = {headers: { Authorization: token }}
const request = axios.delete(`${baseUrl}/${id}`, config)
const response = await request
return response.data
}
const exportedObject = { getAll, create, update, setToken, remove }
export default exportedObject
Frontend App.js
import React, { useState, useEffect, useRef } from 'react'
import blogService from './services/blogs'
import loginService from './services/login'
import Blog from './components/Blog'
import LoginForm from './components/LoginForm'
import BlogForm from './components/BlogForm'
import Togglable from './components/Togglable'
import Notification from './components/Notification'
import axios from 'axios'
const App = () => {
const [blogs, setBlogs] = useState([])
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [user, setUser] = useState(null)
const [loginVisible, setLoginVisible] = useState(false)
const [notificationText, setNotificationText] = useState("")
const [notificationStyle, setNotificationStyle] = useState("notification")
const [Toggle, setToggle] = useState(false)
const BlogFormRef = useRef()
useEffect(() => {
const Data = async () => {
const initialBlogs = await blogService.getAll()
setBlogs( initialBlogs )
}
Data()
}, [])
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem('loggedBlogUser')
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON)
setUser(user)
blogService.setToken(user.token)
}
}, [])
const addBlog = async (blogObject) => {
BlogFormRef.current.toggleVisibility()
if (blogObject.title !== '' && blogObject.author !== '' && blogObject.url !== '') {
const newBlog = await blogService.create(blogObject)
setBlogs(blogs.concat(newBlog))
setNotificationStyle('notification')
setNotificationText(`A new blog ${blogObject.title} by ${blogObject.author} is added`)
setToggle(!Toggle)
setTimeout(() => {
setToggle(false)
}, 5000)
setBlogs('')
console.log(blogObject)
document.location.reload()
} else {
setNotificationStyle('Warning')
setNotificationText('You must fill all fields to create a blog')
setToggle(!Toggle)
setTimeout(() => {
setToggle(false)
}, 5000)
}
}
Backend
const blogsRouter = require('express').Router()
const Blog = require('../models/blog')
const User = require('../models/user')
const jwt = require('jsonwebtoken')
const middleware = require("../utils/middleware")
blogsRouter.get('/', async (request, response) => {
const blogs = await Blog.find({}).populate('user', { username: 1, name: 1 })
response.json(blogs)
})
blogsRouter.get('/:id', (request, response) => {
Blog.findById(request.params.id)
.then(blog => {
if (blog) {
response.json(blog)
} else {
response.status(404).end()
}
})
})
blogsRouter.post('/', middleware.userExtractor, async (request, response) => {
const body = request.body
const user = request.user
const decodedToken = jwt.verify(request.token, process.env.SECRET)
if (!decodedToken.id){
return response.status(401).json({error: 'token missing or invalid'})
}
if(body.title === undefined){
return response.status(400).send({
error: 'title is missing'
})
}
else if(body.author === undefined){
return response.status(400).send({
error: 'author is missing'
})
}
else if(body.url === undefined){
return response.status(400).send({
error: 'url is missing'
})
}
else{
const blog = new Blog({
title: body.title,
author: body.author,
url: body.url,
likes: body.likes,
user: user.id
})
const savedBlog = await blog.save()
//console.log(savedBlog)
//console.log(user)
user.blogs = user.blogs.concat(savedBlog.id)
await user.save()
const populatedBlog = await savedBlog.populate('user', { username: 1, name: 1 }).execPopulate()
response.status(200).json(populatedBlog.toJSON())
}
})
blogsRouter.delete('/:id', middleware.userExtractor, async (request, response) => {
const blog = await Blog.findByIdAndRemove(request.params.id)
const user = request.user
const decodedToken = jwt.verify(request.token, process.env.SECRET)
if(! request.token || !decodedToken.id){
return response.status(401).json({error:'token is missing or invalid'})
}
else if(blog.user.toString() === user.id.toString()){
await Blog.findByIdAndRemove(request.params.id)
response.status(204).end()
}
else{
return response.status(401).json({error:'cannot process deletion'})
}
})
blogsRouter.put('/:id', async (request, response) => {
const body = request.body
const blog = {
title: body.title,
author:body.author,
url: body.url,
likes: body.likes
}
await Blog.findByIdAndUpdate(request.params.id, blog, { new: true })
.then(updatedBlog => {
response.json(updatedBlog)
})
})
module.exports = blogsRouter
Mongoose
const mongoose = require('mongoose')
const blogSchema = new mongoose.Schema({
title: {type:String,
required: true},
author:{type:String,
required: true},
url: {type:String,
required: true},
likes: {type:Number,
default: 0},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'}
})
blogSchema.set('toJSON', {
transform: (document, returnedObject) => {
returnedObject.id = returnedObject._id.toString()
delete returnedObject._id
delete returnedObject.__v
}
})
const Blog = mongoose.model('Blog', blogSchema)
module.exports = Blog
Additional information: terminate output, the info is actually POST just cannot render in the front
terminal output
I'm making a full stack Rick and Morty application. Characters on the screen and the user can login and click on them to add them to favorites and then click on them on the favorites page to delete them from the favorites page.
The application works but crashes after a few minutes saying that a fetch request didn't work. In network section of the developer tools, these requests to add or delete characters are coming up as (pending) and then coming up as failures like two minutes later. At the same time, the requests are working from the perspective of the application, meaning that if I add or delete characters as a user and then logout and log back in, the changes are still there. The register and login requests to the backend are working normally with statuses of 200 as well. What's happening here?
The backend:
const express = require('express');
const application = express();
const mongoose = require('mongoose');
application.use(express.json());
mongoose.connect('process.env.DATABASE_PASSWORD')
.then(console.log('Connected to database'));
const db = mongoose.connection;
const port = process.env.PORT || 8080;
application.post('/register', (request, response) => {
const username = request.body.username;
const password = request.body.password;
const favorites = [];
db.collection('data').insertOne({
username,
password,
favorites,
});
});
application.post('/login', async (request, response) => {
const username = request.body.username;
const password = request.body.password;
const findUser = await db.collection('data').findOne({
username,
password,
});
if (findUser) {
response.send({ message: 'Welcome, ' + username + "!", user: username, favorites: findUser.favorites });
} else {
response.send({ message: 'Login unsuccessful'});
}
});
application.post('/addFavorite', (request, response) => {
const userNow = request.body.username;
const favoritesHere = request.body.favoritesCopy;
console.log({userNow, favoritesHere});
db.collection('data').updateOne(
{ username: userNow },
{ $set: { favorites: favoritesHere }},
)
});
application.post('/deleteFavorite', (request, response) => {
const userNow = request.body.username;
const favoritesHere = request.body.theData;
db.collection('data').updateOne(
{ username: userNow },
{ $set: { favorites: favoritesHere }},
);
});
application.listen(port, () => {
console.log('Application listening');
});
The frontend fetch add request (the delete request is similar):
import React, { useState, useEffect } from 'react';
import logo from '../rickandmortylogo.png';
import { useSelector, useDispatch } from 'react-redux';
import { addFavorite } from '../index.js';
const Body = () => {
const [characters, setCharacters] = useState([]);
const [currentName, setCurrentName] = useState('Placeholder');
const [nameInput, setNameInput] = useState('');
const [locationInput, setLocationInput] = useState('');
const [loading, setLoading] = useState(true);
const favorites = useSelector(state => state.favoritesList);
const userNow = useSelector(state => state.currentUser);
const loggedIn = useSelector(state => state.loggedIn);
const dispatch = useDispatch();
useEffect(() => {
let isMounted = true;
let url = 'https://rickandmortyapi.com/api/character/';
let array = [];
const getData = async () => {
for (let i = 1; i < 4; i++) {
let response = await fetch(url);
let data = await response.json();
for (let j = 0; j < 20; j++) {
array.push(data.results[j]);
}
url = data.info.next;
}
if (isMounted) {
setCharacters(array);
setLoading(false);
}}
getData();
return () => {
isMounted = false;
}
}, []);
const readInput = (e) => {
setNameInput(e.target.value);
}
const readLocationInput = (e) => {
setLocationInput(e.target.value);
}
const addData = (a, b, c, d) => {
const array = [a, b, c, d];
const favoritesCopy = [...favorites];
favoritesCopy.push(array);
dispatch(addFavorite(array));
if (loggedIn === true) {
fetch('/addFavorite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
favoritesCopy,
username: userNow,
}),
});
}
};
return (
<div className="pt-5">
<div className="text-center mt-5">
<img src={logo} className="img-fluid" />
</div>
<h2>Click on a character here to add them to your favorites. Choose "Check Favorites" in the menu bar to see your favorites and "Search Characters" to come back.</h2>
<div className="all">
<h4>Search by name:</h4>
<input onChange={readInput} />
<h4>Search by location:</h4>
<input onChange={readLocationInput} />
<br />
<div className="row m-1">
{loading ? 'Loading can take a few seconds. Your Rick and Morty experience will be ready soon!' : characters.filter((item) => {
if (nameInput == "") {
return item;
} else {
if (item.name.toLowerCase().includes(nameInput.toLowerCase())) {
return item;
}
}
}).filter((item) => {
if (locationInput == "") {
return item;
} else {
if (item.location.name.toLowerCase().includes(locationInput.toLowerCase())) {
return item;
}
}
}).map((item, id) => {
return (
<>
<div className="col-md-4 border border-dark rounded" id="square" onClick={() => addData(item.name, item.image, item.location.name, item.status)}>
<h2>{item.name}</h2>
<img src={item.image} className="border rounded" />
<h4>Location: {item.location.name}</h4>
<h4>Status: {item.status}</h4>
</div>
</>
)
})}
</div>
</div>
</div>
);
};
export default Body;
You never end the request. You don't send anything in the response and don't call response.end either, nor next. That's why your request never ends.
Here are some examples:
Success message with content
res.status(200).json({ success: true});
Success message without content
res.sendStatus(204);
Of course requests are pending, you never send anything on related actions:
Use res.send and send something, or at least in case of success, send a success status like:
204 for a no content success operation, like a DELETE for example.
201 for a POST operation creating a new resource.
5xx for errors
I have multiple API calls with fairly lengthy, yet similar, response/error handling for each call.
What is the best non-repetitive ways to make multiple independent api calls that update state using fetch?
Copying and pasting 40+ instances of fetch doesn't seem right.
I want to avoid doing this ....
fetch(url,options)
.then((response) => {
// ...
return response.json
})
.then((data) => {
setState(data)
//...
})
.catch((err) => {
//Error logic here
})
Here's what I've done so far:
I made (found and modified) a useFetch hook...
useFetch.ts
//Only calls fetch() when .load() is called.
const useFetch = (path : string, HttpMethod : string, dependencies : any = [] , body : {} | undefined = undefined) => {
const history = useHistory()
const [response, setResponse] = useState<any>({});
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(false);
const [controller, setController] = useState(2)
const [isReady, setIsReady] = useState<any>(false)
const load = ():void => {
setError("")
//This prevents useEffect from triggering on declaration.
if (isReady) {
//Math.random() is just to get useEffect to trigger.
setController(Math.random())
}
}
const token = localStorage.getItem("token");
let requestOptions:any = {
method: HttpMethod,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "* always",
Authorization: "Token " + token,
},
};
if (body !== undefined) {
requestOptions["body"] = {
body: JSON.stringify(body)
}
}
const URI = BASE_URI + path
useEffect(() => {
const fetchData = async () => {
if (controller !== 2) {
setIsLoading(true);
try {
const res = await fetch(URI, requestOptions);
const json = await res.json();
if (json?.action == "ENFORCE_BILLING" ) {
history.push(BILLING_CREDENTIALS_PATH, { enforceBillingPopUp: true });
}
if (json?.action == "ENFORCE_SMS_CONFIRMATION") {
// Should we log user out, as well?
history.push(CONFIRMATION_CODE_PATH)
}
if (res.ok) {
setResponse(json);
setIsLoading(false)
} else {
setError(json)
setIsLoading(false)
}
} catch (err) {
setError(err);
// Error logic here...
}
}
}
};
fetchData()
setIsReady(true)
}, [controller, ...dependencies]);
return { response, setResponse ,error, isLoading, load, isReady };
};
Component.tsx
//Inside react functional component...
// Prepares to fetch data from back-end
const data1 = useFetch(PATH1, "GET");
const data2 = useFetch(PATH2, "GET");
const data3 = useFetch(PATH3, "GET");
useEffect(() => {
// Initial on load data fetch
// .load() fetches data
data1.load();
data2.load();
data3.load();
}, [activeReservations.isReady]);
// Sort data depending on sort selection
...
Is useFetch considered bad practice? What are the advantages of using Redux, instead?
Any help would be greatly appreciated. Thanks.
I have this post screen where I can pick an image from the camera roll and type a text and I want it to be saved in Firebase.
Here is my code in fire.js
addPost = async({text,localUri}) => {
const remoteUri = await this.uploadPhotoAsync(localUri)
return new Promise((res,rej) => {
this.firestore.collection("posts").add({
text,
uid: this.uid,
timestamp:this.timestamp,
image: remoteUri
})
.then(ref => {
res(ref)
})
.catch(error => {
rej(error)
})
})
}
uploadPhotoAsync = async uri => {
const path = `photos/${this.uid}/${Date.now()}.jpg`
return new Promise(async (res,rej) => {
const response = await fetch(uri)
const file = await response.blob()
let upload = firebase.storage().ref(path).put(file)
upload.on(firebase.storage.TaskEvent.STATE_CHANGED,snapshot => {},
err => {
rej(err)
},
async () => {
const url = await upload.snapshot.ref.getDownloadURL()
res(url)
}
)
})
}
And here is my postscreen.js screen where I'm getting the error can't find variable atob,
please suggest me a solution.
handlePost = () => {
Fire.shared.addPost({text:this.state.text.trim(),
localUri:this.state.image })
.then(ref => {
this.setState({text:"",image:undefined})
this.props.navigation.goBack()
}).catch(error => {
alert(error)
})
}
pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing:true,
aspect:[4,3]
})
if(!result.cancelled) {
this.setState({image: result.uri})
}
}
By the way, I can see the image is saved in Firestore storage but I can't see the text and photo in the Firestore database
This is a bug in some versions of firebase.
A workaround is to import base64 in the app.js and define it in case it's not defined.
import {decode, encode} from 'base-64'
if (!global.btoa) { global.btoa = encode }
if (!global.atob) { global.atob = decode }
I`m new in react, and I get problem with registration:
http.hook.js:17 POST http://localhost:3000/api/auth/register 500 (Internal Server Error)
Error: Что-то пошло не так :(, попробуйте снова
at http.hook.js:21
at async registerHandler (Registration.js:24)
code http.hook.js :
import {useState, useCallback} from 'react';
export const useHttp = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback( async (url, method = 'GET', body = null, headers = {}) => {
setLoading(true);
try {
if (body) {
body = JSON.stringify(body);
headers['Content-Type'] = 'application/json'
}
const response = await fetch(url, {method, body, headers});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Что-то пошло не так :(');
};
setLoading(false);
return data;
} catch (e) {
console.log(e)
setLoading(false);
setError(e.message);
throw e;
}
}, [])
const clearError = useCallback( () => setError(null), []);
return { loading, request, error, clearError }
}
and code Registration.js without front-end:
import React, { useState, useEffect } from 'react';
import { useHttp } from '../hooks/http.hook';
import { useMessage } from '../hooks/message.hook';
export const Registration = () => {
const message = useMessage();
const {loading, error, request, clearError} = useHttp();
const [form, setForm] = useState({
nick: '', email: '', password:''
});
useEffect(() => {
console.log(error);
message(error);
clearError();
}, [error, message, clearError]);
const changeHandler = event => {
setForm({ ...form, [event.target.name]: event.target.value });
};
const registerHandler = async () => {
try {
const data = await request('/api/auth/register', 'POST', {...form});
console.log('Data', data)
} catch (e) {
console.log(e)
}
};
I think the problem with your server-side.
HTTP response code 500 means that the server encountered an unexpected condition.
Test your endpoint by curl or XHR request.
Also maybe you send not valid data.