react-pdf: use PDFDownloadLink asynchronously without blocking the rest of the application - reactjs

I'm using PDFDownloadLink from the react-pdf package to generate a PDF on the fly in my application and allow the user to download a report based on data being passed to the component that generates the PDF document. However, there are more than 400 pages that need to be rendered in this PDF, and this operation blocks the main thread for a few seconds. Is there any way to make this operation asynchronous, so the rest of the application will continue to function while the PDF is being generated? Also I would like to be able to cache the results, since the data being passed to the component can come from about 8 different arrays of data, which don't change very much, so switching between these arrays I would rather not to have to render the PDF all over again if the PDF for that given array has already been generated once before... I'm guessing the blob data needs to be stored somewhere, perhaps localStorage?
import { Page, Text, View, Document, StyleSheet, PDFDownloadLink } from '#react-pdf/renderer'
const App = () => {
const condition = "firstCondition";
const filteredRowData = rowData.filter(a => a.condition = condition);
return (
<PDFDownloadLink
document={<PDF_REPORT_Document rowData={filteredRowData} />}
fileName={"PDF_REPORT.pdf"}
style={{color:'white'}}
>{({ blob, url, loading, error }) =>
loading ? "Report loading..." : "Report ready to download"
}</PDFDownloadLink>
);
}
const PDF_REPORT_Document = (props) => {
const { rowData } = props;
const styles = StyleSheet.create({
page: {
flexDirection: 'column',
backgroundColor: '#E4E4E4'
},
section: {
margin: 10,
padding: 10,
flexGrow: 1
}
});
return(
<Document>
{rowData.map((row,index) =>
<Page size="A4" style={styles.page} key={index}>
<View style={styles.section}>
<Text>Name: {row.FULLNAME}</Text>
</View>
</Page>
)}
</Document>
);
}

I finally found the answer to this in an issue on github which addresses this exact problem:
Is your feature request related to a problem? Please describe.
It is an improvement. At the moment, if you use 'PDFDownloadLink' the PDF is being generated as the component loads.
Describe the solution you'd like
It is not mandatory, but having multiple heavy PDFs ready to be downloaded wouldn't be the best approach since not every user will need it.
Describe alternatives you've considered
I've used pdf() function to generate the blob and file-saver lib to download it:
import { saveAs } from 'file-saver';
import { pdf } from '#react-pdf/renderer';
import PdfDocument from '../PdfDocument';
const generatePdfDocument = async (documentData,fileName) => {
const blob = await pdf((
<PdfDocument
title='My PDF'
pdfDocumentData={documentData}
/>
)).toBlob();
saveAs(blob, fileName);
};
export default generatePdfDocument;

Related

TypeError with .map and The Movie Database

I am learning programming since a few months and I am starting to practice with some projects that I could create by myself or that I could find on the internet to study and learn more about different topics. In this case, I am working with React.js, Material UI and The Movie Database trying to make some kind of website related to movies and series. I was doing fine, until I needed to display all the genres and filter them on the page.
My intention is to display all the genres from The Movie Database and filter them when I needed. In some cases this code works fine and without any issue but in general it says that "genres.map is not a function" and the component doesn't render.
I know that this type of error normally happens when I try to map something that it is not an array. However, in this case genres IS an array. The most annoying thing for me is that in some rare cases the code works fine.
As I said before, I am new relatively new to programming and it is my first question here so I apologize if this is not the place to ask this. I would be very grateful if someone could help me to solve this and most important to understand what is the problem. Many thanks in advance!
import { Chip } from "#mui/material";
import axios from "axios";
import React, { useEffect } from "react";
const Genres = ({
selectedGenres,
setSelectedGenres,
genres,
setGenres,
type,
setPage,
}) => {
const addGenre = (genre) => {
setSelectedGenres([...selectedGenres, genre]);
setGenres(genres.filter((g) => g.id !== genre.id));
setPage(1);
};
const removeGenre = (genre) => {
setSelectedGenres(
selectedGenres.filter((selected) => selected.id !== genre.id)
);
setGenres([...genres, genre]);
setPage(1);
};
const fetchGenres = async () => {
const { data } = await axios.get(
`https://api.themoviedb.org/3/genre/${type}/list?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`
);
setGenres(data.genres);
};
useEffect(() => {
fetchGenres();
return () => {
setGenres({});
};
// eslint-disable-next-line
}, []);
console.log(genres);
return (
<div style={{ padding: "6px 0" }}>
{selectedGenres.map((genre) => (
<Chip
label={genre.name}
style={{ margin: 2 }}
size="small"
key={genre.id}
clickable
onDelete={() => removeGenre(genre)}
/>
))}
{genres.map((genre) => (
<Chip
label={genre.name}
style={{ margin: 2 }}
size="small"
key={genre.id}
clickable
onClick={() => addGenre(genre)}
/>
))}
</div>
);
};
export default Genres;
genres must either consistently be an array, or your code must handle cases that are not an array. In your case the problem is here:
return () => {
setGenres({});
};
This will cause the state to be set to an object every time this component unmounts. Objects are not an array, and so do not have the functions of an array. When the component mounts again, it will be an object still for a brief period, before it fetches from the API and it gets set back to an array again.
Instead, use:
return () => {
setGenres([]);
};
Additionally, ensure that the default state where genres is initially declared (probably in the parent, or some ancestor) is set to an array otherwise it could be some other type before they are fetched from the API:
const [genres, setGenres] = useState([])
Off-topic but I'd also question if it's necessary to set it back to an empty array on unmount in the first place. Doing nothing would mean the old data is still there when this component remounts and the user would not need to wait for it to fetch again. The new data would replace once it arrives from the API. You could always implement a loading state to tell the user it is refreshing whilst not blocking them from seeing the "stale" data.

