How to implement react-pdf with print function - reactjs

I would like to use react-pdf to display the PDF and develop a printing function for direct printing (like using window.print());
The REST server is developed using Jersey.
The PDF will generate from server and return to React client using Jersey with return type is application/pdf. React client will display the PDF using react-pdf.
I don't want to declare the URL path in "file" because this will retrieve the PDF from server again if the React state changed and triggered re-render. Also, I need to develop a print function to print the PDF displayed (Because the PDF content may change if retrieve the PDF again from server)
Below show my code:
Server:
#Override
#GET
#Path("/pdf")
#Produces(MediaType.APPLICATION_PDF_VALUE)
public Response testPdf() throws Exception {
File file = new File("C:\\Desktop\\test.pdf");
FileInputStream fileInputStream = new FileInputStream(file);
ResponseBuilder response = Response.ok((Object) fileInputStream);
response.type("application/pdf");
response.header("Content-Disposition", "filename=test.pdf");
return response.build();
}
Client
import React, { Component } from 'react';
import { Document, Page } from 'react-pdf';
import axios from 'axios';
class MyApp extends Component {
state = {
numPages: null,
pageNumber: 1,
pdfContent: null
}
componentDidMount(){
var that = this;
axios.get("url\Pdf").then((response) => {
that.setState({pdfContent:response.data});
}).catch((error) => {
console.warn(error);
});
}
onDocumentLoadSuccess = ({ numPages }) => {
this.setState({ numPages });
}
printHandler(){
window.print();
}
render() {
const { pageNumber, numPages } = this.state;
return (
<div>
<Document
file={this.state.pdfContent}
onLoadSuccess={this.onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
<p>Page {pageNumber} of {numPages}</p>
<button onClick={() => this.setState(prevState => ({
pageNumber: prevState.pageNumber + 1 }))}>Next page</button>
<button onClick={() => this.setState(prevState => ({
pageNumber: prevState.pageNumber - 1 }))}>Prev Page</button>
<button onClick={this.printHandler}/>
</div>
);
}
}
I want to get the PDF only one time and display the PDF using react-pdf. Also, I want to print the displayed PDF.
I tried to convert the response.data to base64 followed this line because not success: (this will lose the pdf content)
Encode PDF to base64 in ReactJS
Code like:
componentDidMount(){
var that = this;
axios.get("url\Pdf").then((response) => {
let reader = new FileReader();
var file = new Blob([response.data], { type: 'application/pdf' });
reader.onloadend = () => {
that.setState({
base64Pdf:reader.result
});
}
reader.readAsDataURL(file);
}).catch((error) => {
console.warn(error);
});
}
Anyone can give me some suggestion?
Or any better way to reach my goal?
Thanks

Update on receiving the error message from the back-end:
When a request fails, we receive a JSON object from the back-end which contains the error message. The problem is that when we are forcing to receive the response in Blob format: responseType: 'blob' - no matter if the request fails or not, we receive a Blob Object. So, I was thinking about changing the responseType in the function provided from axios: transformResponse, but unfortunately, we do not have access to 'responseType' object, only to the headers. Here: https://github.com/axios/axios/pull/1155 there is an open issue about converting accordingly to responseType, it is still not resolved.
So, my way of resolving this problem is using fetch instead of axios.
Here is an example:
fetch('here.is.your/endpoint', {
method: 'POST', // specifying the method request
body: JSON.stringify(request), // specifying the body
headers: {
"Content-Type": "application/json"
}
}
).then((response) => {
if (response.ok) { // checks if the response is with status 200 (successful)
return response.blob().then(blob => {
const name = 'Report.pdf';
saveAs(blob, name);
});
} else {
return response.json().then((jsonError) => {
this.setState({
modalMessage: jsonError.message // access the error message returned from the back-end
});
});
}
}).catch(function (error) {
this.setState({
modalMessage: "Error in data type received." // general handler
});
});
I hope this helps!

Recently I got a similar use case with the pdf part, my request is Post, but you can make it Get with no problem. So, what is happening:
1) - I am using axios for making a request to the back-end:
2) - request is the object that I am sending, but you will not have such, since you will probably send only id, for example: axios.get('here.is.your/endpoint/id');
3) - I am using: file-saver for saving the file I receive.
The rest of the code should be self-explaining and I also added some comments.
import {saveAs} from "file-saver";
...
axios.post('here.is.your/endpoint', qs.parse(request), {
headers: {
'Content-Type': 'application/json'
},
responseType: 'blob' // here I am forcing to receive data in a Blob Format
})
.then(response => {
if (response.data) {
//Create a Blob from the PDF Stream
const file = new Blob(
[response.data],
{type: 'application/pdf'});
const name = 'Report.pdf';
saveAs(file, name);
} else {
throw new Error("Error in data type received.");
}
})
.catch(error => {
this.setState({
modalMessage: "Here Add Custom Message"
});
});
I cannot get the error message from the back-end still, I will text back if I get some progress on it - for now, I show a custom message.
I hope that helps!
Wish you luck!

