React

React is one of the most popular JavaScript framework in 2024. Learn how to build a Cloud Phone widget using React, or skip ahead to the finished sample code and live demo hosted on GitHub Pages.
Need a primer on Cloud Phone?
Getting started with Cloud Phone
Learn how Cloud Phone brings compelling web apps to the next billion users
Setup
Prerequisites
- Node & npm
- Git
- A GitHub account
Dependency Versions
This sample was developed using the following libraries and frameworks.
Dependency | Version |
---|---|
Node | v20.11.0 (LTS) |
npm | 10.9.0 |
React | v19 |
react-router | 7 |
i18next | 24 |
Vite | 6 |
Getting Started with Vite
Use Vite to scaffold a new React project.
npm create vite@latest cloudphone-react-sample -- --template react
You can also use .
for the project name to scaffold in the current directory.
Then follow the prompts to install dependencies into the node_modules/
directory.
Scaffolding project in ./cloudphone-react-sample...
Done. Now run:
cd cloudphone-react-sample
npm install
npm run dev
The command npm run dev
is aliased in the scripts
section of the package.json file to run vite
. This command starts a local HTTP server on port 5173 (by default), available at http://localhost:5173/
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
}
Development
Routing
React Router by Shopify is one of the most popular packages for routing React apps. It supports both single-page apps (SPAs) as a library, as well as full-stack development and deployment including static and server-side rendering (SSR) as a framework.
Since Cloud Phone is already rendered on powerful servers, server-side rendering (SSR) is redundant and unnecessary.
In this demo, React Router is used as a library. Install react-router
using NPM:
npm install react-router
Then in main.jsx
, wrap the <App />
element with <HashRouter>
.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { HashRouter } from "react-router";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<HashRouter>
<App />
</HashRouter>
</StrictMode>
);
The default <BrowserRouter>
does not work with GitHub Pages but can still be used for self-hosted widgets. For GitHub Pages, use <HashRouter>
instead.
Internationalization
react-i18next
is a popular package for handling internationalization (abbreviated i18n) in React apps.
npm install react-i18next i18next i18next-browser-languagedetector --save
For this demo, store translations as JSON files located at src/assets/locales/<language_code>/translation.json
. Define a configuration like i18n.js
below.
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import translationEN from "../assets/locales/en/translation.json";
import translationES from "../assets/locales/es/translation.json";
// Translations
const resources = {
en: {
translation: translationEN
},
es: {
translation: translationES
}
};
i18n.use(LanguageDetector).use(initReactI18next).init({
resources,
fallbackLng: "en"
});
export default i18n;
The demo includes two translations–English and Spanish–and uses the LanguageDetector
package to automatically choose the default language based on the browser language.
Keyboard Navigation
Cloud Phones have directional pads (D-pads) to support keyboard navigation. Unlike desktop that uses the tabindex
property and Tab (⇥) key to the next focusable element, Cloud Phone uses Arrow keys (↑↓←→).
![]() | ![]() |
---|
Autofocus on the first element in a page, list, or grid to set the user’s bearings. Use the autoFocus
prop or a callback ref like below.
const autoFocus = (element) => element?.focus();
function AutofocusedLink({ url, text }) {
return (
<a href={url} autoFocus ref={autoFocus}>
{text}
</a>
);
}
export default AutofocusedLink;
Extend the callback ref approach to autofocus on the first item in a list using this example from Tiger Oakes.
// CSS selector to find focusable elements
const FOCUSABLE_CSS_SELECTOR = `a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), *[tabindex], *[contenteditable]`;
// Find the first focusable element in a container (i.e. list)
function findFirstFocusable(container) {
return container.querySelector(FOCUSABLE_CSS_SELECTOR);
}
// Auto-focus the first focusable item in element when its mounted
function autoFocusFirstFocusable(element) {
if (element) {
findFirstFocusable(element)?.focus();
}
}
This technique is used in the OptionsMenu
component. Here is a simplified example:
import React, { useEffect, useRef, useState } from "react";
import { autoFocusFirstFocusable } from "../utils/focus";
function OptionsMenu({ children = [] }) {
const menuRef = useRef(null);
const [focusedIndex, setFocusedIndex] = useState(0);
// Autofocus on the first element in the list
useEffect(() => autoFocusFirstFocusable(menuRef?.current), [menuRef]);
return (
<>
<menu ref={menuRef} role="menu">
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
role: "menuitem",
className: index === focusedIndex ? "focused" : ""
})
)}
</menu>
</>
);
}
export default OptionsMenu;
The OptionsMenu
component can have children of any type, including React Router’s <Link>
component or standard the HTML anchor <a>
tag, to create a navigation menu triggered by the Left Soft Key (LSK)
const [menuVisible, setMenuVisible] = useState(false);
const onSoftKeyClick = (position) => {
// Toggle menu visibility with Escape (␛)
if (position === 'start')
setMenuVisible(!menuVisible);
}
return (
<OptionsMenu visible={menuVisible}>
<Link to="about" replace>{t('About')}</Link>
<Link to="settings" replace>{t('Settings')}</Link>
<a href="https://www.cloudphone.tech/dev-privacy" target="_self">{t('Privacy')}</a>
</OptionsMenu>
<SoftKeyBar
buttons = {{
start: { icon: 'menu' },
center: { icon: 'select' },
end: { icon: 'back' },
}}
onSoftKeyClick={onSoftKeyClick} />
);
Hosting on GitHub Pages
GitHub Pages provides free hosting for public, open-source repositories. This demo uses the github-pages-deploy-action GitHub Action to build and deploy HTML, CSS, and JS to the gh-pages
branch. The action is defined in pages.yml
.
name: Deploy Pages
on:
push:
branches:
- main
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: "Checkout 🛎️"
uses: actions/checkout@v2
- name: "Setup Node 🚧"
uses: actions/setup-node@v4
with:
node-version: ">=20.11.0"
- name: "Dependencies 🏗️"
run: |
npm ci
- name: "Build 🔧"
run: |
npm run build
- name: "Deploy 🚀"
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist
Wrap Up
Tips
Consider these tips if you are starting a React project from scratch.
- Use an accessible component library like Material UI (MUI) or Chakra UI
- Lazy-load translations dynamically over HTTPS with
i18next-http-backend
- Reduce package size and page load time by switching to Preact
- Develop reliably with testing utilities like Enzyme
- Manage global state predictably with Redux
Next Steps
Register for the Cloud Phone Developer Program to try the live demo on the Cloud Phone Simulator or a physical device. Fork us on GitHub to start coding your first Cloud Phone widget using React and set up a custom domain name to point to GitHub Pages.