Have you ever thought of building a single-page application(SPA) with react & Sanity? In this guide, I will take you on an exciting journey to build your first SPA with react and Sanity. We will create a Food Catalog app that will contain various food items and categories to organize them by themselves. All these categories and foods will be fetched from Sanity.
Here is the project GitHub repository; you can clone or download the complete project. You can also see the deployed app here.
Warning
This guide contains code examples for an older version of Sanity Studio (v2), which is deprecated.
React is an open-source JavaScript library created for building fast and interactive user interfaces for web and mobile applications. It is maintained by Facebook and a community of individual developers and companies. React can be used for the development of single-page or mobile applications.
React is a component-based framework, which means we'll be writing our code in small, reusable pieces and putting them all together to make our website.
Understanding SPA
SPA stands for Single Page Application. It is a web application or website that interacts with the user by dynamically rewriting the current page rather than loading entire new pages from the server. In simpler terms, it is an app that works inside a browser and does not require page reloading during use. We'll also touch on how routing works in React and learn how to map different parts of our website to different views.
Setting up a React app
For this guide, we will make use of create-react-app — the React recommended way to create a single-page application. To make use of create-react-app, You'll need to have Node >= 14.0.0 and npm >= 5.6 on your machine.
To install, run the following command:
npx create-react-app food-catalog-app
After the installation, you can move into the project's root directory to start the development server.
cd food-catalog-app
npm start
When the dev server is ready, you can view your project in the browser at http://localhost:3000/.
Your project directory should look like this once the setup is complete.
Let's clean up the app by deleting files we don't need so we can get started building our app. You can delete the following highlighted files.
Protip
Note: This might throw an error, so ensure you delete the logo.svg import from App.css and the index.css, reportWebVitals.js import from index.js.
To be sure your files look like mine exactly, I pushed my setup files to this GitHub repository, and you can clone it or cross-check.
Using Tailwind CSS
TailwindCSS is a utility-first CSS framework used to build frontend applications. Rather than adding obscure CSS classes to your code, with TailwindCSS, you use utility classes to create your components, with as much control over every single styling as you want. All without having to ever write a single line of CSS.
There are so many ways to make use of Tailwind CSS in React and other frameworks generally, but for this guide, we will be making use of the CDN.
In the /src folder, add the following CDN import to the App.css file.
Once this is done, we can now proceed to set up our sanity studio.
What is Sanity?
Sanity is a content platform that treats your structured content as data. It comes bundled with an open-source, real-time headless CMS that can be customized with JavaScript. We'll be using the Sanity studio to manage our content and accessing it in our frontend through Sanity's "out of the box" API.
One of the key benefits of Sanity is that the content management interface, or "studio," is open source and built to be built upon. If you know React, you can extend and customize it to your heart's content.
Getting started with Sanity/setup
To start a new project with Sanity, we'll install the Sanity CLI globally. To do this, you will need to have Node and npm installed.
npm install -g @sanity/cli
As soon as the CLI finishes installing, you'll be able to create a new project using it. Run the following in the directory of the React project you just created (i.e., food-catalog-app).
sanity init
If this is your first time, this will log you into Sanity, and then you will be able to create a project, set up a dataset, and generate files that are necessary to run the editing environment locally.
Gotcha
Note: If you don't have an account, Sanity will direct you on how to get one. Or, you could visit Sanity's website and create an account.
Once that is done, you will be prompted to create a new project, tap Enter. Name your project food-catalog-studio and choose the default dataset configuration by typing Y.
Finally, confirm the project path and choose a schema for your studio. For this demo, you'll need to start with the "Clean project with no predefined schemas" option.
To start the studio, move to the folder by running the following command in the terminal:
cd studio
Now, you can start the studio with the following command:
sanity start
After compiling, the studio will open on http://localhost:3333, and we will have something like this displayed because we choose "clean project with no predefined schemas" when setting up our studio.
Sanity studio
The Sanity Studio is an open-source CMS built with React.js that allows users to use toolkits and plugins to create the workflow that optimizes for how you want to work with content. It offers rapid configuration and free form customization.
Creating a food catalog schema from scratch
A schema describes the different field types in a document. There are different types of schema you can choose from.
For this guide, we will be creating two schemas: one for the food categories and the other for food items. The food schema will have properties such as food name, food description, food image, and its category, while the category schema will have the name, image, description, and then a hex-code field for creating dynamic styling.
When the studio starts up, it looks for the schema.js file in the schemas folder of your project. For now, since we don't have any schema, you will find something like this in your schema.js file.
// First, we must import the schema creatorimport createSchema from"part:@sanity/base/schema-creator";// Then import schema types from any plugins that might expose themimport schemaTypes from"all:part:@sanity/base/schema-type";// Then we give our schema to the builder and provide the result to SanityexportdefaultcreateSchema({// We name our schemaname:"default",// Then proceed to concatenate our document type// to the ones provided by any plugins that are installedtypes: schemaTypes.concat([/* Your types here! */]),});
All your schemas go into the types array by defining the schemas title, name, type, and finally, the fields. For our foods schema, we will be having something like this.
types: schemaTypes.concat([/* Your types here! */{title:'Foods',name:'foods',type:'document',fields:[{title:'Food Name',name:'foodName',type:'string',validation:Rule=> Rule.required()},{title:'A little description',name:'foodDesc',type:'text',options:{maxLength:200,},validation:Rule=> Rule.required()},{title:"Food Image",name:"foodImage",type:"image",options:{hotspot:true,},}]},//Category schema goes here]),
Once you have done this, save and now let's take a deeper look at the code above, each object represents a field and must have a title, name, and type.
Title: The display name for this field
Name: The identifier for this field used in the API's
Type: The type of this field, e.g., string, image, e.t.c. You can find the complete list of built-in types here.
The second schema to create is the category schema which we'll add just below the foods schema object.
Save the file, and you will see something like this:
One last thing to do will be to include a category in our foods schema. It displays the category fetched from the category schema for you when filling in your foods. To do that, we will create a new field wherever in the array we want this field to be
Once you save your code and create a new document of type food in your studio, you should see the categories field show up as expected.
Organizing our schema
Let's organize our schemas before loading them with foods and categories. Having all our schemas in just one file will always work but can become very difficult to maintain when you have more schemas.
It is recommended to describe each document type in a separate file and import them into schema.js like this:
// First, we must import the schema creatorimport createSchema from'part:@sanity/base/schema-creator'// Then import schema types from any plugins that might expose themimport schemaTypes from'all:part:@sanity/base/schema-type'import foods from"./foods";import category from"./category";// Then we give our schema to the builder and provide the result to SanityexportdefaultcreateSchema({// We name our schemaname:'default',// Then proceed to concatenate our document type// to the ones provided by any plugins that are installedtypes: schemaTypes.concat([
foods,
category
]),})
Looking deeper at the code above, we imported two files that export the schemas and called them in the types array. At this point, your directory will look like this:
One last thing will be for us to load our studio with content which we will be fetching into the app. This is totally up to you; you can get lovely images from Unsplash.
Connecting Sanity with React App
Let's allow our frontend to query for and receive data from Sanity.
Connecting React to Sanity
This is done by installing the sanity client package in our React project. Then run this command:
npm install @sanity/client @sanity/image-url
@sanity/client — Sanity Client is the official JavaScript client by Sanity and can be used both in Node.js and modern browsers.
@sanity/image-url — A helper library used to generate image URLs and perform helpful image transformations through the Sanity asset pipeline. Read more on the official docs here.
Once these packages have been installed, we'll create a new file named client.js inside the src directory and add the following code to the client.js file.
import sanityClient from"@sanity/client";exportdefaultsanityClient({projectId:"Your Project ID Here",// find this at manage.sanity.io or in your sanity.jsondataset:"production",// this is from those question during 'sanity init'});
Note: To get your project ID, visit https://www.sanity.io/manage, click on the project, so it displays full info about the project, including the Project ID.
Ensure you save the file.
Connecting Sanity to React
Finally, You will also need to add the port where the React development server is running to the CORS origins of your Sanity project. Visit https://www.sanity.io/manage and click on your Sanity project.
On your project's dashboard, click on Settings → API settings and then add http://localhost:3000/ to the CORS origins field if it was not added automatically.
Once you save, that will be all for our sanity studio; we can now start building the front end of our app to consume the data from the sanity studio.
Building a food catalog application
The first thing to handle would be routing, and this is usually handled in the App.js file. We'll be implementing this using react-router-dom and the Switch and Route components.
We will create a new folder in the /src directory to put all our routes in this app. We will have four routes:
views/Home.js - The main index is where we will list all the categories fetched from the Sanity studio.
views/Foods.js - This will contain a random list of all foods fetched from the Sanity studio.
views/About.js - This is the about page of the application.
views/FilteredFoods.js - This is an individual page containing foods related to a particular category.
Let's create the above files, and our file directory will now look like this:
Let's now handle routing in the App.js file. In this file, we will handle routing using React router. This enables the navigation among views of various components in a React Application.
The first thing will be to install the React Router package using the command below:
npm install react-router-dom --save
After installing react-router-dom, add its components (BrowserRouter, Route, Switch) to the App.js file. You can now proceed to define your routes.
In the code below, you will notice all the routes are wrapped in the Switch and BrowserRouter components. The Switch component is used to render only the first route that matches the location, rather than rendering all matching routes, and the BrowserRouter is a router implementation that uses the HTML5 history API to keep your UI in sync with the URL. It is the parent component that is used to store all the other components. Learn more about routing in react here.
You can confirm if the routes are working, visiting the paths. Now the next thing would be to work on the NavBar of our application.
Let's now create a folder and name its components in the /src directory to house all our reusable components. This would help organize our application. In this folder, we have about five different components, but let's start with the Header section of our application.
Building the Header and footer Section
The header section is going to house the application's logo and nav links, while the footer section will contain footer text. We mentioned earlier that React is all about components, so let's make some!
components/Header.js - This would be the overall container housing the Navbar.
In the code above, we have a div containing welcome information and a search bar, and then after that, we imported a component called categories from the components' folder.
To continue, you will have to create a component and name it Categories.js in the /components folder. This is where we will fetch all our food categories from Sanity, either using GROQ or GraphQL. For this guide, we will make use of GROQ.
Please paste the following code in the Categories.js file:
The above code might seem tricky since we are now fetching data from our Sanity, but I will explain. Let's first understand what GROQ is all about.
GROQ (Graph-Relational Object Queries) is a declarative language designed to query collections of largely schema-less JSON documents.
Gotcha
Note: If you're not yet familiar with GROQ which is used to query Sanity for data, check the official docs here.
Explaining the code...
The first thing we did was to import Sanity Client, which we installed earlier.
import sanityClient from "../Client";
In react, we make use of some hooks to query for data. For this guide, we will make use of useState() and useEffect(). The useState() is a Hook that allows you to have state variables in functional components, while useEffect() lets you perform side effects in function components.
To make use of these two hooks, you have to import them from react, and that's what we did in the first line alongside react, which is necessary.
import React, { useState, useEffect } from "react";
Note: This follows a common pattern where categories is where we access the current value of that particular piece of state, while setCategories is our method for setting or changing it.
To fetch data/information from Sanity studio, you can make use of GROQ, let's now explore our Groq Query:
This query is searching through your Sanity data store, or Content Lake, for a schema that has a _type of category (this is the name of the schema) and then gets the title, slug, description, e.t.c.
Reading further through our code, you'll notice that we are looping through our categories array and mapping each item to an instance of our Category component.
In the above code, we imported imageUrlBuilder from @sanity/image-url, which we use to generate image URLs for our images by creating a method called UrlFor() and making use of it inside the template. This helps us get thumbnail-sized images compared to the regular size of images uploaded to Sanity.
Gotcha
Note: A lot of things can be done with imageUrlBuilder, such as specifying width and height. You can read more about imageUrlBuilder here.
Once you save, you will notice the homepage/index page now looks like this, depending on the data you entered into your studio.
Display all foods on Food's page
Just as we were able to display all categories on the home page, we will also use the same approach to display all foods on the foods page. In the /views/Foods.js paste the code below:
In the code above, We are simply fetching all the foods from the foods schema of our sanity studio. Once you save, you will get something like this, depending on what you have in your sanity studio.
Finally, let's take a look at something very important; you will notice some links in the foods page and home page, which are supposed to take us to a dynamic route. Let's now see how it works.
Create dynamic routes for each category
To create a dynamic route, we will be making use of a component. We will be making use of the FilteredFoods.js page, and if you recall, when we were declaring routes, we added a slug to the route of this page
The code above can be quite tricky, but I will explain everything in plain words. The first thing we did was to import useParams which would enable us to get the slug
const{ slug }=useParams();
Once that is successful, we can now query our sanity studio, but this time around, the query is quite different. To get a feeling for queries in GROQ, you can check their cheat sheet here.
Once you save this code, you will now be able to access dynamic routes via the Link declared either in the home page or foods page
By now, the whole application should be working fine and fast without reloading the browser, we have not yet added any information to the About page, you can paste the code below, so we are sure everything is complete:
import React from"react";import foodsAboutImg from"../images/foods-abt-img.jpg";constAbout=()=>{return(<section className="container w-full lg:px-0 px-5 lg:w-3/4 mx-auto min-h-screen"><div className="mt-16"><h3 className="text-3xl font-bold text-center my-10 lg:my-5">
About Us🦻
</h3><div className="flex gap-10 justify-center items-center flex-col lg:flex-row mt-10"><div className=""><img
src={foodsAboutImg}
className="w-96 rounded-xl lg:rounded-l-xl"
alt=""/></div><div className="w-full lg:w-1/3 flex gap-5 mb-10 lg:mb-0 flex-col">
⭐⭐⭐
<p>A healthy diet rich in fruits, vegetables, whole grains and
low-fat dairy can help to reduce your risk of heart disease by
maintaining blood pressure and cholesterol levels. High blood
pressure and cholesterol can be a symptom of too much salt and
saturated fats in your diet.</p><p>
Many healthful foods, including vegetables, fruits, and beans, are
lower in calories than most processed foods.</p><p>
Children learn most health-related behaviors from the adults
around them, and parents who model healthful eating and exercise
habits tend to pass these on.</p>
⭐⭐⭐
</div></div></div></section>);};exportdefault About;
This might throw an error due to the image imported. Feel free to use another image or get the image from this GitHub repository.
Congratulations! We now have our application running smoothly offline. You can decide to deploy your application online so that others can access it easily.
Conclusion
In this guide, we built a single-page application with React, Tailwind CSS, and Sanity. This guide will help you set up your own version of this project. You can also improve on it by adding/implementing certain features and functionalities.
Here are a few ideas to get you started:
Add Search functionality to the search field, making use of foods.
Style the app using UI libraries like Vuetify, BootstrapVue, or standard CSS.
Make certain fields of the schema required and play with other field types.
Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.
This article explains what Sanity is all about and how to build a simple books list app. Sanity CMS stores our content, which will be editable in Sanity Studio. We will make this app with Nuxt and style it using pure CSS.
How to Create a Progressive Web Application with Vue and Sanity
Studio v2
In this article, you will learn how to build a simple web application with Vue.js, query your data from Sanity, and make it a Progressive Web Application (PWA) by installing the PWA plugin. In making it a PWA, the app will be able to be installed on mobile phones.