I am glad I helped!
I have one more UPDATE FOR RECEIVING THE ERROR MESSAGE
THIS ONE IS VALID ONLY IF YOU RECEIVE TEXT MESSAGE NOT JSON
fetch('here.is.your/endpoint', {
method: 'POST', // specifying the method request
body: JSON.stringify(request), // specifying the body
headers: {
"Content-Type": "application/json"
}
}
).then((response) => {
if (response.ok) { // checks if the response is with status 200 (successful)
return response.blob().then(blob => {
const name = 'Report.pdf';
saveAs(blob, name);
});
} else {
return response.text().then(function (error) {
throw new Error(error); // we should throw an Error with the received error
}
);
}
}).catch(function (error) {
this.setState({
modalMessage: error.message // that way we access the error message
});
});
We are using response.text().then() because of that way we manage to convert it from Promise to text. And it is important to use .then() because the Promise at that moment is resolved and we receive the Promise value. Then we simply throw an Error because we do not have access to the state object.
That is how you get a text from a response.

Related

How to perform action or render component before/after every http request in React?

I want to know if there is a way to create a kind of middleware in React?
What i want is to have an alert component show if there is a failing result for an http request.
Right now, i am making http request on login,registration,etc and i am importing my alert component in every page and setting the Alert component props like type, message, visibility everywhere i need the component, but i think maybe there is a better way of doing this.
Here is my code:
...imports
export const RegisterPage = () => {
const [alertConfig, setAlertConfig] = useState({
type: "",
message: "",
show: false,
});
...code
const onSubmitHandler = async (e) => {
e.preventDefault();
if (!isFormValid()) return;
const formData = new FormData();
formData.append("password", formValues.password);
if (formValues.provider.startsWith("8")) {
formData.append("contact", formValues.provider);
} else {
formData.append("email", formValues.provider);
}
setIsLoading(true);
try {
const response = await fetch(
`${process.env.REACT_APP_API_URL}/auth/register`,
{
method: "POST",
body: formData,
}
);
const data = await response.json();
if (data.status === "success") {
const { token, user } = data.data;
dispatch(setCurrentUser(user, token));
navigate("/choose-actor");
} else {
setAlertConfig({
type: "warning",
message: data.message,
show: true,
});
}
} catch (error) {
console.log(error);
setAlertConfig({
type: "danger",
message: "Ocorreu algum erro",
show: true,
});
} finally {
setIsLoading(false);
}
};
return
...html
{alertConfig.show && <Alert {...alertConfig} />}
...more html
As you can see, i am changing the configuration for the alert inside inside the function that executes the http request, and i have to do the save for every page that performs this action.
I looking for a design patter where i dont have to repeat myself.
Hope my question is clear.

How to fetch and display image in ReactJS, that is returned from web API

How to fetch and display image in reactJS, that is returned from asp.net core web API.
ReactJS:
I can see the image is returned from API in the dev console.
this.state = {
text: "Hello", // the text for which code need to be generated
result: '',
};
}
Below code has no problem until 'response.ok' line. But does not get past 'response.json'.
async componentDidMount() {
await GenQRCode(this.state.text).then(
(response) => {
if (response.ok) {
response.json() // removing response.json and setting response.url below solved the issue
.then((response) => {
this.setState({ result: response });
});
}
})
}
I could display QRCode in my component, if I manually copy the URL of QRCode from preview section of dev tool and set the state of 'result' with that URL.
<p><img src={this.state.result} height = "200" width = "200" /></p>
function setImage(responseAsBlob) {
const imgUrl = URL.createObjectURL(responseAsBlob);
return imgUrl;
}
const response = await fetchAPI('YOUR URL');
if (response) {
this.setState({result:setImage(await response.blob())});
}

