How to Fetch Content
Itās important to remember that you only need to fetch content if the content you need has not already been provided to you by globalContent
, or if you want to retrieve some content client-side. If the content you need in your feature is part of the āglobalā content of the page, meaning it is semantically identified by the URL the user requested, then you probably donāt need to fetch at all.
A quick example: if you have a feature called authors
whose purpose is to list the authors of the main story on the page, then you will want to use props.globalContent
- since the information in the authors
component is semantically tied to the main story. If, however, you are building an unrelated sports_scores
component that shows the most recent scores from local sports games, that content will almost certainly not exist as part of your main story - so youāll need to fetch it separately.
Fetching content and setting state
Once weāve determined we need to fetch content, we need some more information:
-
when do we want to fetch the content (on the server only, the client only, or both?)
-
what content source do we want to fetch from?
-
what arguments do we need to pass to get the content we want?
-
what pieces of data from the returned content do we actually need?
For our purposes, letās say we want to fetch some content from the movie-search
content source we defined previously. Specifically, we want to fetch a list of movies by their titles.
Letās define a simple component called MovieList
for this purpose:
import Consumer from 'fusion:consumer'import React, { Component } from 'react'
@Consumerclass MovieList extends Component { constructor (props) { super(props) this.state = { movies: [] } }
render () { const { movies } = this.state return ( <div className='movie-list col-sm-12 col-md-4'> <h2>Movies</h2> <div className='movie row'> {movies && movies.map((movie, idx) => <div className='col-sm-12 border' key={`movie-${idx}`}> {/* display movie info here */} </div> )} </div> </div> ) }}
export default MovieList
Right now, our component doesnāt do much - we are initializing an empty movies
array in our state
object that the render
method loops over, but the loop just outputs an empty <div>
right now. So we need to 1) fetch some movies and add them to our state
, and 2) output some content inside our movies loop. Letās add a method to the class to do the fetching:
fetch () { const { movies } = this.state; this.fetchContent({ movies: { source: 'movie-search', query: { movieQuery: 'Jurassic' }, filter: '{ totalResults Search { Title Year Poster } }', transform (data) { // Check if data is being returned if(data && data.Search) return { list: [...movies.list, ...data.Search] };
// Otherwise just keep the current list of movies else return movies } } })}
Here, weāre utilizing PageBuilder Engineās FetchContent Method to fetch some content and then set some state. The function takes in one object variable key we call contentConfigMap
, with the key that you would like to use to retrieve in the state.
For our purpose, we will be putting three parameters in the contentConfigMap
object:
-
source
: The name of the content source (movie-search
for now) -
query
: Object that contains the values we actually want to query on - in this case, amovieQuery
param searching for movies with the word āJurassicā in them (this object will be the only argument passed to theresolve
method in our content source). -
transform
: the function that will destructure the result of the query to cleanly insert them into the state.
This method should work great - except we havenāt invoked it anywhere. Letās change that in our constructor
method:
constructor (props) { super(props) this.state = { movies: { list: [] }} this.fetch = this.fetch.bind(this) this.fetch(); }
At this point our fetch should be working. The last problem is we arenāt displaying any data. Letās fix that too:
render () { const { list: movies } = this.state.movies return ( <Fragment> <h2>Movies</h2> <div> {movies && movies.map((movie, idx) => <div key={`movie-${idx}`}> <h4>{movie.Title}</h4> <p><strong>Year:</strong> {movie.Year}</p> <img src={movie.Poster} /> </div> )} </div> </Fragment> ) }
Because React will re-render automatically whenever there is a change to the state
or props
of our component, and weāre triggering a state change when we fetch our new movies, we can simply iterate over the movies
array in our state and output the information we want (Title
, Year
, Poster
) for each movie as if theyād always been there. This should result in a working component that fetches and displays data about movies with the word āJurassicā in the title. Letās see the entire component together:
import Consumer from 'fusion:consumer'import React, { Fragment, Component } from 'react'
@Consumerclass MovieList extends Component { constructor (props) { super(props); this.state = { movies: { list: [] }}; this.fetch = this.fetch.bind(this) this.fetch(); }
fetch () { const { movies } = this.state; this.fetchContent({ movies: { source: 'movie-search', query: { movieQuery: 'Jurassic' }, filter: '{ totalResults Search { Title Year Poster } }', transform (data) { // Check if data is being returned if(data && data.Search) return { list: [...movies.list, ...data.Search] }
// Otherwise just keep the current list of movies else return movies; } } }) }
render () { const { list: movies } = this.state.movies return ( <Fragment> <h2>Movies</h2> <div> {movies && movies.map((movie, idx) => <div key={`movie-${idx}`}> <h4>{movie.Title}</h4> <p><strong>Year:</strong> {movie.Year}</p> <img src={movie.Poster} /> </div> )} </div> </Fragment> ) }}
export default MovieList
Adding pagination
Unfortunately, this only fetches the first page of movies with āJurassicā in the title from OMDB. But since OMDBās API allows us to send a page
param, and our content source is already set up to accept such a param, itās easy to add pagination to this feature as such:
import Consumer from 'fusion:consumer'import React, { Fragment, Component } from 'react'
@Consumerclass MovieList extends Component { constructor (props) { super(props) this.state = { movies: { pages: [] }, page: 0 } this.fetch = this.fetch.bind(this) this.fetch() }
fetch () { const { page } = this.state;
// Increment the page at each call this.state.page += 1;
this.fetchContent({ movies: { source: 'movie-search', query: { movieQuery: 'Jurassic', page: page + 1 }, filter: '{ totalResults Search { Title Year Poster } }', transform: (data) => { // Check if data is being returned if(data && data.Search) { // Add the results to the paginated list of movies this.state.movies.pages[page] = data.Search return this.state.movies }
// Otherwise just keep the current list of movies else{ return this.state.movies; } } } }) }
render () { // Concatenate the lists of the movies and filter duplicates - this would ensure that // a multiple clicks on the 'More' button wouldn't cause issues with incomplete and out-of-order fetches from // network issues const movies = [].concat(...this.state.movies.pages).filter(movie => movie);
return ( <Fragment> <h2>Movies</h2> <div> {movies && movies.map((movie, idx) => <div key={`movie-${idx}`}> <h4>{movie.Title}</h4> <p><strong>Year:</strong> {movie.Year}</p> <img src={movie.Poster} /> </div> )} <button onClick={ this.fetch }>More</button> </div> </Fragment> ) }}
export default MovieList
With this, all we had to do to get pagination working was:
-
add a
page
property to our state object and initialize it to0
- this will be incremented at the first call to thefetch
function. -
increment the
page
whenever we fetch, so next time weāll fetch the following page - since itās not necessary to re-render the component, we can increment it directly. -
include the page variable with 1 added to it (we are not passing the stateās incremented page value) - this is so that we can preserve the ordering of the paginated list of the movies and make sure the list starts properly at the index of 0 when we assign the result of the fetch request to the list.
-
Add a button at the bottom of the component that allows us to call the
fetch
method, which should get the next page of results, display the new results, and increment the page all at once.
And thatās how you fetch content in PageBuilder Engine.