How to export a component to a PDF file, that is not visible on UI, but has to be in PDF document (html-to-image, jsPDF, React)

Like the title says, I want to export a component to a PDF file, that I want to be invisible in the app or should I say on UI, but I want it to be inside a PDF document.
To make this PDF exporting functionality I have used the combination of html-to-image library, jsPDF library and everything is made using React.
This is my code:
function App() {
const [exporting, setExporting] = useState(false);
async function createPdf({ doc, element }) {
const imgData = await toPng(element);
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
doc.addImage(imgData, "PNG", 10, 0, pdfWidth, pdfHeight, "", "FAST");
}
async function handleDownloadPdf() {
const element = document.getElementsByClassName("container")[0];
const doc = new jsPDF(
"p",
"px",
[element.clientWidth, element.clientHeight],
true
);
setExporting(true);
await createPdf({ doc, element });
doc.save(`charts.pdf`);
}
return (
<pdfContext.Provider value={{ exporting, setExporting }}>
<div className="App">
<button onClick={handleDownloadPdf}>Test</button>
<div className="container">
<Hidden />
<Foo />
</div>
</div>
</pdfContext.Provider>
);
}
export default App;
The component that I want to be hidden is <Hidden />, this is a simple component but let me show the code anyways:
const Hidden = () => {
const { exporting, setExporting } = useContext(pdfContext);
return (
<div
className="elementOne"
style={{ visibility: exporting ? "visible" : "hidden" }}
>
</div>
);
};
export default Hidden;
As you can see I want to use the context called pdfContext that sets the visibility of a component to hidden when the component is not being exported, and to visible when it's being exported, but this way is not really a good solution, as the component gets visible for a split second before exporting and in my opinion it's not a good design.
So if anyone has any solution or a workaround on how to export a component to a PDF using these libraries, but without showing it on a UI, that would be great.
I know that the way these components are being exported to a PDF is by converting the container to an image, and probably the way I am asking to do this is maybe impossible but then again it does not hurt to ask.

How to find the number of views of the react native page?

I'm making an app. I want to find the number of views of a forum application and posts shared in this application and send them to the firebase database? What path should I follow?
There are 2 ways to count post views.
Inside Post Feed List
When post posts are rendered inside scrollable components like ScrollView or FlatList, Use components like - https://www.npmjs.com/package/#skele/components .
This is a higher-order component that tracks if the child component is inside viewport then you can use this information to update the post view counts.
import { Viewport } from "#skele/components";
import { FlatList, View, Text } from "react-native";
const ViewportAwareView = Viewport.Aware(View);
function HomeScreen() {
const onViewportEnter = async () => {
// Logic to increase view counts here
};
const onViewportLeave = async () => {};
const renderItem = ({ item }) => {
return (
<ViewportAwareView
onViewportEnter={onViewportEnter}
onViewportLeave={onViewportLeave}
retainOnceInViewport={false}
>
<Text> .... </Text>
</ViewportAwareView>
);
};
return (
<Viewport.Tracker>
<FlatList scrollEventThrottle={16} renderItem={renderItem} />
</Viewport.Tracker>
);
}
Count View inside Post Detail Screen
When users visit post screen, increase post view count when screen mounted.
function postDetailsScreen() {
useEffect(() => {
// Logic to increase post view counts
}, []);
return <View> Post details here... </View>;
}

Gatsby GraphQL Variables In Component