using FormData() in react keeps returning null

I am building a React app and in it there's a part where i should set up a profile picture, but every time i call the api i keep getting an error that i haven't filled one of the fields for the post api to return a success response.
this is my jsx
<form>
<input type="file" name="file" onChange={(e) => this.handleFile(e)}></input>
<button type="button" onClick={(e) => this.handleUpload(e)} >send</button>
</form>
and here is the code i'm using to handle those
state = {
file: null
};
handleFile(e) {
console.log(e.target.files, "ssss");
console.log(e.target.files[0], "ssss");
let file = e.target.files
this.setState({ file: e })
}
handleUpload(e) {
let file = this.state.file
let fromdata = new FormData();
fromdata.append('image', file);
fromdata.append('name', "filedata");
const headers = {
Authorization': localStorage.getItem('api_key'),
}
const user = {
filedata: fromdata,
type: 1,
};
axios.post(`sth sth`, user, {
headers: headers
})
.then(res => {
console.log(res);
console.log(res.data);
})
}
so basically the server requires type and filedata but every time i send an api request it keeps returning me
filedata: ["The filedata field is required."]
and i can't seem to find where the problem is.
Work on your handleFile function properly and add a Content-Type/Accept header to your api request, your final code should look something like this. on your api console.log (req.file) you should see your file now reaches server successfully, or if you can provide a bit of your server code, I can be able to help further.
import React from 'react';
import axios from 'axios';
export class Test extends React.Component {
constructor(props){
super(props);
this.state = {
file : null
}
this.handleFile = this.handleFile.bind(this)
this.handleUpload = this.handleUpload.bind(this)
}
handleFile(e) {
let file = e.target.files[0]
this.setState({ file })
}
handleUpload (e) {
e.preventDefault();
let file = this.state.file
let fromdata = new FormData();
fromdata.append('image', file);
const headers = {
'Authorization': localStorage.getItem('api_key'),
'Content-Type' : 'multipart/form-data'
}
const user = {
filedata: fromdata,
type: 1,
};
axios.post(`sth sth`, user, {
headers
})
.then(res => {
console.log(res);
console.log(res.data);
})
}
render() {
return (
<form onSubmit={this.handleUpload}>
<label>File</label><br/>
<input type='file' onChange={this.handleFile}/>
<button>Send File!</button>
</form>
)
}
}
Assuming that you want to upload a single file, The problem I see is in setting the file to state. You are putting the entire response into the state in this statement: this.setState({ file: e }) change it to this.setState({ file: e.target.files[0] }).
Basically you are taking in more than just the file, you are taking in the entire response and other data that comes in when a user uploads a file using the choose file input button and because of that things can't be read well.
My practice: I would put the file into a global var instead of the state.
Usually in cases like this, its the function that doesn't complete but the main thread keeps on running, thats why variables state null or older value.
var file, fromdata;
handleFile(e) {
file = e.target.files[0];
}
handleUpload(e) {
fromdata = new FormData(); //make sure this statement completes before moving any further
fromdata.append('image', file);
fromdata.append('name', "filedata");
const headers = {
Authorization': localStorage.getItem('api_key'),
}
user = {
filedata: fromdata,
type: 1,
};
axios.post(`sth sth`, user, {
headers: headers
})
.then(res => {
console.log(res);
console.log(res.data);
})
}

Redux testing multipart/form-data with jest, nock, axios and jsdom

