How to fetch content with React hooks
Using global content versus fetched 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 Content, { useContent } from 'fusion:content';import React, { Component } from 'react';
const MovieList = (props) => { const movies = []; 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 we are supposed to call the map function on, but the loop just outputs an empty <div>
right now. So we need to 1) fetch some movies data and 2) output some content inside our movies loop. Letâs call a method to do the fetching:
... const movies = useContent({ 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: [...data.Search] };
// Otherwise just keep the current list of movies else return movies; } })...
Here, weâre utilizing PageBuilder Engineâs useContent
hook 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.
At this point, our fetch should be working. The last problem is we arenât displaying any data. Letâs fix that too:
... const {list: movieList = []} = movies;
return ( <Fragment> <h2>Movies</h2> <div> {movieList && movieList.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> )
This kind of component would formerly be known as stateless
component. However, hooks allow these components to use React features, and thatâs what useContent will do - it will allow you to add React state to this âstatelessâ component. In fact, React would actually prefer you call this simply a Functional Component.
We can now 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 Content, { useContent } from 'fusion:content';import React, { Fragment, Component } from 'react';const MovieList = (props) => { const movies = useContent({ 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: [...data.Search] };
// Otherwise just keep the current list of movies else return movies; } })
const {list: movieList = []} = movies;
return ( <Fragment> <h2>Movies</h2> <div> {movieList && movieList.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
How to fetch content server-side only or client-side only
PageBuilder Engine does not expose an interface to detect if the render is happening on the server side or client side. Itâs easy to detect this though, by checking if the window namespace is defined or not.
// client-side only fetchmovies = useContent({ source: typeof window !== 'undefined' ? null : 'content-source-name' ...})