27. The Sponsors Component#
In a previous chapter Content types III: Sponsors you created the sponsor
content type.
Now let's learn how to display content of this type.
To be solved task in this part:
Advert to sponsors on all pages, sorted by level
In this part you will:
Display data from fetched content
Topics covered:
Create React component
Use React action of Volto to fetch data from Plone backend via REST API
Style component with Semantic UI
Checkout volto-ploneconf
at tag "listing_variation":
git checkout listing_variation
The code at the end of the chapter:
git checkout sponsors
More info in The code for the training
For sponsors we will stay with the default view as we will only display the sponsors in the footer and do not modify their own pages. Using what you learned in Volto view component: A default view for a "Talk" you should be able to write a view for sponsors if you wanted to.
27.1. A React component#
React components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
You can write a view component for the current context - like the
TalkView
.You can also write components that are visible on all views of content objects.
Volto comes with several components like header, footer, sidebar. In fact everything of the UI is build of nested components.
Inspect existing components with the React Developer Tools.
27.2. The Sponsors Component#
Steps to take
Copy and customize the Footer component.
Create component to fetch data from backend and to display the fetched data.
Getting the sponsors data#
With our Sponsors
component in place we can take the next step and explore Volto some more to figure out how it does data fetching.
As the data is in the backend, we need to find a way to address it. Volto provides various predefined actions to communicate with the backend (fetching data, creating content, editing content, etc.). A Redux action communicates with the backend and has a common pattern: It addresses the backend via REST API and updates the global app store according to the response of the backend. A component calls an action and has hereupon access to the global app store (shortened: store) with the fetched data.
For more information which actions are already provided by Volto have a look at core/packages/volto/src/actions
.
Our component will use the action searchContent
to fetch data of all sponsors.
It takes as arguments the path where to search, the information what to search and an argument with which key the data should be stored in the store.
Remember: the result is stored in the global app store.
So if we call the action searchContent
to fetch data of sponsors, that means data of the instances of content type sponsor
, then we can access this data from the store.
The Hook useEffect
lets you perform side effects in function components
. We use it to fetch the sponsors data from the backend.
1const dispatch = useDispatch();
2
3useEffect(() => {
4 dispatch(
5 searchContent(
6 '/',
7 {
8 portal_type: ['sponsor'],
9 review_state: 'published',
10 fullobjects: true,
11 },
12 'sponsors',
13 ),
14 );
15}, [dispatch]);
Search options#
The default representation for search results is a summary that contains only the most basic information like title, review state, type, path and description.
With the option
fullobjects
all available field values are present in the fetched data.Another option is
metadata_fields
, which allows to get more attributes (selection of Plone catalog metadata columns) than the default search. The search is done without a performance expensive fetch via optionfullobjects
as soon as the attributes are available from catalog as metadata.
Possible sort criteria are indices of the Plone catalog.
1const dispatch = useDispatch();
2
3useEffect(() => {
4 dispatch(
5 searchContent(
6 '/',
7 {
8 portal_type: ['News Items'],
9 review_state: 'published',
10 sort_on: "effective",
11 },
12 'sponsors',
13 ),
14 );
15}, [dispatch]);
Check which info you get with the search request in Google developer tools:
See also
REST API Documentation Search
Connection of component and store#
Let's connect the store to our component. The Selector Hook useSelector
allows a function component
to connect to the store.
It's worth exploring the store of our app with the Redux Dev Tools which are additional Dev Tools to React Dev Tools.
There you can see what is stored in state.search.subrequests.sponsors
.
And you can walk through time and watch how the store is changing.
1const sponsors = useSelector((state) =>
2 groupedSponsorsByLevel(state.search.subrequests.sponsors?.items),
3);
With these both: dispatching the action and a connection to the state in place, the component can call the predefined action searchContent
and has access to the fetched data via its constant sponsors
.
The next step is advanced and can be skipped on a first reading. As by now we fetch the sponsors data on mounting event of the component. The mounting is done once on the first visit of a page of our app. What if a new sponsor is added or a sponsor is published? We want to achieve a re-rendering of the component on changed sponsorship. To subscribe to these changes in sponsorship, we extend our already defined connection.
1const content = useSelector((state) => state.workflow.transition);
2
3useEffect(() => {
4 dispatch(
5 searchContent(
6 '/',
7 {
8 portal_type: ['sponsor'],
9 review_state: 'published',
10 fullobjects: true,
11 },
12 'sponsors',
13 ),
14 );
15}, [dispatch, content]);
Listening to this subscription the component fetches the data from the store if a workflow state changes.
Presentation of the prepared data#
With the data fetched and accessible in the component constant sponsors
we can
now render the sponsors data.
We prepare the sponsors data as a dictionary grouped by sponsor level: groupedSponsorsByLevel.
const groupedSponsorsByLevel = (array = []) =>
array.reduce((obj, item) => {
let token = item.level?.token || 'bronze';
obj[token] ? obj[token].push(item) : (obj[token] = [item]);
return obj;
}, {});
Which results in an dictionary Object available with our subscription sponsors
:
{
bronze: [sponsordata1, sponsodata2]
}
With the subscription sponsors
we can now show a nested list.
1{keys(sponsors).map((level) => {
2 return (
3 <div key={level} className={'sponsorlevel ' + level}>
4 <h3>{level.toUpperCase()}</h3>
5 <Grid centered>
6 <Grid.Row centered>
7 {sponsors[level].map((item) => (
8 <Grid.Column key={item['@id']} className="sponsor">
9 <Component
10 componentName="PreviewImage"
11 item={item}
12 alt={item.title}
13 responsive={true}
14 className="ui image"
15 />
16 </Grid.Column>
17 ))}
18 </Grid.Row>
19 </Grid>
20 </div>
21 );
22})}
We group the sponsors by sponsorship level.
An Object sponsors
using the sponsorship level as key helps to build rows with sponsors by sponsorship level.
The Volto component Image
is used to display the logo.
It cares about the markup of an html image node with all necessary attributes in place.
We also benefit from Semantic UI React component Grid
to build our list of sponsors. The styling can be customized but these predefined components help simplifying the code and achieve an app wide harmonic style.
See the new footer. A restart is not necessary as we didn't add a new file. The browser updates automagically by configuration.
27.3. Exercise#
Modify the component to display a sponsor logo as a link to the sponsors website. The address is set in sponsor field "url".
27.4. Summary#
You know how to fetch data from backend. With the data you are able to create a component displayed at any place in the website.