Uploading files from React to Node - cannot access File object - node.js

I'm struggling to access the FileList from an input type="file" submitted from a React frontend to a Node backend. From my browser console I can see that the desired list of files is being submitted as the form data.
Eventually worked out that the FileList looks like an array but isn't! So got the length of it using Object.keys (interestingly other answers recommended here didn't work for me).
But no matter what I try I can't access the individual File objects... I can now cycle through the items in the FileList but I need to be able to get the filename to upload/move it to a new location on my server. It looks like they're empty objects in node but the browser is showing that it's sending the information I need.
Why are the objects I am iterating through empty?
I'm stumped.
Thanks for your help!
Browser Console Output
Node.js
router.post("", (req, res) => {
var tempFiles = req.body.files;
var fileCount = Object.keys(tempFiles).length;
console.log("FileList Keys: " + Object.keys(tempFiles));
console.log("Length: " + Object.keys(tempFiles).length);
console.log("Object Length: " + Object.keys(tempFiles['0']).length);
// Loop through files
for (let i = 0; i < fileCount; i++) {
let file = tempFiles[i];
console.log(tempFiles[i]);
}
}
Node console output:
FileList Keys: 0,1
Length: 2
Object Length: 0
{}
{}
React Form
import React from 'react';
import axios from 'axios';
import {useState} from 'react';
import { useForm } from "react-hook-form";
import { Stack, Input ,InputRightElement, InputGroup, Button, FormControl, FormLabel, FormHelperText, Checkbox, Radio, RadioGroup } from '#chakra-ui/react';
import BasicBanner from '../../core/components/banners/BasicBanner';
export default function UploadEvidence(props) {
const [data, setData] = useState("");
const [formMessage, setFormMessage] = useState("");
const { register, formState: { errors }, handleSubmit } = useForm();
const onError = (errors, e) => console.log(errors, e);
const onSubmit = (data, e) => {
console.log(data);
axios.post('http://localhost:5000/api/uploads', data)
.then(function (response) {
console.log(response);
setFormMessage("Upload successful");
})
.catch(function (error) {
console.log(error);
setFormMessage("Error uploading");
});
}
return (
<form onSubmit={handleSubmit(onSubmit, onError)} enctype="multipart/form-data">
<Stack spacing="10">
{formMessage &&
<BasicBanner message={formMessage} />
}
<Input
type="file"
accept="pdf/*"
multiple
{...register("files", {required: true })}
aria-invalid={errors.active ? "true" : "false"}
/>
</Stack>
<FormControl mt={4}>
<Button type="submit" colorScheme='blue'>Save</Button>
</FormControl>
</form>
)
}
Node server.js
const express = require('express')
const PORT = process.env.PORT || 5000
const app = express()
const bodyParser = require("body-parser");
const fileupload = require("express-fileupload");
var cors = require('cors');
app.use(cors());
app.use(fileupload());
app.use(express.static("files"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
... Routes ...

Related

useNavigate hook not working in my react app

I am trying to develop a page where, after registering yourself, you can login with the OTP, which is gonna get through nodemailer module in react.
I have used postman API to test my REST API. It works fine.
But the main the problem is after sending mail, I canbot go to other page.
import "../styles/mix.css";
import { Navigate, NavLink, useNavigate } from "react-router-dom";
import { useState } from "react";
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { sentOtpFunction } from "../services/Apis";
export default function Login() {
const [email,setEmail]=useState("");
const navigate=useNavigate();
//sendOtp
const sendOtp= async (e)=>{
e.preventDefault();
if(email===""){
toast.error("Enter Your Email!");
}
else if(!email.includes('#')){
toast.error("Enter Valid Email!");
}
else{
const data={
email:email
}
const response=await sentOtpFunction(data);
console.log(response);
if(response.status===200){
navigate("/user/otp");
}
else{
toast.error(response.response.data.error)
}
}
}
return (
<>
<section>
<div className="form_data">
<div className="form_heading">
<h1>Welcome Back, Log In</h1>
<p>Hi, we are glad you are back. Please login.</p>
</div>
<form>
<div className="form_input">
<label htmlFor="email">Email</label>
<input type="email" name="email" onChange={(e)=>setEmail(e.target.value)} id="" placeholder="Enter your Email address"></input>
</div>
<button className="btn" onClick={sendOtp}>Login</button>
<p>Don't have an account? <NavLink to="/register">Sign up</NavLink></p>
</form>
</div>
<ToastContainer/>
</section>
</>
)
}
This is sendOtpFunction:
import {commonrequest} from './ApiCall';
import {BACKEND_URL} from './helper';
export const registerfunction=async(data)=>{
return await commonrequest("POST",`${BACKEND_URL}/user/register`,data)
}
export const sentOtpFunction=async (data)=>{
return await commonrequest("POST",`${BACKEND_URL}/user/sendotp`,data)
}
This is my apicall.js:
import axios from 'axios';
export const commonrequest= async(methods,url,body,header)=>{
let config={
method: methods,
url,
headers:header?header:{
"Content-Type":"application/json"
},
data:body
}
//axios Instance
return axios(config).then((data)=>{
return data
}).catch((error)=>{
return error
})
}
This api.js from backend:
require("dotenv").config();
const express=require('express');
const app=express();
const cors=require('cors');
require("./db/conn");
const router=require("./routes/router");
const PORT=4002;
//middleware
app.use(express.json());
app.use(cors());
app.use(router);
app.listen(PORT,()=> console.log("Server connected!!"));
This is routes.js from backend:
const express=require("express");
const router=new express.Router();
const controllers=require("../controllers/userControllers");
// Routes
router.post("/user/register",controllers.userregister);
router.post("/user/sendotp",controllers.userOtpSend);
module.exports=router;
This is usercontrollers file from backend
const users=require("../models/userSchema")
const userotp=require("../models/userOtp");
const nodemailer=require("nodemailer");
//email config
const transporter=nodemailer.createTransport({
service:"gmail",
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth:{
user:process.env.EMAIL,
pass:process.env.PASSWORD
}
})
exports.userregister=async (req,res)=>{
const {fname,email,password}=req.body;
if(!fname||!email||!password){
res.status(400).json({error:"Please Enter All Input Data"});
}
try{
const preuser=await users.findOne({email:email});
if(preuser){
res.status(400).json({error:"This User name is already exist in Our database"})
}
else{
const userregister=new users({
fname,email,password
});
//here password hashing
const storeData=await userregister.save();
res.status(200).json(storeData);
}
}
catch(error){
res.status(400).json({error:"Invalid Details"})
}
}
//user send otp
exports.userOtpSend=async(req,res)=>{
const {email}=req.body;
if(!email){
res.status(400).json({error:"Please Enter Your Email"})
}
try{
const preuser=await users.findOne({email:email});
if(preuser){
const OTP=Math.floor(100000+Math.random()*900000);
const existEmail=await userotp.findOne({email:email});
if(existEmail){
const updateData=await userotp.findByIdAndUpdate({_id:existEmail._id},{
otp:OTP
},{new:true});
await updateData.save();
const mailOptions={
from:process.env.EMAIL,
to:email,
subject:"Sending Email For Otp Validation",
text:`OTP:- ${OTP}`
}
transporter.sendMail(mailOptions,(error,info)=>{
if(error){
console.log("error",error);
res.status(400).json({error:"email not send"});
}
else{
console.log("Email sent",info.response);
res.status()
}
})
}
else{
const saveOtpData=new userotp({
email,otp:OTP
});
await saveOtpData.save();
}
}
else{
res.status(400).json({error:"This User not exist in our Database"});
}
}
catch(error){
res.status(400).json({error:"Invalid Details",error})
}
}
Most probably the issue would be the lack of status code in the response after sending the email:
// backend ../controllers/userControllers
console.log("Email sent",info.response);
res.status()
...whereas you expect a 200 code to trigger page navigation:
// Front Login
if(response.status===200){
navigate("/user/otp");
}
That being said, just res.status() is strange in itself. You probably meant res.send() to properly terminate the handling, Express using 200 status code by default.
Furthermore, by default Axios treats status codes outside the 2xx range as an error (see validateStatus config option). You handle that error in your catch, but silently return the error object, which is not directly the response (but it still holds it in error.response).

Implementing a collaborative text editor using nodejs/react/socket but encountering problems because of slow heroku servers

I've tried making a collaborative editor using socket.io with reactjs frontend and node backend. Here's the piece of logic which I think is causing problems....
When a client starts typing on the editor, I've used onInput event to emit a socket response say "typing" which carries the complete text on the editor inside data object at the moment client presses a key. Now server catches this typing event and in response to that, emits another socket response called "typed" which contains the same data but the server sends this response to all the clients connected to the server.... Now all clients receive this event inside componentDidMount and then update the state variable "codeValue" which updates the editor content for all the clients.
There are two problems, first one that on one single typing event, server is emitting numerous typed events ( it happens only in heroku server and not on local host ) and the other problem is that heroku servers are slow and before the server sends response to update the state of clients, clients had already entered more text on the editor which simply vanishes when the state is updated.....
FRONTEND CODE:
import React from "react";
import { Dropdown } from "semantic-ui-react";
import languages from "../utils/languages";
//Styles
import "../styles/app.css";
//Editor
import * as ace from "ace-builds";
// import SocketIOClient from "socket.io-client";
import "ace-builds/src-noconflict/mode-c_cpp";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/ext-language_tools";
import AceEditor from "react-ace";
let check = true;
let ld;
// const endpoint = "http://localhost:4676";
// const socket = SocketIOClient(endpoint, { transports: ["websocket"] });
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
codeValue: languages[0].template,
currentLang: languages[0].key,
};
this.codeEditor = React.createRef();
this.fireTyping = this.fireTyping.bind(this);
this.onDDChange = this.onDDChange.bind(this);
this.runCode = this.runCode.bind(this);
this.handleOutput = this.handleOutput.bind(this);
}
componentDidMount() {
this.props.socket.on("typed", (data) => {
console.log(35, data.text)
this.setState({
codeValue: data.text,
});
check = true;
console.log(check)
});
this.props.socket.on('ans',(data) => {
console.log(data.output)
//handleOutput(data.output)
})
}
fireTyping = () => {
ld = this.codeEditor.current.editor.getValue()
//console.log(ld)
if(check) {
console.log(48, this.codeEditor.current.editor.getValue(), check);
this.props.socket.emit("typing", {
text: ld,
});
check = false;
}
console.log(check)
};
onDDChange = (e, data) => {
const selectedVal = languages.filter((v) => v.key == data.value)
this.setState({currentLang : data.value, codeValue: selectedVal[0].template})
}
runCode = () => {
this.props.socket.emit('run', {
code: this.codeEditor.current.editor.getValue(),
lang: this.state.currentLang,
input: ''
})
}
handleOutput = () => {
}
render() {
return (
<div>
<Dropdown
placeholder="Languages"
onChange = {this.onDDChange}
selection
value = {this.state.currentLang}
options={languages}
/>
<AceEditor
style={{
margin: "3rem auto",
width: "80vw",
height: "70vh",
}}
fontSize={18}
ref={this.codeEditor}
mode="c_cpp"
theme="github"
value={this.state.codeValue}
onInput={this.fireTyping}
showPrintMargin={false}
name="UNIQUE_ID_OF_DIV"
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
/>
<div className="container">
<button
onClick={this.runCode}
style={{
marginLeft: "40rem",
}}
className="large ui teal button"
>
Run
</button>
</div>
</div>
);
}
}
export default App;
BACKEND CODE:
const express = require("express");
const request = require("request");
const app = express();
const http = require("http");
const server = http.createServer(app);
const path = require('path')
const socket = require("socket.io");
const io = socket(server);
const port = process.env.PORT || 4676
app.use(express.static(path.join(__dirname, 'client/build')))
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'))
})
io.on("connection", (socket) => {
let previousCode, currentCode;
console.log(socket.id);
socket.on("typing", (data) => {
currentCode = data.text
console.log('typing')
console.log(previousCode === currentCode)
if(previousCode !== currentCode){
console.log(1)
io.emit("typed", data);
previousCode = currentCode;
currentCode = ''
}
});
});
server.listen(port, () => {
console.log("server started at http://localhost:4676");
});
I've spent hours trying to fix this but I couldn't.... Any help would be appreciated ☺️
Let me know if you need code reference, I'll share the repository

