• Basic building block of React app are components
  • Component is a piece of UI with its own appearance and logic
  • Components are functions that return markup
  • Can nest components within other components
  • Main component in file uses export default function
  • Markup syntax used is JSX
  • JSX is stricter than HTML as it must close tags, must start with a capital letter
  • Component must only return one JSX tag, so must nest or wrap with empty <>...</> wrapper
  • CSS class is className attribute in JSX
  • Use curly braces to use JS inside JSX
  • Can also use JS for attributes but instead of double quotes use curly braces
  • Conditional rendering
let content;
if (isLoggedIn) { 
	content = <AdminPanel />;
} else {  
	content = <LoginForm />;
}
 
return (<div>content}</div>);
<div>  
	{isLoggedIn ? (<AdminPanel />) : (<LoginForm />  )}  
</div>
<div>  
	{isLoggedIn && <AdminPanel />}  
</div>
  • Render lists using for loop and map function
    • Each item needs key attribute so React knows when add, remove, reorder item
const listItems = products.map(product =>  
	<li key={product.id}>  
		{product.title}  
	</li>  
);  
 
return (
	<ul>{listItems}</ul>  
);
  • Responding to events
    • onClick={fnName}
    • Do not call function inside
  • State inside component
    • import { useState } from 'react';
    • Declare state variable (const [count, setCount] = useState(0);)
    • useState returns current state and a function for you to update the state of that variable
    • Each component gets its own state
  • Hooks
    • Functions that start with use
    • Built-in hook is useState
    • Can build your own by combining existing ones
  • Sharing state
    • State should go in top most component that it wants to share state with
    • Parent component passes it down to child components via props (state and event handler)
    • Child reads prop
function MyButton({ count, onClick }) {  
	return (  
		<button onClick={onClick}>  
			Clicked {count} times  
		</button>  
	);  
}
  • https://react.dev/reference/react-dom/components/common
  • JSX turned into JS objects so majority of attributes need to be in camel case and not use reserved keywords
  • Inline style properties written in camel case
  • Can set default value for prop
  • children prop is what is passed to parent component when children are between its opening and closing tags
  • Components are pure
    • It shouldn’t change any objects or variables that existed before rendering
    • Same input, same output
  • Components shouldn’t depend on each others rendering sequence since rendering can happen at any time
  • Do not mutate any inputs (props, state, context)
    • Use setState instead
  • Use event handlers. As a last resort use useEffect
  • Render tree only contains React components to remain platform agnostic
  • Module dependency tree shows imports of modules
    • May include modules of components, functions, constants

Interactivity

  • state: data that changes over time
  • Event handlers
    • Functions that will be triggered in response to user events / interactions
    • Usually defined inside your components
    • Have name that start with handle, followed by the name of the event
  • onClick={handleClick} pass function as a prop
    • handleClick is an event handler
  • onClick={() => { alert('You clicked me'); }}
    • Inline event handlers convenient for short functions
  • Do not call functions passed to event handlers
    • It will fire the function immediately during rendering, without any interactions
    • This happens because JS inside curly braces executes right away
  • By convention, event handler props should start with on followed by a capital letter
  • Events propagate in React except onScroll
  • Event handlers receive an event object as their only argument
  • To stop propagation, e.stopPropagation()
  • onClickCapture={() => { runs first }}
    • Catches all events on child elements, even if they stopped propagation
    • Each event propagates in 3 phases:
      • Travels down, calling all onClickCapture handlers
      • Runs clicked element’s onClick handler
      • Travels upwards, calling all onClick handlers
    • Useful for code like routers or analytics
  • Can pass handlers as an alternative to propagation
    • Add more code to be called before calling handler passed down from parent
    • Benefit is that you can clearly follow whole chain of code that executes as a result of some event
  • e.preventDefault()
    • Some browser events have default behaviour associated with them (e.g. form submit event)
  • Event handlers don’t need to be pure so can change something

