Notes on React#
This is not JavaScript, this is JSX#
At the start of the React Quick Start section, it is said the components are JavaScript functions that return markup, and that they are named with an uppercase letter.
But Vanilla JavaScript cannot return markup! It only works here because React components actually use JSX (JavaScript Syntax Extension) to extend JavaScript capabilities. JSX can be written both in .jsx and .js files, and if the code editor supports the syntax, it won't highlight returning markup as an error in the later:

By convention most people will create a file per component. But as shown in the Tic Tac Toe exercise in the React docs, it's possible to store several components inside the same file.
It quickly creates an avalanche of function declarations that can be hard to read. So, in order to help me, I think about React components as JSX functions, and the rest as JavaScript functions. It helps me distinguish between what's React and what's not.
Markup can be everywhere#
When using other frameworks like Vue or Svelte, or templating languages like Liquid or Nunjucks, there's a separation of concerns between data and markup, even if everything is done inside the same file. For example, consider this Svelte 4 component, with its <script> section followed by the template:
<script>
let myVar = 1;
</script>
<div>{myVar}</div>
At first, it could be tempting to see React components as an entirely different mental model. But if you consider that the return() function returning the markup will always be located at the end, and that the scripting will always be above it, it's actually quite similar:
function MyComponent() {
let myVar = 1;
return <div>{myVar}</div>
}
But this paradigm can be disturbed, as React make it possible (but not an obligation) to render and store markup in advance in the "scripting" part of the component, for later use by the return() function. For example, rendering a list this way:
function MyComponent({people}) {
const listItems = people.map(person => <li>{person}</li>);
return <ul>{listItems}</ul>;
}
Both approaches may have pro and cons? It's probably nice to be able to work on data from its arrival in the component up to its markup in one go. But if the focus is working on the markup of the component and iterating over it, scrolling through the component to change things can probably be annoying.
Naming conventions#
- Components names start with an uppercase letter
- Event handlers start with
handle - Event handlers props start with
on - State setting functions start with
set
Props can be almost anything#
Props are defined as the arguments of the function that define the component. They can be destructured using curly braces, or just be grouped as a single object to ease passing them to a children component.
export default function myComponent({ prop1, prop2}) {
return <MyOtherComponent prop1={prop1} prop2={prop2} />
}
// or
export default function myComponent(props) {
return <MyOtherComponent {...props} />
}
React is kinda lax when it comes to props. It's possible to pass a variable, a function, an event handler, or even a whole component as a prop by nesting them in JSX. When it happens, the nested component becomes a prop called children. In a way, passing the children prop and placing it inside markup has the same effect as using a `
export default function MainComponent(props) {
return (
<ParentComponent>
<ChildrenComponent />
</ParentComponent>
)
}
function ParentComponent({children}) {
return (
<div className="card">
{children}
</div>
);
}
And to finish: it's impossible to change props, and every render receives a new version of the props.
Be careful with event handlers#
Event handlers must be passed, not called. Use onClick={handleClick} instead of onClick={handleClick()}.
Be careful with event propagation in JavaScript. If a parent component has an event handler (for example onClick) and one of his children components has one too, the children onClick will trigger the parent's onClick. To prevent this from happening, use e.stopPropagation().
Same with default behaviors like ones in <form>, use e.preventDefault().
State variables#
Local variables don't persist between renders, and changes to local variables don't trigger renders. To fix this, declare variables with useState(), which will return the value and a setter to change the value.
const [value, setValue] = useState(null);
State is scoped to the component instance and private, and must be declared at the top level of the components. You can use as many state variables as necessary.
Immutability is very important for state variables#
All state variables should be treated as immutable to avoid side effects. The docs have very good example both for objects and arrays that can act as cheat sheets.
The docs themselves recommend using the package immer to manipulated nested state, due to the complexity of updating nested objects,
The rendering cycle is tricky#
React renders in three steps:
- Trigger: React detects a change
- Rendering: React renders the change in memory
- Committing: React commits the change into the DOM
The difference between step 2 and 3 is important. When rendering starts, React takes a snapshot in time and uses everything at its disposal from that exact frozen moment (events, props, variables) to create the new UI in memory.
It means a state variable’s initial value never changes within a render, React keeps the state values “fixed” within one render’s event handlers before replacing it.
If you ask for sequential changes of a state value inside the same event, each change will be done from the data present in the snapshot. In the example below where number equals to 0 and setNumber is called 3 times, the end result is 1 because each setNumber is executed with values from the snapshot.
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
But it's still possible to queue state updates during a render. To do that, instead of replacing the state, you update it using a function that takes the state as an argument.
<button onClick={() => {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>+3</button>
It’s common to name the updater function argument by the first letters of the corresponding state variable.
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
The difference between replacing state and updating state within the same render is important, as it allows to work on several changes before sending it to the DOM. React re-renders the component tree for each change, and in deep nested applications it can quickly become a source of performance issues. Be careful with React renders.