Use react to store binary data to SQL Server

I'm working on a feature that allows the user to upload a pdf to a SQL Server database. The user clicks a button to open a file dialog and selects the pdf they want to upload. The SQL Server database takes a varbinary(max).
I'm using base64 to encode the file, however I am getting an error saying
RequestError: Implicit conversion from data type varchar to varbinary(max) is not allowed. Use the CONVERT function to run this query
Looks like I'm sending a string, not binary.
Here is the code I'm using:
React.js
import React, { Component } from 'react';
import './styles.css';
import base64 from 'base-64';
class SaveSDS extends Component {
constructor() {
super();
this.state = { user: {} };
this.chemNameField = React.createRef(); // create ref for first name field
}
state = {
file: null
}
handleFile(e) {
e.preventDefault();
let file = e.target.files[0];
this.setState({file: file});
}
handleUpload(e){
e.preventDefault();
const data = new FormData();
let file = this.state.file;
data.append('file', file)
var r = new FileReader();
fetch('http://localhost:5000/db', {
method: 'POST',
headers: { "Content-Type": "application/json" },
mode: 'no-cors',
body: JSON.stringify({
chemName: this.chemNameField.current.value,
chemPDF: base64.encode(data)
})
})
.then((res) =>{
console.log(res)
});
}
render() {
return (
<div className="submitForm">
<div style={{padding: "10px"}}>
<input className="fileButton" type="file" accept=".pdf" name="file" onChange={(e) => this.handleFile(e)} />
</div>
<input type="text" style={{width: "50%"}} placeholder="Enter chemical name" ref={this.chemNameField} />
<br />
<button type="button" onClick={(e) => this.handleUpload(e)}>Upload</button>
</div>
);
}
}
export default SaveSDS;
And here is what I'm using on the express side:
Server.js
const express = require('express');
const bodyParser = require('body-parser');
var sql = require("mssql");
const app = express();
app.use(bodyParser.json());
app.use(express.json({
type: ['application/json', 'text/plain']
}));
var config = {
user: 'user',
password: 'pass',
server: 'localhost',
database: 'Master'
};
app.post('/db', function(req, res) {
console.log(req.body)
res.set('Access-Control-Allow-Origin', '*');
let connection = new sql.ConnectionPool(config, function(err) {
let request = new sql.Request(connection);
request.query("insert into chemicals (chemName, chemPDF) values ('" + req.body.chemName + "', '" + req.body.chemPDF + "')");
});
});
const port = process.env.PORT || 5000; //get the process port
app.listen(port, () => console.log(`Server running on port ${port}`));
Is it even possible to send binary to the backend from react, or would it be easier to convert to binary on the backend? Maybe my approach is totally wrong.
Basically I want the user to be able to upload a file, then allow other users to open and view that file. I've done this in C# by uploading binary to SQL Server, then decoding it so people can view it. Not sure if that translates to node/react.
Thanks!

Why formData does not work with multiple files?

I'm facing a problem with a React project I'm working on: I'm trying to upload multiple images to a Node Express API. I'm using a formData object and I used the append() method to append the form fields from the component State.
In the Express code I'm using multer, all the attributes in the req.body are there but req.files is empty.
I changed the code to upload a single image also using formData() and it works; the problem seems to be only when I try with multiple files using the formData object. I also tested using a regular form (not react) and that also worked!
I'm wondering if there is something I'm missing when I use formData with a file input with multiple files?
import React, { Component } from "react";
import axios from "axios";
class Form extends Component {
constructor() {
super();
this.state = { images: {} };
}
onChangeImages = e => {
this.setState({ images: e.target.files })
};
onSubmit = e => {
e.preventDefault();
const { images } = this.state;
const formData = new FormData();
formData.append("images", images);
axios
.post("/api/post/create", formData)
.then(res => console.log(res.data))
.catch(err => console.error(err));
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
onChange={this.onChangeImages}
type="file"
name="images"
multiple
accept="image/png, image/jpeg, image/jpg"
/>
<br />
<input type="submit" value="Send" />
</form>
);
}
}
export default Form;
Express code
const express = require('express');
const router = express.Router();
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
router.post('/create', upload.array('images', 2), (req, res) => {
console.log(req.files);
console.log(req.body);
res.status(200).json(req.body);
});
module.exports = router;
formData.append("images", images);
You need to append each file in turn. FormData doesn't support a FileList object.
for (let i = 0 ; i < images.length ; i++) {
formData.append("images", images[i]);
}
there are a lot of other alternatives than using the backend. You can use cloudinary or firebase to upload all images and get their urls and let your backend store those URLs in the database.

