Generate PDF in NodeJS and open in React with react-pdf - node.js

I'm generating a PDF file using html-pdf-node in my NodeJS backend.
exports.generatePDF = async (req, res, next) => {
const data = req.body || req.body.data;
const content = data.html;
let options = {
format: 'letter',
margin: {
right: '40px',
left: '40px'
}
};
let file = { content };
html_to_pdf.generatePdf(file, options).then(pdfBuffer => {
res.setHeader('Content-Length', pdfBuffer.length);
res.setHeader('Content-Type', 'application/pdf');
return res.end(pdfBuffer);
});
}
Then, I'm trying to open the generated PDF in React using react-pdf.
import React, { useState, useEffect } from 'react';
import { Document } from 'react-pdf';
import * as helperAPI from '../../api/Helper'; // This is just a helper for the axios request to NodeJS
const PDFViewer = (props) => {
//const html = props.html;
const html = "<html><body><div><p>This is a Test</p></div></body></html>";
const [ pdfLink, setPDFLink ] = useState(null);
useEffect(() => {
async function fetchData() {
const res = await helperAPI.generatePDF({html});
const file = new Blob(
[res],
{type: 'application/pdf'}
);
const fileURL = URL.createObjectURL(file);
setPDFLink(fileURL);
}
fetchData();
}, [html]);
const onDocumentLoadSuccess = () => {
console.log('onLoad');
console.log(pdfLink);
}
console.log('Link: ' + pdfLink);
return (
<div>
<Document
file={{
url: pdfLink,
}}
onLoadSuccess={onDocumentLoadSuccess}
/>
</div>
);
}
export default PDFViewer;
The axios request inside the helperAPI:
import axios from 'axios';
import * as url from '../Constants/Custom';
export const generatePDF = async (data) => {
let output = "";
await axios.post(`${url.REQ_URL}/helper/generatePDF`, data)
.then((res) => {
output = res.data;
});
return output;
};
When I run the code, the browser returns: Failed to load PDF file.
How can I load a blob generated file from NodeJS in react-pdf?

Related

Cookies doesn't show up in my application ReactJS

