Broken backwards compatibility of dynamic rendering a `View` using the `render` prop
See original GitHub issueDescribe the bug
- A
View
whose content was rendered using therender
prop function used to be rendered correctly onreact-pdf
v1 even if one of the sub-View
s returned as its children was aText
that uses therender
prop to render its content as well. - On
react-pdf
v1 the return of therender
prop function of aView
could be aReact.Fragment
with as manyView
s as wanted. Now an error (TypeError: element.type is not a function
) is thrown if the return of therender
function is not a singleView
. This can be seen happening here on the REPL.
In this issue I’ll use the Footer
component of my project for code examples. This is how this footer looks like. Note that the layout of the footer changes depending on the page number: it’s aligned to the right on even pages and to the left on even pages. That’s why I need to use the render
prop function of a View
and return in it a Text
that is also rendered using its own render
prop function. The pageNumber
in the View
is used to determine the layout of the footer.
The reason why I need to also render the content of Text
using the render
prop function is because only Text
can give me totalPages
, which I use as a flag to differentiate between the first render from the last one, when all the pages are already at their final position. I can then properly instruct my PageTracker
class about keeping a count of page numbers and at what page each chapter starts. This is crucial in order to enable a Table of Contents.
Here’s the Footer
component’s code which works on react-pdf
v1.6.14:
import React, { Fragment } from 'react'
import { Text, View } from '@react-pdf/renderer'
import { isEmpty, isNil } from 'lodash'
import { useReportContext } from 'core/hooks/useReportContext'
import { getFullName } from 'core/utils/BirthInfo'
import type ReactPDF from '@react-pdf/renderer'
import type { FC, ReactElement } from 'react'
import {
absolute,
bottomPt,
exoLight,
exoSemiBold,
f,
flex,
flexColumn,
flexRow,
flexRowReverse,
hPt,
itemsEnd,
itemsStart,
justifyBetween,
mt,
pb,
phPercent,
w100,
} from '../../style'
interface FooterProps {
title: string
chapterName?: string
forcePageNumber?: number
}
export const Footer: FC<FooterProps> = ({
title,
chapterName,
forcePageNumber,
}) => {
const { birthInfo, pageTracker, theme } = useReportContext()
const name = getFullName(birthInfo)
const textColor: ReactPDF.Style = {
color: theme.footer.textColor,
}
const dividerColor: ReactPDF.Style = {
backgroundColor: theme.footer.dividerColor,
}
return (
<View
fixed
style={[w100, phPercent(10), absolute, bottomPt(0), pb(5)]}
render={({ pageNumber }): ReactElement => {
pageNumber = forcePageNumber ?? pageNumber
const rowDirection = (pageNumber + pageTracker.totalPages).isEven()
? flexRow
: flexRowReverse
const alignment = (pageNumber + pageTracker.totalPages).isEven()
? itemsStart
: itemsEnd
return (
<Fragment>
<View style={[w100, hPt(3), dividerColor]} />
<View
style={[
flex,
rowDirection,
justifyBetween,
itemsStart,
mt(2),
textColor,
]}
>
<View style={[flex, flexColumn, alignment, f(6)]}>
<Text style={[exoSemiBold]}>{name}</Text>
<Text style={[exoLight]}>{title}</Text>
</View>
<Text
style={[f(7), exoSemiBold]}
render={({ pageNumber, totalPages }): Optional<string> => {
if (isNil(totalPages)) {
return
}
pageNumber = forcePageNumber ?? pageTracker.totalPages
pageTracker.registerPage()
pageTracker.registerChapterPage(chapterName)
if (!isEmpty(chapterName)) {
{/* Registers beginning of chapter. This enables the Table of Contents */}
pageTracker.registerChapterStartPage(
pageNumber,
chapterName
)
}
return pageNumber.asDoubleDigitsString()
}}
/>
</View>
</Fragment>
)
}}
/>
)
}
On react-pdf
v2.0.4 this code produces the TypeError: element.type is not a function
.
I can stop that error from happening if I replace the Fragment
with react-pdf
’s View
primitive:
export const Footer: FC<FooterProps> = ({
title,
chapterName,
forcePageNumber,
}) => {
const { birthInfo, pageTracker, theme } = useReportContext()
const name = getFullName(birthInfo)
const textColor: ReactPDF.Style = {
color: theme.footer.textColor,
}
const dividerColor: ReactPDF.Style = {
backgroundColor: theme.footer.dividerColor,
}
return (
<View
fixed
style={[w100, phPercent(10), absolute, bottomPt(0), pb(5)]}
render={({ pageNumber }): ReactElement => {
pageNumber = forcePageNumber ?? pageNumber
const rowDirection = (pageNumber + pageTracker.totalPages).isEven()
? flexRow
: flexRowReverse
const alignment = (pageNumber + pageTracker.totalPages).isEven()
? itemsStart
: itemsEnd
return (
// <Fragment> Replaced this with the View below
<View>
<View style={[w100, hPt(3), dividerColor]} />
<View
style={[
flex,
rowDirection,
justifyBetween,
itemsStart,
mt(2),
textColor,
]}
>
<View style={[flex, flexColumn, alignment, f(6)]}>
<Text style={[exoSemiBold]}>{name}</Text>
<Text style={[exoLight]}>{title}</Text>
</View>
<Text
style={[f(7), exoSemiBold]}
render={({ pageNumber, totalPages }): Optional<string> => {
if (isNil(totalPages)) {
return
}
pageNumber = forcePageNumber ?? pageTracker.totalPages
pageTracker.registerPage()
pageTracker.registerChapterPage(chapterName)
if (!isEmpty(chapterName)) {
{
/* Registers beginning of chapter. This enables the Table of Contents */
}
pageTracker.registerChapterStartPage(
pageNumber,
chapterName
)
}
return pageNumber.asDoubleDigitsString()
}}
/>
</View>
</View>
// <Fragment> Replaced this with the View above
)
}}
/>
)
}
However, this is the result I get from the code above:
A much simpler example of this can be seen here on the REPL. After some tests, it became evident that even a simple dynamic render is not working. In this example on the REPL I could not render a simple View
as the return of the render
prop function. I expected to see a Text
with a green background inside a blue View
inside a red View
. Only the red, outermost View
is rendered.
I couldn’t find a way to run the REPL using other versions of react-pd
, which could be useful to test these scenarios.
Environment:
- OS: macOS
- Node v14.15.0
- React-pdf version: v2.0.4
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (1 by maintainers)
Top GitHub Comments
I noticed that you can wrap
render
function withuseCallback
as a workaround to prevent infinite re-renders, e.g.Thanks @gabrielvincent ! and sorry 😦 I’ll try to see this asap