Sept 25th
Front-End Engineering in 2024 & Mastering React
Oct 2nd
Building Large Scale Web Apps
Oct 9th
Building a Personal Brand & Breaking Into FAANG
Oct 16th
Leveling Up From Junior To Senior To Staff
- State Management
- Personalization & A/B Testing
- Deployment + Monitoring
- Design Systems & Component Libraries
Adeyemi
Maliha
today's folks
Asmaa
Emad
Satya
Richard
Q/A!
Any questions before we get started?
<form>
<label for="name">Name:</label>
<input type="text" id="name" name="name" />
<label for="email">Email:</label>
<input type="email" id="email" name="email" />
<input type="submit" value="Submit" />
</form>HTML
form {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
background: #f9f9f9;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
/* ... */CSS
import React from 'react';
import './ContactForm.css';
function ContactForm() {
return (
<form>
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" />
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" />
<input type="submit" value="Submit" />
</form>
);
}
export default ContactForm;
form {
/* ...styles from your initial CSS... */
}
label {
/* ...styles from your initial CSS... */
}
/* Additional styles if needed */
./ContactForm.css
import React from "react";
import styles from "./ContactForm.module.css";
function ContactForm() {
return (
<form className={styles.form}>
<label htmlFor="name" className={styles.label}>
Name:
</label>
<input
type="text"
id="name"
name="name"
className={styles.input}
/>
<label htmlFor="email" className={styles.label}>
Email:
</label>
<input
type="email"
id="email"
name="email"
className={styles.input}
/>
<input
type="submit"
value="Submit"
className={styles.button}
/>
</form>
);
}
export default ContactForm;.form {
/* ...styles from your initial CSS... */
}
.label {
/* ...styles from your initial CSS... */
}
.input {
/* Input-specific styles */
}
.button {
/* Button-specific styles */
}
CSS Modules
./ContactForm.module.css
import React from 'react';
const Button = ({ label, onClick, variant = 'primary' }) => {
const baseStyles = 'py-2 px-4 font-semibold rounded';
const variantStyles = {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
danger: 'bg-red-500 text-white hover:bg-red-600',
};
return (
<button
className={`${baseStyles} ${variantStyles[variant]}`}
onClick={onClick}
>
{label}
</button>
);
};
export default Button;
import React from 'react';
import Button from './Button';
function App() {
return (
<div className="flex justify-center items-center h-screen">
<Button label="Primary Button" onClick={() => alert('Clicked!')} />
<Button
label="Secondary Button"
onClick={() => alert('Secondary clicked!')}
variant="secondary"
/>
<Button
label="Danger Button"
onClick={() => alert('Danger clicked!')}
variant="danger"
/>
</div>
);
}
export default App;
import * as React from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
export function CardWithForm() {
return (
<Card>
<CardHeader>
<CardTitle>Create project</CardTitle>
<CardDescription>Deploy your new project in one-click.</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="name">Name</Label>
<Input id="name" placeholder="Name of your project" />
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="framework">Framework</Label>
// ...
</div>
</div>
</form>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">Cancel</Button>
<Button>Deploy</Button>
</CardFooter>
</Card>
)
}and so many more...
What do companies like Doordash, Shopify, Meta, etc. do for their React apps?
They often develop their own comprehensive component libraries and design systems for their React apps to ensure consistent UI/UX, improve developer efficiency, and maintain scalability across their platforms.
They develop their own to to address unique business needs and enforce brand consistency.
and a lot more...
Component libraries are just one aspect of an overall Design System
A Design System is a collection of reusable components, guidelines, and assets that help teams build cohesive products.
Component libraries
Design Tokens
Typography Guidelines
Branding
Documentation
Iconography
Collaboration Tools (e.g. Figma)
etc.
Front-End Engineers
Designers
Product
Mobile (iOS)
Mobile (Android)
Web
Q/A!
and a small 5 min break...
import React, {
useState,
createContext,
useContext,
} from "react";
// Create a context
const MessageContext = createContext();
function App() {
const [message, setMessage] = useState(
"Hello World!",
);
return (
// Provide the state to nested components
<MessageContext.Provider
value={{ message, setMessage }}
>
<Child1>
<Child2>
<Child3>
<DeeplyNestedChild />
</Child3>
</Child2>
</Child1>
</MessageContext.Provider>
);
}function DeeplyNestedChild() {
/*
Use the data directly without receiving it
as a prop
*/
const { message, setMessage } = useContext(
MessageContext,
);
return (
<div>
<h1>{message}</h1>
<button
onClick={() =>
setMessage(
"Hello from nested component!",
)
}
>
Change Message
</button>
</div>
);
}import React, { useState } from "react";
function App() {
const [message, setMessage] = useState("Hello World!");
return (
<div>
<h1>{message}</h1>
<button
onClick={() => setMessage("New Message!")}
>
Change Message
</button>
</div>
);
}
export default App;
import React, { useReducer } from "react";
const initialState = {
message: "Hello World!",
};
function reducer(state, action) {
switch (action.type) {
case "CHANGE_MESSAGE":
return {
...state,
message: action.payload,
};
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(
reducer,
initialState,
);
return (
<div>
<h1>{state.message}</h1>
<button
onClick={() =>
dispatch({
type: "CHANGE_MESSAGE",
payload: "New Message!",
})
}
>
Change Message
</button>
</div>
);
}The additional steps in useReducer are helpful for separating the logic of how state is updated from the UI components.
This makes the management of data flow clearer and more predictable. With useReducer, state updates have descriptive action types, which help make it easier to trace where and how state changes occur.
import {
createSlice,
configureStore,
} from "@reduxjs/toolkit";
const initialState = {
message: "Hello World!",
};
export const messageSlice = createSlice({
name: "message",
initialState,
reducers: {
changeMessage: (state, action) => {
state.message = action.payload;
},
},
});
export const store = configureStore({
reducer: messageSlice.reducer,
});import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import { App } from "./App";
import { store } from "./store";
const rootElement =
document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<Provider store={store}>
<App />
</Provider>
);import React from "react";
import {
useSelector,
useDispatch,
} from "react-redux";
import { messageSlice } from "./store";
export function App() {
const message = useSelector(
(state) => state.message,
);
const dispatch = useDispatch();
const handleChangeMessage = () => {
dispatch(
messageSlice.actions.changeMessage(
"New Redux Message!",
),
);
};
return (
<div>
<h1>{message}</h1>
<button onClick={handleChangeMessage}>
Change Message
</button>
</div>
);
}
export default App;- First, start with data-fetching considerations
- Next, gauge the necessity for more robust custom state management solutions.
- Evaluate the merit of simpler state management tools.
- Finally, always keep component state at the component level
Q/A!
Personalization refers to the practice of creating tailored experiences for individual users or groups based on their preferences, behavior, or other identifiable attributes.
A website personalization example from Amazon; product recommendations based on customer data and intent
A website personalization example from Netflix; viewing recommendations based on customer data and reviews
import React from "react";
// Create UserPreferences context
const UserContext = React.createContext({});
export const UserProvider = ({ children }) => {
/*
get user preferences information from
external service/API
*/
const userPreferences = getUserPreferences();
return (
<UserContext.Provider
value={{ userPreferences }}
>
{children}
</UserContext.Provider>
);
};Using context to store user preference info
import { UserProvider } from './UserContext';
function App() {
return (
<UserProvider>
{/* other components */}
</UserProvider>
);
}
export default App;A/B testing, also known as split testing or controlled experiments, is the method of comparing two or more versions of a web page, feature, or product against each other to determine which one performs better.
Segmenting users
Serving different versions
Collecting data
Analyzing experiment results
Button Color Test
Hypothesis: Changing the color of a certain button to green will significantly increase the click-through rate when compared to the existing button color blue.
Objective: Determine which button color leads to higher click-through rates.
Setup: Create two variations of a button, one blue and one green. Serve one variation to 50% of all users and the other variation to the remaining 50% of users.
Data collection: Track button clicks for both variations using Statsig’s built-in event tracking.
Analysis: Compare the click-through rate of both variations to determine which color is more effective.
import { StatsigProvider } from "statsig-react";
function App() {
return (
<StatsigProvider
sdkKey="<STATSIG_CLIENT_SDK_KEY>"
waitForInitialization={true}
>
<div className="App">
{/* Rest of App ... */}
</div>
</StatsigProvider>
);
}
export default Appimport { useExperiment } from "statsig-react";
function ButtonComponent() {
// access experiment configuration
const { config } = useExperiment(
"button_color",
);
// access value of experiment parameter
const showGreenButton = config.get(
"enable_feature",
);
return (
// ...
)
}
export default ButtonComponentimport {
Statsig,
useExperiment,
} from "statsig-react";
function ButtonComponent() {
const { config } = useExperiment(
"button_color",
);
const showGreenButton = config.get(
"enable_feature",
);
const buttonColor = showGreenButton
? "green"
: "blue";
const onButtonClick = () => {
// log button click
Statsig.logEvent(
"button_click",
buttonColor,
);
};
return (
<button
style={{ backgroundColor: buttonColor }}
onClick={onButtonClick}
>
Click Me
</button>
);
}
In a large-scale web app, when we launch a new feature to users — there can be many instances where we may not want to launch the feature to 100% of all users right out of the box.
Mitigate risk
Collect user feedback
Test the market
Ensuring a rollback plan
import { useGate } from "statsig-react";
function ButtonComponent() {
// Evaluate the feature flag
const { value: isFeatureEnabled } = useGate(
"new_button_color",
);
// Decide which button color to use
const buttonColor = isFeatureEnabled
? "green"
: "blue";
return (
<button
style={{ backgroundColor: buttonColor }}
>
Click Me
</button>
);
}
export default ButtonComponent;Q/A!
Deployment — getting our code from development to production (i.e., to our users).
npm run build
Static site
Client (React) app
Server app
Database
AWS/Digital Ocean/Heroku/etc.
Host Postgres/MongoDB
Client (React) app
Server app
Can be deployed as a single server that outputs both the React client and handles backend server-side logic
isomorphic/universal app
const express = require('express');
const path = require('path');
const app = express();
// Serve static files from the React app
app.use(express.static(path.join(__dirname, 'build')));
// API routes
app.get('/api/data', (req, res) => {
res.json({ data: 'Some API data' });
});
// Serve the React app for all other routes
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
How can we ensure our app runs consistently across different environments?
How do we handle dependencies, configurations, and scalability in a seamless way?
Docker allows us to package our entire React app, its environment, and dependencies into a container. This ensures that it runs consistently across different environments.
# Step 1: Use an official Node.js image as the base image
FROM node:16-alpine
# Step 2: Set the working directory inside the container
WORKDIR /usr/src/app
# Step 3: Copy package.json and package-lock.json to the container
COPY package*.json ./
# Step 4: Install the app dependencies
RUN npm install
# Step 5: Copy the rest of the app's source code to the container
COPY . .
# Step 6: Build the React app for production
RUN npm run build
# Step 7: Use a lightweight web server (nginx) to serve the built app
FROM nginx:alpine
# Step 8: Copy the build output from the previous step to the nginx HTML directory
COPY --from=0 /usr/src/app/build /usr/share/nginx/html
# Step 9: Expose port 80 to allow external traffic to access the app
EXPOSE 80
# Step 10: Start the nginx server
CMD ["nginx", "-g", "daemon off;"]
When using cloud tools like AWS/GCP, we can provision a Virtual Machine or managed service to run a Docker Container.
Continuous Integration (CI), is the practice of automatically merging code changes from multiple developers into a single software project immediately after the changes are made.
Linting/Code Formatting
Unit/Integration/E2E Tests
TypeScript Types
Accessibility Testing
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout the code
- name: Checkout repository
uses: actions/checkout@v2
# Step 2: Set up Node.js (using Node.js 16)
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
# Step 3: Install dependencies
- name: Install dependencies
run: npm install
# Step 4: Run Linting and Code Formatting (ESLint and Prettier)
- name: Lint and Format Code
run: |
npm run lint
npm run format:check
# Step 5: Run TypeScript Type Checking
- name: TypeScript Type Check
run: npm run type-check
# Step 6: Run Unit Tests
- name: Run Unit Tests
run: npm run test
# Step 7: Run Integration and E2E Tests (using Cypress or Jest)
- name: Run Integration and E2E Tests
run: npm run test:e2e
# Step 8: Run Accessibility Tests (example using axe-core or cypress-axe)
- name: Run Accessibility Tests
run: npm run test:accessibility
# Step 9: Build the React app for production (optional)
- name: Build for Production
run: npm run build
Monitor Requests
Success/Errors
Monitor JS UI Errors
P99: The 99th percentile latency value means that 99% of the requests have a latency equal to or less than this value.
In simpler terms, it represents the worst-case latency for the fastest 99% of the requests. Only 1% of the requests take longer than this time.
P50: The 50th percentile latency value means that 50% of the requests have a latency equal to or less than this value.
The median latency
P50
P90
P95
P99
Logs
poetry add ddtrace
poetry installfrom ddtrace import tracer
from ddtrace.contrib.flask import TraceMiddleware
from flask import Flask
app = Flask(__name__)
traced_app = TraceMiddleware(app, tracer, service="my-service", distributed_tracing=True)
Run the python service with instrumentation enabled
curl -X GET 'localhost:8080/notes'
{}
curl -X POST 'localhost:8080/notes?desc=hello'
( 1, hello)
curl -X GET 'localhost:8080/notes?id=1'
( 1, hello)
curl -X POST 'localhost:8080/notes?desc=newNote'
( 2, newNote)
curl -X GET 'localhost:8080/notes'
{ "1": "hello", "2": "newNote" }Custom metrics
from datadog import initialize, api
# Set your Datadog API key
options = {
'api_key': '<YOUR_API_KEY>',
'app_key': '<YOUR_APP_KEY>' # Optional, only needed for specific API endpoints
}
# Initialize the Datadog client
initialize(**options)
# Send a custom metric
try:
api.Metric.send(
metric='custom.metric',
points=42, # Value for the metric
tags=['env:production'], # Tags associated with the metric
type='gauge' # Optional, 'gauge' is the default type
)
print("Custom metric sent successfully!")
except Exception as e:
print(f"Error sending custom metric: {e}")
Monitor JS UI Errors
<html>
<head>
<title>Example to send logs to Datadog</title>
<script type="text/javascript" src="https://www.datadoghq-browser-agent.com/datadog-logs-us.js"></script>
<script>
// Set your client token
DD_LOGS.init({
clientToken: '<CLIENT_TOKEN>',
forwardErrorsToLogs: true,
});
// optionally add global metadata attribute--one attribute can be added at a time
DD_LOGS.addLoggerGlobalContext('request_id', <REQUEST_ID>);
</script>
[ … ]
</head>
[ … ]
</html>Automatically collect JavaScript error logs and console logs
DD_LOGS.logger.info('signup button clicked', { env: 'dev', user_id: <USER_ID> })
Also send custom logs
Set up Application Performance Monitoring (APM)
Monitor Error Rates
Track Latency Percentiles (P99, P95, P50)
Capture Client-Side JavaScript Errors
Set Alerts + Downtime Alerts + ping the team/on-call
#1 Try out a design/component library!
Take-home coding exercises
#2 Interact with Context/useReducer
#3 Look into CI/CD OR Datadog
- Building a Personal Brand & Breaking Into FAANG
What's coming next week?