passing down props with React server side rendering

I am having trouble passing down initial props to my React component when rendered from Node (express) server side
Here's a short version of my component:
/* mycomponent.jsx */
import React, {PropTypes, Component} from 'react/addons'
import GoogleMap from 'google-map-react'
import Place from './place.jsx'
export default class EventsMapPage extends Component {
static defaultProps = {
center: [59.838043, 30.337157]
}
constructor (props) {
super(props)
}
render () {
const locations = this.props.locations
.map(place => {
const {id, ...coords} = place
return (
<Place
key={id}
{...coords}
text={id}
/>
)
})
return (
<div className='map-canvas'>
<GoogleMap
center={this.props.center}
>
{locations}
</GoogleMap>
</div>
)
}
}
And here's the server side
/* app.js */
require('babel/register')({ stage: 0 })
var ejs = require('ejs')
var express = require('express')
var venues = require('./venues')
var React = require('react/addons')
var MyComponent = require('./src/js/components/mycomponent.jsx')
var Component = React.createFactory(MyComponent)
var app = express()
app.get('/locations/:id', function (req, res, next) {
var loc = venues[req.params.id]
return res.render('location', {
react: React.renderToString(Component({locations: [loc]}))
})
})
app.listen(process.env.port || 2000)
It seems that locations prop is not being passed down to the component when trying to use it in render() method
Any idea ?
There is a typo where you add your locations in the "GoogleMaps" Component.
{locatios}

Resources