I am working to achieve the function for user to generate their trip plan into a pdf that they can carry around with them. react-pdf is a very powerful tool for me to achieve this goal.

Prevent SSR in Next.js

As the web rendering functions of react-pdf only work on client side, the first problem I faced was to prevent server side rendering. I wrote another blog on this issue.

Code for a nice pdf

So here comes the big part: create a nice pdf for user’s trip plan. There are limited options on what components I could use:

  • Page
  • Link
  • Text
  • View
  • Image
  • Note
  • Canvas
    It is to be noted that View can be nested within another View.

Creat a fixed header that appears on each page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Text, View, Link } from '@react-pdf/renderer';

<View fixed>
<Text
style={{
fontSize: 10,
textAlign: 'right',
margin: 5,
color: '#ff4200',
}}
>
Presented to you by{' '}
<Link src={'http://triptime.cc'}>TripTime</Link>
</Text>
</View>

Here’s the generated header:

a fixed header on top of each page

Styled Components

1
2
3
4
5
import styled from '@react-pdf/styled-components';

const GrayText = styled.Text`
color: #666;
`;

Then I can use<GrayText>{activity.description}</GrayText> to generate gray Text components.

Nesting Views

Views can be nested.

DayView:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import _ from 'underscore';
import { Text, View} from '@react-pdf/renderer';

export default function DayView(props) {
const day = props.dailyRecord.day;
const events = _.sortBy(props.dailyRecord.events, 'start');

return (
<View>
<Text style={{ color: '#ff6400', fontSize: 16 }}>{day}</Text>
{events.map((event, index) => {
if (event.type === 'travel')
return <TravelView travel={event} key={index} />;
else return <ActivityView activity={event} key={index} />;
})}
</View>
);
}

ActivityView:

ActivityView(props) {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  const activity = props.activity;
return (
<View style={{ fontSize: 14, margin: 5 }} wrap={false}>
<Text>Activity: {activity.name}</Text>
<View style={{ paddingLeft: 10, fontSize: 12 }}>
<GrayText>{activity.description}</GrayText>
{activity.address && (
<Text>
<GrayText>At:</GrayText> {activity.address}
</Text>
)}
<Text>
<GrayText>From:</GrayText> {formatTimeString(activity.start)}{' '}
<GrayText>To:</GrayText> {formatTimeString(activity.end)}
</Text>
</View>
</View>
);
}

Here’s the look of the trip plan pdf, organised by days:

activities and travels organised in days