Hello i'm trying to code auth for my app i'm using json web token the problem is when i send post request using postman i can see the cookie and access token in headers but in my application i can't see anything in my localstorage&cookies
Here is my code
authContext.js
import axios from "axios";
import { createContext, useEffect, useState } from "react";
export const AuthContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(
JSON.parse(localStorage.getItem("user")) || null
);
const login = async (inputs) => {
const res = await axios.post("http://localhost:8800/api/auth/login", inputs, {
withCredentials: true,
});
setCurrentUser(res.data)
};
useEffect(() => {
localStorage.setItem("user", JSON.stringify(currentUser));
console.log(currentUser);
}, [currentUser]);
return (
<AuthContext.Provider value={{ currentUser, login }}>
{children}
</AuthContext.Provider>
);
};
in login.jsx
const [inputs, setInputs] = useState({
username: "",
password: "",
});
const [err, setErr] = useState(null);
const navigate = useNavigate()
const handleChange = (e) => {
setInputs((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const login = useContext(AuthContext);
const handleLogin = async (e) => {
e.preventDefault();
try {
await login(inputs);
navigate("/")
} catch (err) {
setErr(err.response.data);
}
};
console.log(err);
console.log(inputs);
I'm trying to solve the problem because i'm trying to create a basic social app i need accessToken to display posts in my feed easily

Does axios need extra config to get data from REST API?

I am trying to convert the fetch API to axios with get method.
Prior to do this, I plan to keep using 'async, await'.
And when I replaced the code below:
// before
const fetchPlanets = async () => {
const res = await fetch("http://swapi.dev/api/planets/");
return res.json();
};
// after
const fetchPlanets = async () => {
const res = await axios
.get("http://swapi.dev/api/planets/")
.then((respond) => {
respond.data;
});
};
async can be used when to address the function.
and returned const res as res.json();
Also...axios does not require to res.json as it returned as json type.
That's how I understand this so far. And with fetch API, this work flawlessly.
How the code should be to let axios work as I expected?
// Planets.js
import React from "react";
import { useQuery } from "react-query";
import Planet from "./Planet";
// import axios from "axios";
const fetchPlanets = async () => {
const res = await fetch("http://swapi.dev/api/planets/");
return res.json();
};
const Planets = () => {
const { data, status } = useQuery("planets", fetchPlanets);
console.log(data);
return (
<div>
<h2>Planets</h2>
{status === "loading" && <div>Loading data...</div>}
{status === "error" && <div>Error fetching data!</div>}
{status === "success" && (
<div>
{data.results.map((planet) => (
<Planet key={planet.name} planet={planet} />
))}
</div>
)}
</div>
);
};
export default Planets;
And Planet.js; just in case.
import React from "react";
const Planet = ({ planet }) => {
return (
<div className="card">
<h3>{planet.name}</h3>
<p>Population - {planet.population}</p>
<p>Terrain - {planet.terrain}</p>
</div>
);
};
export default Planet;
There are 2 problems in your axios code.
You should return respond.data.
You should return the whole axios response.
So this would work:
const fetchPlanets = async () => {
return await axios
.get("http://swapi.dev/api/planets/")
.then((respond) => {
return respond.data;
});
};

How to receive Cloudinary image URL immediately upon upload (React JS and Node Js)?

I can successfully upload images to Cloudinary. But my question is how can I get the Cloudinary url of the successfully uploaded image sent back to me immediately upon upload?
I know it's sent back as part of const uploadedResponse = await cloudinary.uploader.upload(fileStr, {upload_preset: 'dev_setups'}), but this is on the backend (see code #2 below), I would like to receive the URL on the frontend (see code #1 below) so I can set it to React state. What is the best approach to accomplishing this?
Please let me know if you need more details.
Code #1: Below is my code to upload a picture to Cloudinary (Cloudinary specific code is commented below for reference as /* Cloudinary upload */)
import React, { useState } from 'react'
import { Card, Button, CardContent } from '#material-ui/core';
import { post, makePostAction } from '../actions';
import { useSelector, useDispatch } from 'react-redux';
export default function MakePost() {
const [title, setTitle] = useState("")
const dispatch = useDispatch();
const usernameHandle = useSelector(state => state.username)
const [fileInputState, setFileInputState] = useState('') /* new */
const [previewSource, setPreviewSource] = useState('') /* new */
const [selectedFile, setSelectedFile] = useState('') /* new */
const onInputChange = (event) => {
setTitle(event.target.value);
}
const handleSubmit = (evt) => {
evt.preventDefault();
if (!title) return
dispatch(makePostAction({
title,
comment: false,
comments_text: "",
handle: usernameHandle,
post_date: new Date().getTime()
}))
setTitle("")
}
/* Cloudinary upload */
const handleFileInputChange = (e) => {
const file = e.target.files[0]
previewFile(file)
}
const previewFile = (file) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => {
setPreviewSource(reader.result)
}
}
const handleSubmitFile = (e) => {
e.preventDefault();
if(!previewSource) return;
uploadImage(previewSource)
}
const uploadImage = async (base64EncodedImage) => {
console.log(base64EncodedImage)
try {
await fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({data: base64EncodedImage}),
headers: {'Content-type': 'application/json'}
})
} catch (error) {
console.error(error)
}
}
/* Cloudinary upload */
return (
<div>
<Card>
<CardContent>
<form onSubmit={handleSubmit}>
<input type="text" value={title} onChange={onInputChange} />
</form>
{/* new */}
<form onSubmit={handleSubmitFile} className="form">
<input type="file" name="image" onChange={handleFileInputChange} value={fileInputState} className="form-input" />
<button className="btn" type="submit">Submit</button>
</form>
{/* new */}
{previewSource && (
<img
src={previewSource}
alt="chosen"
style={{height: '300px'}}
/>
)}
</CardContent>
</Card>
</div>
)
}
Code #2: Here is my server.js
const express = require('express');
const app = express();
const {cloudinary} = require('./utils/cloudinary');
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({limit: '50mb', extended: true}))
app.get('/api/images', async (req, res) => {
const {resources} = await cloudinary.search.expression('folder:dev_setups')
.sort_by('public_id', 'desc')
.max_results(1)
.execute()
const publicIds = resources.map(file => file.secure_url)
res.send(publicIds)
})
app.post('/api/upload', async (req, res) => {
try {
const fileStr = req.body.data;
const uploadedResponse = await cloudinary.uploader.upload(fileStr, {upload_preset: 'dev_setups'})
res.json({msg: "Success"})
} catch (error){
console.error(error)
res.status(500).json({err: 'Something went wrong'})
}
})
const port = process.env.PORT || 3001
app.listen(port, () => {
console.log(`listening on port ${port}`)
});
The Cloudinary upload response object includes a secure_url attribute which you can send back to the front end. Looking at code #2, it seems that you're currently sending a "Success" msg (res.json({msg: "Success"})). Sounds like you want to change that line to -
res.json({url: uploadedResponse.secure_url})
In your front end (code #1), I'd consider switching from async/await to .then mechanism, as you don't want to the entire app to wait for the response -
const uploadImage = (base64EncodedImage) => {
console.log(base64EncodedImage);
fetch('/api/upload', {
method: 'POST',
body: JSON.stringify({data: base64EncodedImage}),
headers: {'Content-type': 'application/json'}
})
.then(doWhateverYouWant)
.catch((error) => console.error(error))
}
const doWhateverYouWant = async (res) => {
// you can use res.url
}

How to send data from react editor to server?

I am trying to create an editor to update my backend data but I am stuck at sending data from client to backend
Here is my following front-end code:
import React, { useState } from "react";
import dynamic from "next/dynamic";
import { convertToRaw, EditorState, getDefaultKeyBinding } from "draft-js";
import draftToHtml from "draftjs-to-html";
const Editor = dynamic(
() => import("react-draft-wysiwyg").then((mod) => mod.Editor),
{ ssr: false }
);
const Missions = ({ teamData, editable }) => {
const { title, mission, teamId } = teamData;
const classes = useStyle();
const [missionContent, setMissionContent] = useState(mission);
const [editing, setEditing] = useState(false);
const [editorState, updateEditorState] = useState(EditorState.createEmpty());
const onEditorStateChange = (editData) => {
updateEditorState(editData);
};
const handleSave = async () => {
const selection = editorState.getSelection();
const key = selection.getAnchorKey();
const content = editorState.getCurrentContent();
const block = content.getBlockForKey(key);
const type = block.getType();
if (type !== "unordered-list-item" && type !== "ordered-list-item") {
if (
editorState.getCurrentContent().getPlainText("").trim().length !== 0
) {
const content = editorState?.getCurrentContent();
let html = await draftToHtml(convertToRaw(content));
await updateEditorState(EditorState.createEmpty(""));
setMissionContent(html.trim());
}
}
setEditing(false);
};
return (
<div className="team-mission-editor-container">
<Editor
wrapperClassName={"mission-editor-wapper"}
toolbarClassName={"mission-editor-toolbar"}
editorClassName={"mission-editor-editor"}
editorState={editorState}
onEditorStateChange={onEditorStateChange}
toolbar={{...}}
/>
)
Here is my back-end router:
router.put(
"/team/:teamId",
restrictedRoute,
checkData,
catchErrors(checkTeamPermissions),
catchErrors(updateTeamData)
);
and here is my update function from backend:
exports.updateTeamData = async (req, res) => {
// Get userId
const userId = req.session.passport.user.id;
// Get teamId
const publicTeamId = req.params.teamId;
// Fetch private id for team
const teamId = await getTeamId(publicTeamId);
// The user making the request
const userPublicId = req.session.passport.user.publicId;
// The creator of the team
const creatorPublicId = req.body.creator;
// Check who is making the request
if (userPublicId !== creatorPublicId) {
res.status(401).json("msg: You cant update a team you did not create");
}
// Updates
const payload = {
title: req.body.title,
mission: req.body.mission,
inputs: req.body.inputs,
outputs: req.body.outputs,
duration_in_months: req.body.duration_in_months,
status: req.body.status,
mergedTo: teamId,
};
// Update team data
await models.Team.update(payload, {
where: {
id: teamId,
creatorId: userId,
},
});
res.status(200).json("msg: Updated team successfully");
};
How can I send data fromo my editor to backend and update it?
Thank you so much for helping me

File upload from React Native ( expo ) to Node ( multer )

How can I upload a file( pdf, docs etc) from React Native using expo to the server using node. I've seen many examples for images using the expo image-picker api but I've come across none that uses document-picker or filesystem apis from expo. The expo file system documentation was a little hard to interpret for a beginner like me.
Thanks for the help. I was able to come up with a solution and I'll post it below so it can be of some use to whoever comes here in the future.
React Native
import React, { useState } from 'react';
import { Button, View } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import * as FileSystem from 'expo-file-system';
const DocPicker = () => {
const [ doc, setDoc ] = useState();
const pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({ type: "*/*", copyToCacheDirectory: true }).then(response => {
if (response.type == 'success') {
let { name, size, uri } = response;
let nameParts = name.split('.');
let fileType = nameParts[nameParts.length - 1];
var fileToUpload = {
name: name,
size: size,
uri: uri,
type: "application/" + fileType
};
console.log(fileToUpload, '...............file')
setDoc(fileToUpload);
}
});
// console.log(result);
console.log("Doc: " + doc.uri);
}
const postDocument = () => {
const url = "http://192.168.10.107:8000/upload";
const fileUri = doc.uri;
const formData = new FormData();
formData.append('document', doc);
const options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
console.log(formData);
fetch(url, options).catch((error) => console.log(error));
}
return (
<View>
<Button title="Select Document" onPress={pickDocument} />
<Button title="Upload" onPress={postDocument} />
</View>
)
};
export default DocPicker;
Node.js
const express = require('express')
const bodyParser = require('body-parser')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
const app = express()
const fs = require('fs')
const http = require('http')
const port = 8000
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req,res) => {
res.json({
success: true
})
})
app.post('/', (req, res) => {
console.log(req.body)
res.status(200)
})
app.post('/upload', upload.single('document'),(req , res) => {
console.log(req.file, req.body)
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Cheers!!!
If the solution given by #Anandhu doesn't work then try the above code like this.
import React, { useState } from 'react';
import { Button, View } from 'react-native';
import * as DocumentPicker from 'expo-document-picker';
import * as FileSystem from 'expo-file-system';
const DocPicker = () => {
const [ doc, setDoc ] = useState();
const pickDocument = async () => {
let result = await DocumentPicker.getDocumentAsync({
type: "*/*",
copyToCacheDirectory: true })
.then(response => {
if (response.type == 'success') {
let { name, size, uri } = response;
/ ------------------------/
if (Platform.OS === "android" && uri[0] === "/") {
uri = `file://${uri}`;
uri = uri.replace(/%/g, "%25");
}
/ ------------------------/
let nameParts = name.split('.');
let fileType = nameParts[nameParts.length - 1];
var fileToUpload = {
name: name,
size: size,
uri: uri,
type: "application/" + fileType
};
console.log(fileToUpload, '...............file')
setDoc(fileToUpload);
}
});
// console.log(result);
console.log("Doc: " + doc.uri);
}
const postDocument = () => {
const url = "http://192.168.10.107:8000/upload";
const fileUri = doc.uri;
const formData = new FormData();
formData.append('document', doc);
const options = {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
},
};
console.log(formData);
fetch(url, options).catch((error) => console.log(error));
}
return (
<View>
<Button title="Select Document" onPress={pickDocument} />
<Button title="Upload" onPress={postDocument} />
</View>
)
};
export default DocPicker;
There is a bug in the way the path was encoded, and the file:// scheme is missing.
This bug may be fixed in next release.
Try this Uploading pictures,documents and videos from your phone in your app with React Native, Expo
Here is an example, which also uses multer and express on the backend: https://github.com/expo/examples/tree/master/with-formdata-image-upload
That said, I'd recommend using FileSystem.uploadAsync instead of fetch and the background sessionType in order to support uploads while the app is backgrounded on iOS.

Resources