State

  • Local variables don’t persist between renders

  • Changes to local variables won’t trigger renders

  • useState provides a state variable to retain data between renders and a state setter function to update variable and trigger React re-render

  • import { useState } from 'react';

  • const [index, setIndex] = useState(0);

  • Functions that start with use is a Hook

    • Special functions that are only available while React is rendering
    • Can only be called at top level of components or your custom Hooks
      • Cannot be called inside conditions, loops, or other nested functions
  • Isolated and private for each component

  • Render triggered

    • Initial render
      • createRoot with target DOM node, then call render method with component
      • const root = createRoot(document.getElementById('root));
      • root.render(<Image/>)
    • State updated so re-render
      • Render gets queued
  • React renders components

    • React calls components to figure out what to display on screen
    • Calls root component on initial render
      • DOM nodes created
    • Calls component on re-renders
      • Calculate properties changes since previous render
  • React commits changes to DOM

    • appendChild() DOM API to place all rendered nodes on screen on initial render
    • React will execute minimum necessary operations to update screen with new values
    • Browser painting
  • State value never changes during render

  • Updating same state multiple times before next render

    • Pass in updater function to setState… parameter is the current value of that state variable
    • setNumber(n => n + 1)

Managing State

  • Imperative UI
    • Giving step by step instructions on how the UI should be updated
    • Manually update DOM based on user interactions or data changes
    • Vanilla JS, jQuery
  • Declarative UI
    • Describes what the UI should show
    • State management handled by framework
    • Step 1: Identify component’s different visual states
      • For a form, different states might include empty, typing, submitting, success, and error
      • Create mocks for different states before adding logic
    • Step 2: Determine what triggers those state changes
      • Computer inputs, user inputs
    • Step 3: Represent state in memory using useState
    • Step 4: Remove any non-essential state variables
    • Step 5: Connect event handlers to set state
  • Principles for structuring state
    • Group related state
      • If certain values are usually updated at the same time, think about merging them into a single state variable
    • Avoid contradictions in state
      • Causes confusion and leaves room for mistakes
      • Example is a form with isSending and isSent state
      • Leaves room for “impossible” states like having both set to true at the same time
      • Better to have a single status state variable
    • Avoid redundant state
      • If can calculate new state from existing props or state variables, don’t need a new state variable for this
      • Example is state with firstName, lastName, and fullName… can calculate fullName from firstName and lastName during render so it is redundant
    • Avoid duplication in state
      • Difficult to keep them in sync
      • Example is having an array of products in state and also a selectedItem in state that holds the selected product (same as in the products array). If item is editable, updates may only go in the object inside the array and we forget to update selectedItem
        • Better to change to use selectedId
    • Avoid deeply nested state
      • Not convenient to update
  • “controlled” (driven by props) or “uncontrolled” (driven by state)
  • React preserves a component’s state for as long as it’s being rendered at its position in the UI tree
  • if you want to preserve the state between re-renders, the structure of your tree needs to “match up” from one render to another. If the structure is different, the state gets destroyed because React destroys state when it removes a component from the tree.
  • You can use keys to make React distinguish between any components.

Reducers

  • Can place all state update logic outside of component into a reducer function
    • Good if components have many state updates spread across many event handlers
  • Migrate from useState to useReducer
    • Step 1: Move from setting state to dispatching actions
      • Specifies what the user just did by dispatching actions from event handlers in component
      • State update logic lives in reducer
      • dispatch({ type: 'added', id: nextId++, text })
      • Object passed to dispatch is an “action”
    • Step 2: Write reducer function
      • Function where state logic goes
      • Takes two arguments: current state and action object
      • Returns next state
    • Step 3: Use reducer from component
      • Import useReducer Hook from 'react'
      • Replace useState with useReducer
        • const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);

Context

  • allows parent component to make information available to components below it (children and grandchildren) without passing via props
  • “prop drilling”
    • lifting state high through many layers
  • Step 1: Create a context
    • import { createContext } from 'react';
    • export const LevelContext = createContext(1); // argument is the default value
  • Step 2: Use that context from the component that needs the data
    • import { useContext } from 'react';
    • import { LevelContext } from './LevelContext.js';
    • const level = useContext(LevelContext);
      • useContext is a Hook
      • tells React that this component wants to read the LevelContext
  • Step 3: Provide that context from the component that specifies the data
    • Wrap children with context provider to provide context to them
    • import { LevelContext } from './LevelContext.js';
    • <LevelContext.Provider value={level}>{children}</LevelContext.Provider>
  • Context lets you write components that adapt to their surroundings and display themselves differently depending on where they are being rendered
  • It’s like CSS property inheritance… the only way to override some context coming from above is to wrap children into a context provider with a different value
  • Different React contexts don’t override each other
  • Before using context
    • Start by passing props
    • Extract components and pass JSX as children to them
      • <Layout posts={posts}/> to <Layout><Posts posts={posts}/></Layout>
  • Use cases
    • Theming
    • Current account
    • Routing
    • Managing state

Combining reducers and context to manage state

  • State and dispatch function / event handlers may only be available in the top-level component
    • If child components need these, would need to prop them down as props
    • Bad if many children below to get to child component that needs these values
    • Instead, place the state and dispatch function into context so any component below top-level component can read the state and dispatch actions without prop drilling
  • Step 1: Create the context
    • Create separate context for state variable and dispatch function
    • export const TasksContext = createContext(null);
    • export const TasksDispatchContext = createContext(null);
  • Step 2: Put state and dispatch into context
    • Import both contexts into parent component, take the state and dispatch function returned by useReducer() and provide them to entire tree below
    • const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
    • <TasksContext.Provider value={tasks}><TasksDispatchContext.Provider value={dispatch}>...</TasksDispatchContext.Provider></TasksContext.Provider>
  • Step 3: Use context anywhere in the tree
    • Any component that needs task list can read from TasksContext
      • const tasks = useContext(TasksContext);
    • To update task list, any component can read dispatch function from context and call it
      • const dispatch = useContext(TasksDispatchContext);
      • dispatch({ type: 'added', id: nextId++, text });
  • Can also move reducer code and context code into a single file
    • Create a new TasksProvider component that will manage state with reducer, provide both contexts to components below, and take children as a prop so you can pass JSX to it
export function TasksProvider({ children }) {  
	const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);  
	
	return (  
		<TasksContext.Provider value={tasks}>  
			<TasksDispatchContext.Provider value={dispatch}>  
				{children}  
			</TasksDispatchContext.Provider>  
		</TasksContext.Provider>
	);  
}
 
// Custom Hooks
export function useTasks() {
  return useContext(TasksContext);
}
 
export function useTasksDispatch() {
  return useContext(TasksDispatchContext);
}

Escape Hatches

Referencing Values with Refs

  • Used when you want a component to remember some info but don’t want it to trigger new renders
  • import { useRef } from 'react';
  • const ref = useRef(0);
    • Pass initial value you want to reference
    • Returns an object with current value ({ current: 0 })
      • Mutable (can read and write to it), React doesn’t track this
  • When piece of info only needed by event handlers and changing it doesn’t require re-render, use ref instead of state
  • Examples of when to use refs:
    • Storing timeout IDs
    • Storing and manipulating DOM elements
    • Storing other objects that aren’t necessary to calculate the JSX

Manipulating DOM with refs

  • Sometimes might need to access DOM elements managed by React (focus node, scroll to node, measure size and position)
  • Pass ref as ref attribute to JSX tag for which you want to get the DOM node
  • Can access DOM node from event handlers and use built-in browser APIs defined on it
  • Managing a list of refs
    • Could get a single ref to parent element then use DOM manipulation to “find” the child nodes of interest but easily breakable if DOM changes
    • Callback passed to ref attribute
      • Function is called with DOM node when it’s time to set the ref, and with null when it’s time to clear it
      • Maintain an array or a Map and access any ref by its index or an ID
  • Can access another component’s DOM nodes by passing refs from parent to child components but can make code fragile
  • useImperativeHandle
    • Restrict exposed functionality
    • Instructs React to provide your own special object as the value of a ref to the parent component
function MyInput({ ref }) {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    // Only expose focus and nothing else
    focus() {
      realInputRef.current.focus();
    },
  }));
  return <input ref={realInputRef} />;
};
  • React sets ref.current during the commit
    • Before updating DOM, React sets affected ref.current to null
    • After updating DOM, React sets them to the corresponding DOM nodes
  • Force React to update/flush the DOM synchronously
    • import { flushSync } from 'react-dom';
    • Wrap state in call flushSync(() => { setTodos([...todos, newTodo]);
    • Then call listRef.current.lastChild.scrollIntoView();
  • Avoid changing DOM nodes managed by React because it can lead to inconsistent visual results or crashes

Effects

  • Run code after rendering so you can sync component with some system outside React

  • Examples include connecting to a server or use of a third-party library

  • Run at the end of a commit after screen changes

  • Writing an effect

    • Step 1: Declare an effect
      • import { useEffect } from 'react';
      • useEffect(() => { ..code here will come after every render.. });
    • Step 2: Specify Effect dependencies
      • Array of dependencies as second argument to useEffect
      • Skip running if anything not in array is the same as it was during previous render
      • Empty array as dependencies will only run useEffect on mount / when component appears
      • ref and set functions returned by useState have stable identity so they’re usually omitted from dependencies
    • Step 3: Add cleanup if needed
      • Clean up function returned from function inside useEffect
      • return () => { connection.disconnect(); };
      • Clean up function is called each time before the Effect runs again and one final time when the component unmounts / gets removed
  • Controlling non-React widgets

    • Example: add map component to page with setZoomLevel method. Want to keep zoom level in sync with zoomLevel state variable in React code

      • Effect is called twice but not a problem because setting same value twice doesn’t do anything though it may be slightly slower
      useEffect(() => {
      	const map = mapRef.current;
      	map.setZoomLevel(zoomLevel);
      }, [zoomLevel])
    • Example: showModal method of built-in <dialog> element throws an error if call it twice. Need cleanup function to close dialog

      useEffect(() => {
      	const dialog = dialogRef.current;
      	dialog.showModal();
      	return () => dialog.close();
      }, []);
  • Subscribing to events

    useEffect(() => {  
    	function handleScroll(e) {
    		console.log(window.scrollX, window.scrollY);  
    	}  
     
    	window.addEventListener('scroll', handleScroll);  
    	
    	return () => window.removeEventListener('scroll', handleScroll);  
    }, []);
  • Triggering animations

useEffect(() => {
	const node = ref.current;  
	node.style.opacity = 1; // Trigger the animation  
	
	return () => {  
		node.style.opacity = 0; // Reset to the initial value  
	};  
}, []);
  • Fetching data

    useEffect(() => {  
    	let ignore = false;  
    	
    	async function startFetching() {  
    		const json = await fetchTodos(userId);  
    		
    		if (!ignore) {  
    			setTodos(json);  
    		}  
    	}  
    	
    	startFetching();  
    	
    	return () => {  
    		ignore = true;  
    	};  
    }, [userId]);
    • Fetching data inside effects is popular way but its very manual and has many downsides
      • Effects don’t run on the server. Initial server-rendered HTML will only include a loading state with no data. Client computer will have to download all JS and render app only to find out that it needs to load the data
      • Fetching directly in effects makes it easy to create “network waterfalls”. Fetch data only when parent component is done rendering, then when child components done rendering. Significantly slower than fetching data in parallel
      • Fetching directly in effects usually means you don’t preload or cache data
      • Not ergonomic. A lot of boilerplate code
    • Frameworks may have built-in data fetching mechanism that are efficient and don’t suffer from above pitfalls
    • Or build a client-side cache
    • Can use AbortController to cancel requests that are no longer needed
  • Sending analytics

    useEffect(() => {  
    	logVisit(url); // Sends a POST request  
    }, [url]);
    • Can also send analytics from route change event handlers instead
    • Intersection observers can help track which components are in the viewport and how long they remain visible
  • Some logic that only runs once when application start can be placed outside components

  • In Strict Mode, React mounts components twice (in development only) to stress-test your Effects

  • If Effect breaks because of remounting, you need to implement a cleanup function

Might not need effect

  • No external system involved
  • Removing unnecessary Effects will make code easier to follow, faster to run, less error-prone
  • Do not need to transform data for rendering
    • When state is updated, React will call component functions to calculate what should be on the screen. React will commit these changes to the DOM to update the screen. React then runs Effects. Effects also immediately updates state, restarting the whole process again
    • Unnecessary render passes
    • Instead transform all data at top level of component
  • Do not need to handle user events
    • By the time an Effect runs, you don’t know what the user did
    • Handle them in event handlers instead
  • Need to sync with external systems
    • Keep jQuery widget in sync with React state
    • Fetch data, sync search results with current search query
  • Modern frameworks provide more efficient built-in data fetching mechanisms than writing Effects directly in components
  • Can cache expensive calculation by wrapping it in useMemo Hook
    const visibleTodos = useMemo(() => {
    	return getFilteredTodos(todos, filter);
    }, [todos, filter]);
    • Tells React you don’t want the inner function to re-run unless either todos or filter have changed
    • React remembers return value during initial render. If value of todos or filter are same as last time, useMemo returns last result it has stored
    • Runs during rendering… only pure calculations
    • Test performance with artificial slowdown (Chrome CPU throttling option)
  • useSyncExternalStore Hook
    • Subscribe to an external store
    • Better to use this instead of subscribing inside useEffect
    function subscribe(callback) {  
    	window.addEventListener('online', callback);  
    	window.addEventListener('offline', callback);  
     
    	return () => {  
    		window.removeEventListener('online', callback);  
    		window.removeEventListener('offline', callback);  
    	};  
    }  
     
      
     
    function useOnlineStatus() {  
    	// ✅ Good: Subscribing to an external store with a built-in Hook  
    	return useSyncExternalStore(  
    		subscribe, // React won't resubscribe for as long as you pass the same function  
    		() => navigator.onLine, // How to get the value on the client 
    		() => true // How to get the value on the server  
    	);  
    }  
     
    function ChatIndicator() {  
    	const isOnline = useOnlineStatus();  
    	// ...  
    }

Lifecycle of Effects

  • Can only do 2 things: start syncing something, stop syncing it
  • Can happen multiple times if they depend on props and state that change over time
  • React components lifecycle
    • Mounts, updates, unmounts
  • Effect is independent from component’s lifecycle
  • How to sync an external system to current props and state
  • Sometimes it may be necessary to start and stop syncing multiple times while the component remains mounted
    • Example: on first load, user connects to a “general” chatroom. when user selects a different chatroom, it should close connection with “general” chatroom and connect to the new one
  • How re-sync works
    • React calls cleanup function that your Effect returned after connecting to “general” chatroom.
    • React will run Effect that you’ve provided during this render (the one with the new chatroom id)
  • Stop syncing when user goes to a different screen and the component unmounts
  • Always focus on a single start/stop cycle at a time. Doesns’t matter whether a component is mounting, updating, or unmounting
  • How React verifies that your Effect can resync
    • In development, React always remounts each component once
    • React forces mount, unmount, remount immediately in develop to verify Effect can resync
  • How React knows it needs to resync the Effect
    • Dependency list
    • Every time after component re-renders, React looks at array of dependencies. If any values is different from value in the same spot you passed during previous render, React will resync Effect
  • Each Effect represents a separate sync process
    • Want to send analytics event when user visits the room. Currently the Effect depends on roomId so you might want to add it to the same effect. But what if we add another dependency to this Effect that needs to re-establish connection. If resyncs, it will also call to log the analytic event.
    • Write as two separate Effects instead
  • Effect with empty dependencies
    • Specified what your Effect does to start and stop syncing
    • No reactive dependencies but if change so it relies on reacting to state or prop changes, just add them to dependency list
  • All variables declared in component body are reactive
  • Mutable values (including global variables) are not reactive
    • Can change at any time completely outside of React rendering data flow
    • Changing it won’t trigger re-render of component
    • Reading mutable data during rendering (when you calculate dependencies) breaks purity of rendering
      • Instead, should read and subscribe to an external mutable value with useSyncExternalStore
    • location.pathname, ref.current
    • ref object returned by useRef can be a dependency but current property is intentionally mutable
  • React linter will check that every reactive value used by Effect’s code is declared as its dependency

Separating Events from Effects

  • Sometimes want an Effect that re-runs in response to some values but not others

  • Event handlers

    • Run in response to specific interactions
    • Example: Sending a message
  • Logic inside event handlers is not reactive

    • Will not run again unless user performs the same interaction again
    • Can read reactive values without “reacting” to their changes
  • Logic inside Effects is reactive

    • If reads a reactive value, must specify it as a dependency
    • If re-render causes value to change, Effect logic will re-run with the new value
  • Extracting non-reactive logic out of Effects

    • Example: When user connects to a chatroom, display a notification (also by getting the colour theme prop and show notification in this colour)
    • Setting theme and showing notification should not be reactive… but the changing of chatroomId and connecting to it is reactive (belongs in Effect)
    • Declare Effect Event (Experimental API as of Feb 7 when this note was taken)
      • useEffectEvent Hook to extract non-reactive logic out of Effect (not released in stable version yet)
      • Behaves like an event handler
      • Logic inside is not reactive and it always “sees” latest values of props and state
      function ChatRoom({ roomId, theme }) {  
      	const onConnected = useEffectEvent(() => {  
      		showNotification('Connected!', theme);  
      	});  
       
      	useEffect(() => {  
      		const connection = createConnection(serverUrl, roomId);  
      		connection.on('connected', () => {  
      			onConnected();  
      		});  
      	
      		connection.connect();  
      		return () => connection.disconnect();  
      	
      	}, [roomId]); // ✅ All dependencies declared  
       
      	// ...
  • Reading latest props and state with Effect Events (Experimental API as of Feb 7 when this note was taken)

function Page({ url }) {  
	const { items } = useContext(ShoppingCartContext);  
	const numberOfItems = items.length;  
 
	const onVisit = useEffectEvent(visitedUrl => {  
		logVisit(visitedUrl, numberOfItems);  
	});  
 
	useEffect(() => {  
		onVisit(url);  
	}, [url]); // ✅ All dependencies declared  
	// ...  
}
  • Better to pass url to Effect Event explicitly
    • Pass it as an argument to Effect Event… says that visiting a page with different url constitutes a separate event from the user’s perspective
    const onVisit = useEffectEvent(visitedUrl => {  
    	logVisit(visitedUrl, numberOfItems);  
    });  
     
    useEffect(() => {  
    	onVisit(url);  
    }, [url]);
    • Especially important if there is some async logic inside Effect
      const onVisit = useEffectEvent(visitedUrl => {  
      	logVisit(visitedUrl, numberOfItems);  
      });  
       
      useEffect(() => {  
      	setTimeout(() => {  
      		onVisit(url);  
      	}, 5000); // Delay logging visits  
      }, [url]);
      • url inside onVisit corresponds to the latest url (which could have already changed), but visitedUrl corresponds to url that originally caused this Effect (and this onVisit call) to run
  • Limitations of Effect Events
    • Only call them from inside Effects
    • Never pass them to other components or Hooks
  • Always declare Effect Events directly next to the Effects that use them