I'm using jest+nock+jsdom modules to test my React\Redux application.
I need to test this async action function:
export function updateUserPhoto (file, token) {
const data = new FormData()
data.append('file', file)
return dispatch => {
dispatch(userPutPhotoRequest())
return axios({
method: 'PUT',
headers: {
'x-access-token': token
},
data: data,
url: API_URL + '/user/photo'
})
.then(res => dispatch(userPutPhotoSuccess(res.data)))
.catch(err => dispatch(userPutPhotoFilure(err)))
}
}
So i'm using jsdom to provide FormData and File objects into tests:
const {JSDOM} = require('jsdom')
const jsdom = (new JSDOM(''))
global.window = jsdom.window
global.document = jsdom.window.document
global.FormData = jsdom.window.FormData
const File = jsdom.window.File
global.File = jsdom.window.File
And this is the method to test "upload photo" function:
it('creates USER_UPDATE_SUCCESS when updating user photo has been done', () => {
const store = mockStore(Map())
const file = new File([''], 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
const expectedFormData = new FormData()
expectedFormData.append('file', file)
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', expectedFormData)
.reply(200, {body: {}})
const expectedActions = [
{
type: ActionTypes.USER_PUT_PHOTO_REQUEST
},
{
type: ActionTypes.USER_PUT_PHOTO_SUCCESS,
response: {
body: {}
}
}
]
return store.dispatch(actions.updateUserPhoto(file, token))
.then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
Where i'm using nock to mock axios requests, redux-mock-store to mock Redux store.
Creating File and FormData objects to compare it with response from axios.
And then i'm calling action function passing file and token as parameters.
In production action function works and dispatch action success fine. But in testing i'm receiving error:
Error: Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream
When i pass into axios empty object as data test passes, so problem in FormData object.
How can i mock FormData object for axios in an appropriate way to make this test work ?
This answer is coming way too late, but I was looking to do something similar and I wanted to post a solution here that someone else might stumble across and find useful.
The main problem here is that nock mocks network requests and not Javascript libraries. FormData is a Javascript object that eventually gets transformed to text when making network requests. By the time the FormData object makes it to nock, its been converted to a string or a Buffer, hence the error you see. nock is unable to use the FormData object for comparison.
You have a few options:
1. Easiest solution
Just don't match against the data in the PUT request. The reason you are mocking is because you don't want a real HTTP request to go out but you want a fake response back. nock only mocks the request once, so if you mock all PUT requests to /user/photo nock will catch it but only for that test:
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo')
.reply(200, {body: {}})
Before you implement the test this way, think about what your test is trying to verify. Are you trying to verify that the file is sent in the HTTP request? If yes, then this is a poor option. Your code could send a completely different file than the one dispatched and still pass this test. If however you have another test to verify the file is being put in the HTTP request properly then this solution might save you some time.
2. Easy solution for getting nock to fail on not matching the request
If you do want the test to fail if your code passed a corrupted or wrong file, then he simplest solution would be to test for the filename. Since your file is empty there is no need to match the content, but we can match on the filename:
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', /Content-Disposition\s*:\s*form-data\s*;\s*name="file"\s*;\s*filename="filename.txt"/i)
.reply(200, {body: {}})
This should match the simple case where you have one file uploading.
3. Matching the content of form data fields
Say you have additional fields to be added to your request
export function updateUserPhoto (file, tags, token) {
const data = new FormData()
data.append('file', file)
data.append('tags', tags)
...
OR you have fake content in the file that you want to match on
const file = new File(Array.from('file contents'), 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
This is where things get a bit complex. Essentially what you need to do is to parse the form data text back into an object and then write your own matching logic.
parse-multipart-data is a fairly simple parser that you could use:
https://www.npmjs.com/package/parse-multipart-data
Using that package your test might look something like this
it('creates USER_UPDATE_SUCCESS when updating user photo has been done', () => {
const store = mockStore(Map())
const file = new File(Array.from('file content'), 'filename.txt', {
type: 'text/plain',
lastModified: new Date()
})
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
}).put('/user/photo', function (body) { /* You cannot use a fat-arrow function since we need to access the request headers */
// Multipart Data has a 'boundary' that works as a delimiter.
// You need to extract that
const boundary = this.headers['content-disposition']
.match(/boundary="([^"]+)"/)[1];
const parts = multipart.Parse(Buffer.from(body),boundary);
// return true to indicate a match
return parts[0].filename === 'filename.txt'
&& parts[0].type === 'text/plain'
&& parts[0].data.toString('utf8') === 'file contents'
&& parts[1].name === 'tags[]'
&& parts[1].data.toString('utf8') === 'tag1'
&& parts[2].name === 'tags[]'
&& parts[2].data.toString('utf8') === 'tag2';
})
.reply(200, {body: {}})
const expectedActions = [
{
type: ActionTypes.USER_PUT_PHOTO_REQUEST
},
{
type: ActionTypes.USER_PUT_PHOTO_SUCCESS,
response: {
body: {}
}
}
]
return store.dispatch(actions.updateUserPhoto(file, ['tag1', 'tag2'], token))
.then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions)
})
})
I was dealing with the same issue, the problem was that axios was setting http as the default adapter. And xhr is the one you need.
// axios/lib/defaults.js
function getDefaultAdapter() {
var adapter;
// Only Node.JS has a process variable that is of [[Class]] process
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
}
return adapter;
}
So, setting the xhr adapter explicitly on the axios calls worked for me.
Something like:
export function updateUserPhoto (file, token) {
const data = new FormData()
data.append('file', file)
return dispatch => {
dispatch(userPutPhotoRequest())
return axios({
method: 'PUT',
headers: {
'x-access-token': token
},
adapter: require('axios/lib/adapters/xhr'),
data: data,
url: API_URL + '/user/photo'
})
.then(res => dispatch(userPutPhotoSuccess(res.data)))
.catch(err => dispatch(userPutPhotoFilure(err)))
}
}
Also, I had issues with nock and CORS, so, if you have same issue, you can add access-control-allow-origin header
nock(API_URL, {
reqheaders: {
'x-access-token': token
}
})
.defaultReplyHeaders({ 'access-control-allow-origin': '*' })
.put('/user/photo', expectedFormData)
.reply(200, {body: {}})