I'm an iOS developer and I've been struggling for what seems like the longest time making my portfolio site from scratch. I've tried a bunch of different technologies and have finally settled on using Gatsby to create it.
So far things have been fairly straightforward but I can not figure out for the life of me how to get a component that looks like the picture below. I've gotten most of the layout design working, but I can't seem to use graphql to query the images I need in the component.
Desired Layout
I've found plenty of Gatsby example templates such as this one and this one that are similar. However the main difference is that each of these only have one image and they seem to be using Gatsby 2.0 instead of 3.0.
I can get one image using "useStaticQuery", however I need access to different images for each component. From my understanding this is not possible to do within a component, only on a page. I also can not pass the image path as a variable to StaticImage either.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} />
</div>
);
Result
Can anyone please explain to me how I can get the desired layout in a component?
Edit
Here is some relevant code of what I am doing.
This is my index.js home page.
export default function IndexPage({ data }) {
const projects = data.apps.edges;
return (
<Layout>
<SEO title="Home" />
<HeroSection />
<DescriptionSection />
<div>
{projects.map(({ node: project }) => (
<AppSection app={project} />
))}
</div>
<FooterSection />
</Layout>
);
}
//export page query
export const query = graphql`
query Apps {
apps: allAppsJson(sort: { order: ASC, fields: order }) {
edges {
node {
appLink
title
tagline
moreLink
order
icon
}
}
}
}
`;
Here is the component.
export default function App(props) {
const query = useStaticQuery(graphql`
query AppSectionImages {
icon: file(relativePath: { eq: "EzMaxRequest/AppIcon_180.png" }) {
childImageSharp {
gatsbyImageData(
width: 200
placeholder: BLURRED
formats: [AUTO, WEBP, AVIF]
)
}
}
}
`);
const image = getImage(query.icon);
const app = props.app;
return (
<div>
<h1>{app.title}</h1>
<GatsbyImage image={image} alt={app.title} />
</div>
);
}
You have a few options:
Query for all of your image data in your page query and prop-drill the data to the component that uses it to display the image.
Using Gatsby v3+, hardcode the image references for each component using the new StaticImage component.
If you have a single component used multiple times with different content/images, but a static parent component with your content, you can leverage option #2 above but pass the image component down as a prop or children.

Markdown in Antd

I can confirm that this has neither been asked nor been addressed anywhere. I am currently working on a website using Gatsby, Strapi and Antd for the design. I am using the the rich text editor for one of the content types where I have put all my markdown content. However, when I try to display the actual content on the webpage, the styling is completly nuked. I figured this was because the content uses normal HTML elements like <h1> and <p> instead of the antd components like <Title> or <Text>.
So I did some researches and found that Antd has a markdown.less in their source, which I figure is used to style the markdown in their documentation. I haven't found the same after scouring the source code inside the node modules folder. Does this mean that Antd does not support styling for markdown or am I missing something here?
Btw I am using the react-markdown library to display the all the markdown. I have also posted all the relevant code below.
template.tsx
const ProductTemplate: React.FC<Props> = ({ data }: Props) => {
const {
...
} = data
const {
product: { strapiId: selectedKeyProp },
} = data
return (
<Layout>
<AntLayout>
<ProductSidebar
selectedKeyProp={selectedKeyProp}
productsInfo={productsInfo}
>
<ProductInfo product={product} />
</ProductSidebar>
</AntLayout>
</Layout>
)
}
export const query = graphql`
...
`
export default ProductTemplate
page-component.tsx
const ComponentName = ({ data }) => {
const {
...
} = data
console.log(data)
return (
<Layout>
<AntLayout>
<ProductSidebar productsInfo={productsInfo}>
<div style={{ display: "unset", padding: "15px 35px" }}>
<ReactMarkdown className="markdown" children={content} />
</div>
</ProductSidebar>
</AntLayout>
</Layout>
)
}
export const query = graphql`
...
`
export default ComponentName
There problem can be pointed out here I guess. The <ReactMarkdown> receives the markdown content in the children props. But once the content is displayed to the page, the styling, as I mentioned above, is nuked out.
I raised this issue on github here. According to the devs, there is currently no markdown support for antd. As I thought, the markdown.less file linked above was just for the markdown in their documentation.
One way to get around this solution is to include the tags exactly as specified by antd component elements. For example, instead of a # or <h1> for a header, we can use <h1 class="antd typography">, though this definitely is painful and prone to error.
This other solution would be to use MDX, which support jsx inside markdown.
About a month late, but I just ran into this: I needed a way to change the styling of html generated by the react-markdown React component. I am using react-markdown in a NextJS app, and using Antd as my component library. I am additionally using react-syntax-highlighter and react-syntax-highlighter-prismjs for handling lighting codeblocks
While there is no support in Antd for markdown, there is support for custom components in react-markdown! react-markdown allows you to override the rendering engine and replace the individual components with your own, so I went through and replaced a bunch of them with Antd components:
Fair warning: This was my first pass to make sure this worked, it doesn't for instance handle checkboxes inside list item inputs.
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import {coy} from "react-syntax-highlighter/dist/cjs/styles/prism/prism";
import gfm from 'remark-gfm';
import { Typography } from 'antd';
const { Title, Text } = Typography;
import { List } from 'antd';
const mymarkdowndata = '
# A heading
Some text
## A second heading
* List!
* Has
* Many
* Items!
'
const renderers = {
heading: function Heading(props) {
return <Title level={props.level}>{props.children}</Title>;
},
list: function MakeList(props) {
return <List bordered>{props.children}</List>
},
listItem: function MakeListItem(props) {
return <List.Item>{props.children}</List.Item>
},
inlineCode: function makeInlineCode(props) {
return <Text code>{props.children}</Text>
},
code: function makeCodeBlock(props) {
return <SyntaxHighlighter language={props.language} style={coy}>{props.value}</SyntaxHighlighter>
},
blockquote: function makeBlockQuote(props) {
return <Text type="secondary">{props.children}</Text>
}
};
Then inside your component rendering function:
render() {
return <ReactMarkdown renderers={renderers} plugins={[gfm]} children={mymarkdowndata} />
}

Resources