Yes, the react-pdf documentation about dynamic content.
So having faced the same challenge, here's what I discovered. It should help you ...
If you look at the codebase, under @react-pdf/renderer/index.d.ts (v2.1.1), you'll find two major optional props - render and children - that <Text/> can accept (at least it vital for you to understand them for this use case of passing dynamic content/data)
declare namespace ReactPDF {
// ...
interface TextProps extends NodeProps {
render?: (props: {
pageNumber: number;
totalPages: number;
subPageNumber: number;
subPageTotalPages: number;
}) => React.ReactNode;
children?: React.ReactNode;
// ...
}
/**
* A React component for displaying text...
*/
class Text extends React.Component<TextProps | SVGTextProps> {}
// ...
}
declare const Text: typeof ReactPDF.Text;
// ...
export { Text, // ... }
Note this:
render prop expects specific props to be passed through it.
So it cannot accept any kind of data fetched via the API calls unless you're obviously passing it stuff related to no. of pages and total pages etc.
On the hand, the children prop is more open versatile in this case.
The type of "children" expected are based off the React.ReactNode.
type React.ReactNode = boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined
So your answer lies in utilizing the children prop. How?
Here's a step-by-step process:
- In the Parent component, do this...
const Parent = props => {
React.useEffect(() => {
// make API call
apiCall(...).then(res => {
if (res) {
// store your data in a session or local storage
// Why don't we use "state" and pass it down as props?
sessionStorage.setItem('data', JSON.stringify(res));
};
}).catch(error => ... )
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (...);
};
If you try to pass your fetched data (after the API call) by updating the Parent component state with an aim to pass it to the Child component as props, it does not work!
Why? The behavior is that the passed props are often undefined (I'm yet to understand why!).
Therefore storing the fetched data using those storage methods helps (or if you're using redux you might want to keep it in the store)
- In the Child component, do this...
import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer';
// Create styles
const styles = StyleSheet.create({
page: { ... },
section: { ... }
});
// Create Document Component
export const Child = () => {
const fetchedData = JSON.parse(sessionStorage.getItem("data"));
// let's destructure our data
const { email, gender, username, lastname, firstname, ... } = fetchedData;
// we define block-scope React Components to be passed into <Text>
// for example ...
const LastName = () => `${lastname}`;
return (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.section}>
<Text>Last name:</Text>
<Text children={<LastName/>} />
</View>
<View style={styles.section}>
<Text>Section #2</Text>
</View>
</Page>
</Document>
);
};
If you change this line:
<Text children={<LastName/>} />
and replace it with this:
<Text>{lastname}</Text>
It will not work (to your dismay) ... see the "Why?" under Parent component.
Otherwise, here's a screenshot where this approach worked - look towards the bottom-left to see what the Child component renders (that's dynamic).