Testing fetch using Jest- React Native

I have a common api class that i use for handling api calls in React Native. It will make the call and get the json/ error and return it. See the code below.
// General api to acces data from web
import ApiConstants from './ApiConstants';
export default function api(path,params,method, sssid){
let options;
options = Object.assign({headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}},{ method: method }, params ? { body: JSON.stringify(params) } : null );
return fetch(ApiConstants.BASE_URL+path, options).then( resp => {
let json = resp.json();
if (resp.ok) {
return json;
}
return json.then(err => {
throw err;
}).then( json => json );
});
}
But when i write the jest test to mock the api as folllows in tests folder.
test('Should login',() => {
global.fetch = jest.fn(() => new Promise((resolve) => {
resolve( { status: 201, json: () => (mock_data_login) });
}));
return Api(ApiConstants.LOGIN,{'un':'test1','pwd':'1234'},'post', null).then((data1)=>{
expect(data1).toBeDefined();
expect(data1.success).toEqual(true);
expect(data1.message).toEqual('Login Success');
});
});
it fails with:
TypeError: json.then is not a function
When I change the fetch return to this, the test passes:
return fetch(ApiConstants.BASE_URL+path, options).then( resp => {
let json = resp.json();
return json
});
}
Why is this type error error popping up? I can't change the API module, because that will my redux saga code to change. What should I do?
In your code, json is just an Object and not a Promise, so then is undefined. That's the complain you are getting because you are trying to use undefined as a function. The problem is not in the test but in your code that ha san error. Try the following instead.
return fetch(ApiConstants.BASE_URL+path, options)
.then(resp => resp.json())
.then( json => json)
.catch((error) => error);
});
Edit: oh, just read you can't make changes to the component where the error occurs?
Try converting your fetch like this:
return fetch(ApiConstants.BASE_URL+path, options)
.then(resp => {
let json = resp.json();
if (resp.ok) {
return json;
} else {
throw Error(resp.error) // assuming you have some kind of error from endpoint?
}
})
.then(/*handle your ok response*/)
.catch(/*handle your error response*/);
I faced the same issue, The problem is that you are mocking only response.json as function but it should be a Promise, Like this,
global.fetch = jest.fn(() => new Promise((resolve) => {
resolve( { status: 201, json: () => {
return Promise.resolve(mock_data_login);
}
});
}));
This will return a Promise for you json function.
Hope this fix your problem.

Resources