Skip navigation

Build your first React app

In this tutorial, you'll use the Create React appOpens in a new window boilerplate together with the JavaScript Delivery SDK to create a simple blog. You'll learn how to create react components to list articles, display article detail, resolve embedded content in rich text, and resolve links to content items. Let's get started.

You can view the final result hereOpens in a new window, see the source code on GithubOpens in a new window, or explore a live exampleOpens in a new window without installing anything.

New to Kentico Kontent?

If you're starting with Kentico Kontent, we suggest you begin with the Hello World tutorial, in which you'll learn the basics of creating content.

Table of contents

    Requirements

    To follow along, you'll need an IDE (such as Visual Studio CodeOpens in a new window) or the text editor of your choice. A basic knowledge of react and react-router comes handy but isn't required. Make sure you have Node and npmOpens in a new window installed and that you have a Kontent Sample Project to use.

    Create a new project

    1. Prepare the development environment

    In your command line, run the following command to set up a new React project the create-react-appOpens in a new window boilerplate.

    • shell
    npx create-react-app my-kontent-react-app
    npx create-react-app my-kontent-react-app

    2. Add dependencies

    Navigate to the my-kontent-react-app project folder and use npm to install all the required libraries.

    • react-router-dom – for client-side routing in a single page application
    • @kentico/kontent-delivery – for retrieving content from Kentico Kontent
    • rxjs – for managing asynchronous operations
    • shell
    cd my-kontent-react-app npm install --save react-router-dom rxjs @kentico/kontent-delivery
    cd my-kontent-react-app npm install --save react-router-dom rxjs @kentico/kontent-delivery

    3. Create a config file with delivery client

    In the src folder, create a config.js file and define the JavaScript SDK's DeliverClient.

    • JavaScript
    import {DeliveryClient} from "@kentico/kontent-delivery"; export const deliveryClient = new DeliveryClient({ // Tip: Use your own sample project ID instead of the Sample Project ID projectId: "975bf280-fd91-488c-994c-2f04416e5ee3", });
    import {DeliveryClient} from "@kentico/kontent-delivery"; export const deliveryClient = new DeliveryClient({ // Tip: Use your own sample project ID instead of the Sample Project ID projectId: "975bf280-fd91-488c-994c-2f04416e5ee3", });

    You can paste your own Sample Project ID into the DeliveryClient properties or keep using the default 975bf280-fd91-488c-994c-2f04416e5ee3 project ID. The default sample project contains everything required for this tutorial.

    It's good practice to keep your DeliveryClient configuration in a separate file and import it to your components and services.

    4. (Optional) Simplify the looks

    Overwrite contents of the App.css with something simpler.

    • CSS
    .App { padding: 0 20%; } .App-header { background-color: #222; height: 150px; padding: 20px; color: white; }
    .App { padding: 0 20%; } .App-header { background-color: #222; height: 150px; padding: 20px; color: white; }

    5. Run your development environment

    In your command line, use npm to start a development server.

    • shell
    npm start
    npm start

    This opens http://localhost:3000/ in your browser.

    The app will auto-reload every time you save a file to reflect the latest changes, you can keep it running as you code.

    Add routing

    In the src folder, rewrite the App.js file with the code below. This creates routes for the ArticleView and ArticleListing components, which you'll add in a moment.

    • JavaScript
    import React from "react"; import {BrowserRouter as Router, Route} from "react-router-dom"; // You'll add these two Article components in a moment import ArticleListing from "./ArticleListing"; import ArticleView from "./ArticleView"; import "./App.css"; function App() { return ( <div className='App'> <header className='App-header'> <h1 className='App-title'>Our blog</h1> </header> {/* Specifies components to handle specific routes */} <Router> <div> {/* This line specifies that if the browser URL matches /, the ArticleListing component should be rendered in this place. */} <Route exact path='/' component={ArticleListing} /> {/* Paths can contain parameters, for example, path="/post/:slug", which are delivered to the target component through react props. */} <Route path='/post/:slug' component={ArticleView} /> </div> </Router> </div> ); } export default App;
    import React from "react"; import {BrowserRouter as Router, Route} from "react-router-dom"; // You'll add these two Article components in a moment import ArticleListing from "./ArticleListing"; import ArticleView from "./ArticleView"; import "./App.css"; function App() { return ( <div className='App'> <header className='App-header'> <h1 className='App-title'>Our blog</h1> </header> {/* Specifies components to handle specific routes */} <Router> <div> {/* This line specifies that if the browser URL matches /, the ArticleListing component should be rendered in this place. */} <Route exact path='/' component={ArticleListing} /> {/* Paths can contain parameters, for example, path="/post/:slug", which are delivered to the target component through react props. */} <Route path='/post/:slug' component={ArticleView} /> </div> </Router> </div> ); } export default App;

    To make sure the new routes work, let's add the two components for displaying articles!

    List articles

    In the src folder, create a new file named ArticleListing.js. This component will retrieve all published articles in the project and render them as links.

    Instead of requesting entire articles, use the elementsParameter method to get only the elements you need. In this case, the title of the article and its URL.

    • JavaScript
    import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleListing() { // Uses the React state hook const [articles, setArticles] = useState([]); const [isLoading, setLoading] = useState(true); // Gets URL slugs and titles of all articles in the project const getArticles = () => { return deliveryClient .items() .type("article") .elementsParameter(["url_pattern", "title"]) .toObservable() .subscribe((response) => { setArticles(response.items); setLoading(false); }); }; useEffect(() => { const subscription = getArticles(); return () => subscription.unsubscribe(); }, []); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } // Displays a list of the retrieved articles with links to their detail return ( <ul> {articles.map((article) => { return ( <li key={article.url_pattern.value}> <Link to={`/post/${article.url_pattern.value}`}>{article.title.value}</Link> </li> ); })} </ul> ); } export default ArticleListing;
    import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleListing() { // Uses the React state hook const [articles, setArticles] = useState([]); const [isLoading, setLoading] = useState(true); // Gets URL slugs and titles of all articles in the project const getArticles = () => { return deliveryClient .items() .type("article") .elementsParameter(["url_pattern", "title"]) .toObservable() .subscribe((response) => { setArticles(response.items); setLoading(false); }); }; useEffect(() => { const subscription = getArticles(); return () => subscription.unsubscribe(); }, []); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } // Displays a list of the retrieved articles with links to their detail return ( <ul> {articles.map((article) => { return ( <li key={article.url_pattern.value}> <Link to={`/post/${article.url_pattern.value}`}>{article.title.value}</Link> </li> ); })} </ul> ); } export default ArticleListing;

    Your articles displayed in a list.

    Now you'll add another component that takes care of displaying a specific article. 

    Display article detail

    In the src folder,  create a new file named ArticleView.js. This will retrieve a single article and render it on the page.

    • JavaScript
    import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleView({match, history}) { // Uses the react state hook const [article, setArticle] = useState({}); const [isLoading, setLoading] = useState(true); // Gets an article by its URL slug const getArticle = (slug) => { return deliveryClient .items() .type("article") .equalsFilter("elements.url_pattern", slug) .toObservable() .subscribe((response) => { setArticle(response.items[0]); setLoading(false); }); }; useEffect(() => { const subscription = getArticle(match.params.slug); return () => subscription.unsubscribe(); }, [match.params.slug]); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } return ( <div> <Link to='/'>Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{__html: article.body_copy.resolveHtml()}} /> </div> ); } export default ArticleView;
    import React, {useState, useEffect} from "react"; import {Link} from "react-router-dom"; import {deliveryClient } from "./config"; function ArticleView({match, history}) { // Uses the react state hook const [article, setArticle] = useState({}); const [isLoading, setLoading] = useState(true); // Gets an article by its URL slug const getArticle = (slug) => { return deliveryClient .items() .type("article") .equalsFilter("elements.url_pattern", slug) .toObservable() .subscribe((response) => { setArticle(response.items[0]); setLoading(false); }); }; useEffect(() => { const subscription = getArticle(match.params.slug); return () => subscription.unsubscribe(); }, [match.params.slug]); // Shows loading until the app gets article from Kontent if (isLoading) { return <div>Loading...</div>; } return ( <div> <Link to='/'>Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{__html: article.body_copy.resolveHtml()}} /> </div> ); } export default ArticleView;

    When you check your app in the browser and click on an article, the app will display the article's content.

    Detail of a specific article.

    Render rich text content

    Kontent gives you a great amount of flexibility for embedding pieces of content in rich text elements such as videos, tweets, or code examples.

    Here is an example of how inserting videos works in a nutshell:

    1. You create a Hosted video content type. This defines information about the video your application needs.
    2. In articles, you insert a Hosted video component (or item) into a rich text element.
    3. Your application resolves the rich text component (or item) to the appropriate HTML and renders it.

    The Sample Project already contains the Hosted video content type.

    A screenshot of a component for a hosted video in a content item.

    A Hosted video component used in a rich text element in one of the articles in Kontent.

    This tutorial assumes you've already defined a video content type and used it in an article. If you don't know how to do that yet, check out the basics of structured content.

    1. Define a resolver for the video

    Use a content item resolver to define how to render items and components of a specific type in rich text elements.

    • A content resolver accepts a content item object as its argument and returns the desired HTML output as a string.
    • It's good practice to define your resolvers in a separate file and import them to your components.

    In the src folder, create a new file named itemResolver.js.

    • JavaScript
    export const resolveItemInRichText = (item) => { if (item.system.type === "hosted_video") { let video = item; if (video.video_host.value.find((item) => item.codename === "vimeo")) { return `<iframe class="hosted-video__wrapper" src="https://player.vimeo.com/video/${video.video_id.value}?title=0&byline=0&portrait=0" width="100%" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen > </iframe>`; } else if (video.video_host.value.find((item) => item.codename === "youtube")) { return `<iframe class="hosted-video__wrapper" width="100%" height="360" src="https://www.youtube.com/embed/${video.video_id.value}" frameborder="0" allowfullscreen > </iframe>`; } } return undefined; };
    export const resolveItemInRichText = (item) => { if (item.system.type === "hosted_video") { let video = item; if (video.video_host.value.find((item) => item.codename === "vimeo")) { return `<iframe class="hosted-video__wrapper" src="https://player.vimeo.com/video/${video.video_id.value}?title=0&byline=0&portrait=0" width="100%" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen > </iframe>`; } else if (video.video_host.value.find((item) => item.codename === "youtube")) { return `<iframe class="hosted-video__wrapper" width="100%" height="360" src="https://www.youtube.com/embed/${video.video_id.value}" frameborder="0" allowfullscreen > </iframe>`; } } return undefined; };

    In a real-world app, you'd define a type resolver for each content type that your editors can use for creating content.

    2. Register the resolver

    In the src folder, modify the ArticleView.js component.

    • Import your resolveItemInRichText resolver.
    • Add a .queryConfig method to your delivery client query to register the resolver.
    • JavaScript
    // Imports your new content item resolver. import {resolveItemInRichText} from "./itemResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ richTextResolver: resolveItemInRichText }) ... };
    // Imports your new content item resolver. import {resolveItemInRichText} from "./itemResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ richTextResolver: resolveItemInRichText }) ... };

    Notice that you've added the resolver to a single query. If you don't want to do this each time, define resolvers globallyOpens in a new window.

    A screenshot of a hosted video embedded in a final website.

    Your app can now display videos inserted into articles. Try http://localhost:3000/post/coffee-beverages-explainedOpens in a new window.

    Textual links to content items in rich text elements are, by default, returned as <a> tags without any href value. This tells you to which item the link points but you need to specify the link destination yourself.

    • HTML
    <a data-item-id="23f71096-fa89-4f59-a3f9-970e970944ec" href="">this is a link to another article</a>
    <a data-item-id="23f71096-fa89-4f59-a3f9-970e970944ec" href="">this is a link to another article</a>

    Use a link resolver to define how to render links to content items of a specific content type. A link resolver accepts a rich text link object as its argument and returns the desired relative URL as a string.

    It's good practice to define your resolvers in a separate file and import them to your components.

    In the src folder, create a file named linkResolver.js.

    • JavaScript
    export const resolveContentLink = (link) => { if (link.type === "article") { return {url: `/post/${link.urlSlug}`}; } return undefined; };
    export const resolveContentLink = (link) => { if (link.type === "article") { return {url: `/post/${link.urlSlug}`}; } return undefined; };

    The resolver here is short on purpose. In a real app, you would define a link resolver for each type of content your editors can link to in rich text.

    In the src folder, modify the ArticleView.js component.

    • Import your resolveContentLink resolver.
    • Add a .queryConfig method to your delivery client query to register the resolver.
    • JavaScript
    // Imports your new link resolver. import {resolveContentLink} from "./linkResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ urlSlugResolver: resolveContentLink }) ... };
    // Imports your new link resolver. import {resolveContentLink} from "./linkResolver"; ... function ArticleView({match, history}) { ... const getArticle = (slug) => { return deliveryClient .items() .type("article") ... // Registers your new resolver. .queryConfig({ urlSlugResolver: resolveContentLink }) ... };

    Notice that you've added the resolver to a single query. If you don't want to do this each time, define resolvers globallyOpens in a new window.

    Clicking on links to other Article content items now takes you to the linked article. But the app reloads completely every time you click a link.

    Because you can't use the react-router's <Link> components in your resolvers, you need to write a handler method that will navigate to the linked article using react-router-dom programmatically.

    3. Add a click event handler

    Add an onClick handler to the div rendering the HTML of the rich text element. You need to pass the event object and the bodyCopy rich text element of the article containing metadata about all links to content items.

    In the src folder, modify the return method in your ArticleView.js.

    • JavaScript
    return ( <div> <Link to="/">Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{ __html: article.body_copy.resolveHtml() }} // Registers your custom onClick handler onClick={(event) => handleClick(event, article.body_copy)} /> </div> );
    return ( <div> <Link to="/">Home</Link> <h1>{article.title.value}</h1> <div className='article_body' dangerouslySetInnerHTML={{ __html: article.body_copy.resolveHtml() }} // Registers your custom onClick handler onClick={(event) => handleClick(event, article.body_copy)} /> </div> );

    4. Resolve and navigate to the new route

    In the same file, add a new handleClick method to the ArticleView function. This is what the method does:

    1. Checks if the user clicked on a link to a content item, other links should behave normally. In such case it prevents the browser from handling the event with event.preventDefault();.
    2. Handles the event by constructing a new path with the imported resolveContentLink method.
    3. Pushes the new path into the history object to navigate to the new URL. This passes new props to the ViewArticle component.
    • JavaScript
    const handleClick = (event, richTextElement) => { // Checks if the user clicked on a link to a content item. if (event.target.tagName === 'A' && event.target.hasAttribute('data-item-id')) { event.preventDefault(); const id = event.target.getAttribute('data-item-id'); const link = richTextElement.links.find(link => link.linkId === id); const newPath = resolveContentLink(link).url; if (newPath) history.push(newPath); } }
    const handleClick = (event, richTextElement) => { // Checks if the user clicked on a link to a content item. if (event.target.tagName === 'A' && event.target.hasAttribute('data-item-id')) { event.preventDefault(); const id = event.target.getAttribute('data-item-id'); const link = richTextElement.links.find(link => link.linkId === id); const newPath = resolveContentLink(link).url; if (newPath) history.push(newPath); } }

    Consider how you want to route

    There is more that one way to navigate to a new route programmatically and this article on React RouterOpens in a new window does a good job explaining the pros and cons of each approach.

    It also explains how to use withRouter if your component is not rendered by Router and can't access history through its props.

    Build and deploy

    Because your application doesn't require any server-side code, you can host it as a collection of static files for free on Surge, Github pages, or a similar service.

    Run the following commands to build and deploy your app using Surge.

    • Before deploying, you need to rename the built index.html file to 200.html to enable client-side routingOpens in a new window.
    • The first time you run Surge, it will prompt you to set up your account. Every other time it will confirm the build directory and generate a funny subdomain for your app.
    • You can store the set of commands for deploying your app as a script inside your package.json file.
    • shell
    npm install --global surge # Build your app npm run build # Rename index.html to 200.html in the build folder mv build/index.html build/200.html # Run surge to upload your built app surge build
    npm install --global surge # Build your app npm run build # Rename index.html to 200.html in the build folder mv build/index.html build/200.html # Run surge to upload your built app surge build
    • JSON
    { // ... "scripts": { "deploy": "npm run build && mv build/index.html build/200.html && surge build <your domain>.surge.sh" // ... }
    { // ... "scripts": { "deploy": "npm run build && mv build/index.html build/200.html && surge build <your domain>.surge.sh" // ... }

    Conclusion

    Congratulations! Your app can retrieve and display content from Kentico Kontent, change routes, resolve links between content items and display embedded videos. View the final source code on GithubOpens in a new window.

    For learning purposes, we have simplified some aspects of the application in this tutorial. For example, we recommend defining strongly-typed models for your project's content types using a model generatorOpens in a new window.

    What's next?