Person details component
Contents
In this chapter you’ll create a component to display details of a single person.
Query
Similar to the PersonList
component, create a query which will fetch details of a person:
src/queries/Person.ts
const query = `query PersonQuery($personId: ID!) {
guillotine {
get(key: $personId) {
_name
displayName
... on com_enonic_app_intro_Person {
data {
dateofbirth
photos {
... on media_Image {
imageUrl(scale: "width(1000)", type: absolute)
attachments {
name
}
}
}
bio(processHtml: { type: absolute }) {
processedHtml
links {
ref
media {
content {
_id
}
}
content {
_id
_name
type
}
}
images {
ref
image {
_id
}
}
macros {
ref
name
descriptor
config {
factbox {
header
}
}
}
}
}
}
}
}
}`;
export default query;
bio contains Rich Text content |
Styling
This time, we will use css modules to style this component.
Add the following file to your project:
src/components/Person.module.css
.person,
.person .bio {
font-family: sans-serif;
}
.person {
display: flex;
flex-direction: column;
padding: 30px
}
.bio {
line-height: 24px;
}
.bio figcaption {
font-size: 12px;
}
.photos {
padding: 0 20px 20px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 50px;
gap: 50px;
}
.photos img {
width: 100%;
height: auto; /* Ensures images scale properly */
}
@media screen and (max-width: 720px) {
.bio figure {
width: auto !important;
}
}
Component
Add the Person
component:
src/components/Person.tsx
import type {RichTextData} from '@enonic/react-components';
import {RichText} from '@enonic/react-components';
import {useEffect, useState} from 'react';
import {Link as RouterLink, useParams} from 'react-router-dom';
import PERSON_QUERY from '../queries/Person';
import styles from './Person.module.css';
export function Person() {
const {
// name,
personId = ''
} = useParams();
const guillotineUrl = import.meta.env.VITE_GUILLOTINE_URL as string;
const [data, setData] = useState<{
_name: string
data: {
bio: RichTextData
dateofbirth: string
photos: {
imageUrl: string
}[]
}
displayName: string
}>();
useEffect(() => {
fetch(guillotineUrl, {
body: JSON.stringify({
query: PERSON_QUERY,
variables: {
personId,
}
}),
headers: {
'content-type': 'application/json'
},
method: 'POST',
})
.then(response => response.json())
.then(json => {
const value = json.data.guillotine.get;
setData(value)
});
}, []); // useEffect
if (!data) {
return null;
}
const {
data: {
bio,
photos
},
displayName
} = data;
return (
<>
<div className={styles.person}>
<h2>{displayName}</h2>
<RichText
className={styles.bio}
data={bio}
tag='article'
/>
{
photos.length && <h4 className={styles.photosheader}>Photos</h4>
}
<div className={styles.photos}>
{
photos.map((photo: any, i: number) => (
<img key={i}
src={photo.imageUrl}
title={getTitle(photo, displayName)}
alt={getTitle(photo, displayName)}
width="500"
/>
))
}
</div>
</div>
<RouterLink to='/'>Back to list</RouterLink>
</>
);
}
function getTitle(photo: any, displayName: string) {
return (photo.attachments || [])[0].name || displayName;
}
Here, we use the RichText component from @enonic/react-components npm module to render data from the bio . We will dive into more details in the next chapter. |
Routing
Finally, App.tsx
must be updated to handle the Person
component, and trigger it on the following route pattern: /p/:name/:personId
.
This means the link should look something like this: /p/name-of-person/id-of-person
. Since the ID is unique, we can use this to look up the content in Enonic.
src/App.tsx
import './App.sass';
import {Route, BrowserRouter as Router, Routes, } from 'react-router-dom';
import {PersonList} from './components/PersonList';
import {Person} from './components/Person';
export default function App() {
return (
<Router>
<Routes>
<Route path="/" element={<PersonList />}/>
<Route path="/p/:name/:personId" element={<Person />}/>
</Routes>
</Router>
);
}
Result
To see the Person
component in action, open http://localhost:3000 and click any of the persons in the list.
Alternatively, here’s the direct link to Lea Seydoux: