Short notes about React
I recently went through the new React documentation and took notes on the important parts. I will share these with you along with examples.
Common
-
Strict Mode of React calls each component function twice during development and helps you catch impure components. Keep this in mind when counting render times.
-
Code that runs only once at the start of the application can be placed outside of the
component.App
1if (typeof window !== "undefined") { 2 checkAuthToken(); 3 loadDataFromLocalStorage(); 4} 5 6function App() { 7 // ... 8}
Rendering
-
Component is a function that return HTML elements. They use a syntax extension called JSX that looks like HTML but is actually typescript. Since it's still typescript, it makes working with elements in JS easier. Ultimately, JSX is also transpiled into HTML.
1function MyButton() { 2 return <button>I'm a button</button>; 3} 4 5export default function MyApp() { 6 return ( 7 <div> 8 <h1 className="heading-1">Welcome to my app</h1> 9 <MyButton /> 10 </div> 11 ); 12}
-
If there is no child item, always use the closing tag.
1<CustomElement /> 2// not <CustomElement></CustomElement>
-
We can't return multiple elements in a component. We need to wrap them with
or<div></div>
which is called a fragment. Second one avoids adding an extra element to the HTML. This is because a function can't return more than one value.<></>
1<> 2 <Header /> 3 <Main /> 4 <Footer /> 5</>
-
We can use typescript variables and expressions in JSX with curly braces.
1const user = { 2 fullName: "Hedy Lamarr", 3 imageUrl: "https://i.imgur.com/yXOvdOSs.jpg", 4 imageSize: 90, 5}; 6 7export default function Profile() { 8 return ( 9 <> 10 <h1>{user.fullName}</h1> 11 <img 12 className="avatar" 13 src={user.imageUrl} 14 alt={`Photo of ${user.fullName}`} 15 style={{ 16 width: user.imageSize, 17 height: user.imageSize, 18 }} 19 /> 20 </> 21 ); 22}
-
We can use three different approaches for conditional rendering.
,false
, andnull
values are ignored in JSX.undefined
1// first way 2let content; 3 4if (isLoggedIn) { 5 content = <AdminPanel />; 6} else { 7 content = <LoginForm />; 8} 9return <div>{content}</div>; 10 11// second way 12<div>{isLoggedIn ? <AdminPanel /> : <LoginForm />}</div> 13 14// third way 15<div> 16 {isLoggedIn && <AdminPanel />} 17 {!isLoggedIn && <LoginForm />} 18</div>
-
We can use three different approaches for conditional rendering.
,false
andnull
values returned from conditions are ignored in JSX.undefined
1// first way: if statement 2if (isPacked) { 3 return <li className="item">{name} ✔</li>; 4} 5 6return <li className="item">{name}</li>; 7 8// second way: ternary operator 9return <li className="item">{isPacked ? name + " ✔" : name}</li>; 10 11// third way: and operator 12return ( 13 <li className="item"> 14 {name} {isPacked && "✔"} 15 </li> 16);
-
We can use JSX arrays to render multiple elements. Just make sure to give each element a
attribute to ensure that React renders the correct elements even if the array changes.key
1const products = [ 2 { title: "Cabbage", id: 1 }, 3 { title: "Garlic", id: 2 }, 4 { title: "Apple", id: 3 }, 5]; 6 7const listItems = products.map((product) => ( 8 <li key={product.id}>{product.title}</li> 9)); 10 11return <ul>{listItems}</ul>;
-
In React, the DOM is updated after the code block completes when the state changes. If we need to access the new DOM immediately after a state change, we can use the
method.flushSync
1import { useState, useRef } from "react"; 2import { flushSync } from "react-dom"; 3 4export default function TodoList() { 5 const listRef = useRef(null); 6 const [todos, setTodos] = useState(initialTodos); 7 8 function handleAdd() { 9 flushSync(() => { 10 setTodos([...todos, { id: nextId++, text: text }]); 11 }); 12 13 listRef.current.lastChild; // can access new todo dom element 14 } 15 16 return ( 17 <> 18 <button onClick={handleAdd}>Add</button> 19 <input value={text} onChange={(e) => setText(e.target.value)} /> 20 <ul ref={listRef}> 21 {todos.map((todo) => ( 22 <li key={todo.id}>{todo.text}</li> 23 ))} 24 </ul> 25 </> 26 ); 27}
Event Handler
-
Event handler is a function that will be executed when a user triggers an event. We can pass these functions as props to component. By default, the
object is passed to the function. If we want to send extra data, we can use an arrow function.Event
1function MyButton() { 2 function handleClick() { 3 alert("You clicked me!"); 4 } 5 6 return ( 7 // <button onClick={(e) => handleClick(e, customProp)}> 8 <button onClick={handleClick}>Click me</button> 9 ); 10}
-
It's common to start naming with
for event props andon
for handler. For example:handle
.onMouseEnter={handleMouseEnter}
-
Event propagation is also works with components. Looking at the example code, first the button's
and then div'sonClick
is called. All events exceptonClick
propagate upwards. To prevent this,onScroll
is used.e.stopPropagation()
1export default function Toolbar() { 2 return ( 3 <div 4 className="Toolbar" 5 onClick={() => { 6 alert("You clicked on the toolbar!"); 7 }} 8 > 9 <button 10 onClick={() => { 11 e.stopPropagation(); 12 alert("Playing!"); 13 }} 14 > 15 Play Movie 16 </button> 17 </div> 18 ); 19}
-
Some events, such as the
event of a form element, have default browser behavior and will execute their own code after your code. To prevent this, we can useonSubmit
.e.preventDefault
-
To execute event handlers from top to bottom (capturing), we can use
instead ofonClickCapture
.onClick
1<div 2 onClick={() => { 3 alert("Call 1"); 4 }} 5> 6 <div 7 onClickCapture={() => { 8 alert("Call 2"); 9 }} 10 > 11 <div 12 onClickCapture={() => { 13 alert("Call 3"); 14 }} 15 > 16 Click! 17 </div> 18 </div> 19</div>
Call 2 Call 3 Call 1
Props
-
We use props to pass data to a component. It's the same logic as passing params to a function.
1function Avatar({ person, size = 100 }) { 2 // ... 3} 4 5function Profile() { 6 return ( 7 <div className="card"> 8 <Avatar person="Enes" size={80} /> 9 </div> 10 ); 11}
-
We can get children elements from the
props.children
1function Heading({ children }) { 2 return <div className="heading">{children}</div>; 3} 4 5export default function Text() { 6 return <Heading>Hello</Heading>; 7}
State
- We use state for values that can change over time and cause the screen to be updated. It is like a component-based memory. The difference from classic variables is that value is preserved between renders.
- State is associated with the location of the component in the DOM. Otherwise, it is preserve its own value. If we want to reset the state, we can change the
parameter.key
- To define state in React, we use the
hook.useState
1import { useState } from "react";
2
3function MyButton() {
4 // count: current value
5 // setCount: setter function
6 // 0: initial value
7 const [count, setCount] = useState(0);
8
9 function handleClick() {
10 setCount(count + 1);
11 }
12
13 return <button onClick={handleClick}>Clicked {count} times</button>;
14}
-
State setter çağırıldıktan sonra kod bloğu tamamlandığında rerender tetiklenir.
-
React states are stored in an array and should only be defined at the root of the component. They are read the value in the order they are called. That's why they should only be defined at the root of the component.
-
We should use the spread operator when updating it.
1const [person, setPerson] = useState({ 2 name: "Niki de Saint Phalle", 3 artwork: { 4 title: "Blue Nana", 5 city: "Hamburg", 6 image: "https://i.imgur.com/Sd1AgUOm.jpg", 7 }, 8}); 9 10function handleTitleChange(e) { 11 setPerson({ 12 ...person, 13 artwork: { 14 ...person.artwork, 15 title: e.target.value, 16 }, 17 }); 18}
-
The state value doesn't change immediately when we call the setter method. So, if we call the setNumber method three times, we won't get the final value of 3. Instead, each time the setter is called, the state value remains as the value read at the start of render (in this case 0). The state value is updated in memory only after the onClick block executed and a render is triggered. To access the current value of the component in memory, we can use the updater function.
1import { useState } from "react"; 2 3export default function Counter() { 4 const [count, setCount] = useState(0); 5 6 return ( 7 <> 8 <h1>{count}</h1> 9 <button 10 onClick={() => { 11 setCount(count + 1); // setCount(0 + 1) 12 setCount(count + 1); // setCount(0 + 1) 13 setCount(count + 1); // setCount(0 + 1) 14 // the value will be 1 in the next render. 15 }} 16 > 17 +3 18 </button> 19 <button 20 onClick={() => { 21 setCount((count) => count + 1); // setCount(0 + 1) 22 setCount((count) => count + 1); // setCount(1 + 1) 23 setCount((count) => count + 1); // setCount(2 + 1) 24 // the value will be 3 in the next render. 25 }} 26 > 27 +3 28 </button> 29 </> 30 ); 31}
Reducer
-
We may have difficulty managing complex states. Reducer establishes an action-based state update mechanism. We can use the
hook to define a reducer.useReducer
1import { useReducer } from "react"; 2 3function tasksReducer(tasks, action) { 4 switch (action.type) { 5 case "add": { 6 return [ 7 ...tasks, 8 { 9 id: action.id, 10 text: action.text, 11 done: false, 12 }, 13 ]; 14 } 15 case "change": { 16 return tasks.map((t) => { 17 if (t.id === action.task.id) { 18 return action.task; 19 } else { 20 return t; 21 } 22 }); 23 } 24 case "delete": { 25 return tasks.filter((t) => t.id !== action.id); 26 } 27 default: { 28 throw Error("Unknown action: " + action.type); 29 } 30 } 31} 32 33const initialTasks = [ 34 { id: 0, text: "Visit Kafka Museum", done: true }, 35 { id: 1, text: "Watch a puppet show", done: false }, 36 { id: 2, text: "Lennon Wall pic", done: false }, 37]; 38 39const useTasksReducer = () => useReducer(tasksReducer, initialTasks); 40 41export { tasksReducer, useTasksReducer };
1import AddTask from "./AddTask.js"; 2import TaskList from "./TaskList.js"; 3import { useTasksReducer } from "./tasksReducer.js"; 4 5export default function TaskApp() { 6 const [tasks, dispatch] = useTasksReducer(); 7 8 function handleAddTask(text) { 9 dispatch({ 10 type: "add", 11 id: nextId++, 12 text, 13 }); 14 } 15 16 function handleChangeTask(task) { 17 dispatch({ 18 type: "change", 19 task, 20 }); 21 } 22 23 function handleDeleteTask(taskId) { 24 dispatch({ 25 type: "delete", 26 id: taskId, 27 }); 28 } 29 30 return { 31 // ... 32 }; 33}
Ref
-
Ref is used to access HTML elements, so there is no need to deal with
. We can use thequerySelector
hook to define a reference.useRef
1import { useRef } from "react"; 2 3export default function Form() { 4 const inputRef = useRef(null); 5 6 function handleClick() { 7 inputRef.current.focus(); 8 } 9 10 return ( 11 <> 12 <input ref={inputRef} /> 13 <button onClick={handleClick}>Focus the input</button> 14 </> 15 ); 16}
-
The ref value cannot be read or modified in the component body because it is inaccessible during DOM rendering. It can only be accessed inside
and event handler that we are sure will run after rendering.useEffect
1function VideoPlayer({ src, isPlaying }) { 2 const ref = useRef(null); 3 4 // WRONG! this will crash. 5 if (isPlaying) { 6 ref.current.play(); 7 } else { 8 ref.current.pause(); 9 } 10 11 // CORRECT! instead we can use useEffect 12 useEffect(() => { 13 if (isPlaying) { 14 ref.current.play(); 15 } else { 16 ref.current.pause(); 17 } 18 }); 19 20 return <video ref={ref} src={src} loop playsInline />; 21}
-
It can also be used to store a value because it is preserved during renders. Unlike state, it does not trigger a rerender when the value changes.
1import { useRef } from "react"; 2 3export default function Counter() { 4 let count = useRef(0); 5 6 function handleClick() { 7 count.current = count.current + 1; 8 alert("You clicked " + count.current + " times!"); 9 } 10 11 return ( 12 <div> 13 <p>Click count: {count.current}</p> 14 <button onClick={handleClick}>Click me!</button> 15 </div> 16 ); 17}
-
We can use a single reference to keep track of the references of multiple elements. However, when an element is deleted, it also needs to be removed from this reference. We can use a ref callback for this.
1import { useRef } from "react"; 2 3export default function CatFriends() { 4 const itemsRef = useRef(new Map()); 5 6 return ( 7 <ul> 8 {catList.map((cat) => ( 9 <li 10 key={cat.id} 11 ref={(node) => { 12 if (node) { 13 itemsRef.current.set(cat.id, node); 14 } else { 15 itemsRef.current.delete(cat.id); 16 } 17 }} 18 > 19 ... 20 </li> 21 ))} 22 </ul> 23 ); 24}
-
We may want to assign a ref to an HTML element inside a component. The ref we passed as a prop is not automatically added to the DOM. To pass this, we need to use
.forwardRef
1import { forwardRef, useRef } from "react"; 2 3const MyInput = forwardRef((props, ref) => { 4 return <input {...props} ref={ref} />; 5}); 6 7export default function Form() { 8 const inputRef = useRef(null); 9 10 return <MyInput ref={inputRef} />; 11}
Context
-
Context provides data flow from the component to sub components. Instead of passing the props 100 times down between components, we can define a context and read it with
hook.useContext
1import React, { createContext, useContext, useState } from "react"; 2 3// firstly we create context 4const LanguageContext = createContext(); 5 6// the provider allows to access context for children 7const LanguageProvider = ({ children }) => { 8 const [language, setLanguage] = useState("en"); 9 10 // value props store the provided context's values 11 return ( 12 <LanguageContext.Provider value={{ language, setLanguage }}> 13 {children} 14 </LanguageContext.Provider> 15 ); 16}; 17 18// we can read the values with useContext hook 19const useLanguageContext = () => useContext(LanguageContext); 20 21export { LanguageProvider, useLanguageContext };
1import { LanguageProvider } from "./LanguageContext.tsx"; 2import { Navbar } from "./Navbar.tsx"; 3import { PageTitle } from "./PageTitle.tsx"; 4 5export default function App() { 6 return ( 7 <div className="App"> 8 <LanguageProvider> 9 <Navbar /> 10 <PageTitle /> 11 </LanguageProvider> 12 </div> 13 ); 14}
1import { useLanguageContext } from "./LanguageContext.tsx"; 2 3export const PageTitle = () => { 4 const { language } = useLanguageContext(); 5 6 const titleByLang = { 7 en: "Welcome to my App!", 8 tr: "Uygulamama hoşgeldiniz!", 9 }; 10 11 return <h1>{titleByLang[language]}</h1>; 12};
1import { useLanguageContext } from "./LanguageContext.tsx"; 2 3export const Navbar = () => { 4 const { setLanguageHandler } = useLanguageContext(); 5 6 return ( 7 <nav> 8 <button onClick={() => setLanguageHandler("en")}>English</button> 9 <button onClick={() => setLanguageHandler("tr")}>Türkçe</button> 10 </nav> 11 ); 12};
Effect
-
It is used to perform side effects caused by rendering. For example, we can use it to save analytic data when the page is loaded or to establish a connection with the server in a chat application. We use the useEffect hook.
1useEffect(() => { 2 // run after every render 3}); 4 5useEffect(() => { 6 // run after every render when dependency changed (will compare with Object.is) 7}, [someCompVariable, someProps, someState]); 8 9useEffect(() => { 10 // run on mount (appears on the screen for the first time) 11 12 return () => { 13 // run on unmount 14 }; 15}, []);
-
It doesn't work during server-side.