Intro To React: Part 1
React From First Principles
November 30, 2020
4 min read
Last updated: July 27, 2021
React is a JavaScript library for creating interactive UIs that is all the rage right now. However too often beginners dive straight into React and treat what they’ve learnt as “React magic”. I want to take a different approach - by building up React from a set of good solid fundamentals.
Vanilla JS and the DOM
The vanilla JS approach to adding interactivity to a website is by directly manipulating the DOM, using the special document
object. We query specific DOM elements by tagging that element in some way (e.g through an id or through a CSS class).
<div id="root" /><script>document.getElementById("root").innerHTML ="<div><h1> Hello World </h1> <p> Other text </p></div>"</script>
The document
object is quite barebones and error-prone. For one, it is on us to correctly close tags in HTML strings. If we miss out a </p>
tag, as we do here, we won’t get an error until runtime.
It’s also a lot of boilerplate to write the querying logic and tag our elements accordingly.
<div id="root" /><script>document.getElementById("root").innerHTML ="<div><h1> Hello World </h1> <p> Other text </div>"</script>
Really, we want a library to abstract away the messy nature of the document
API for us. This is where React comes in.
Enter React
React abstracts this DOM manipulation away by introducing a virtual DOM (ReactDOM
). We now manipulate React elements in the virtual DOM, and let React handle changes to the actual DOM. All we need to specify is the DOM node into which we hook our ReactDOM
into - here it’s the root
div. From now on, say bye to document
!
<div id="root" /><script>ReactDOM.render(<div><h1> Hello World </h1> <p> Other text </p></div>,document.getElementById("root"))</script>
JSX
React elements are written with JSX syntax (this is just like writing HTML in JS), with the added perk of React checking that we close our tags correctly!
ReactDOM.render(<div><h1> Hello World </h1> <p> Other text </p></div>,document.getElementById("root"))
This is syntactic sugar for a set of createElement
calls to update the virtual ReactDOM
. This abstraction is handled by Babel, a JavaScript compiler that usually comes pre-bundled with React frameworks like Create React App.
ReactDOM.render(React.createElement("div",null,React.createElement("h1", null, " Hello World ")," ",React.createElement("p", null, " Other text ")),document.getElementById("root"))
It might appear that we haven’t gained much from writing React JSX compared to HTML, as we’ve effectively written the same code.
However, using a virtual DOM means React can introduce abstractions that aren’t present in the actual DOM and then translate those under the hood to actual DOM operations.
ReactDOM.render(<div><h1> Hello World </h1> <p> Other text </p></div>,document.getElementById("root"))
Components
Say we want to reuse parts of the DOM to avoid duplicating code. How would we reuse duplicate JS code? Functions. And that’s all components in React are. Functions that return React elements.
const App = () => (<div><h1> Hello World </h1> <p> Other text </p></div>)ReactDOM.render(<App />, document.getElementById("root"))
Again, this JSX tag syntax for <App/>
is just syntactic sugar. You could equivalently write it with normal function syntax (App()
).
const App = () => (<div><h1> Hello World </h1> <p> Other text </p></div>)ReactDOM.render(App(), document.getElementById("root"))
And like you can call functions in multiple places in the code, you can reuse this App
component.
const App = () => (<div><h1> Hello World </h1> <p> Other text </p></div>)ReactDOM.render(<div><App /><App /></div>,document.getElementById("root"))
Sidenote: <>
is shorthand for <React.Fragment>
, and you can use it to indicate to React virtual DOM that you want to return a group of elements. Wrapping with <> </>
doesn’t actually add any elements to the actual DOM! This is a perk of the virtual DOM, with the actual DOM we’d have to unnecessarily wrap groups with a <div>
.
const App = () => (<div><h1> Hello World </h1> <p> Other text </p></div>)ReactDOM.render(<><App /><App /></>,document.getElementById("root"))
Just as we can call functions within each other, we can nest components within another.
const Button = () => <button> Click me! </button>const App = () => (<div><h1> Hello World </h1> <p> Other text </p><Button /><Button /></div>)ReactDOM.render(<App />, document.getElementById("root"))
Components can take arguments too, since they are functions after all. The JSX syntax for this looks a lot like HTML attributes. We wrap JS expressions passed as arguments in {}
, but this is nothing specific to React, it’s just like template strings in vanilla JS: 'This is ${1 + 2}'
.
const App = () => (<div><h1> Hello World </h1> <p> Other text </p><ButtoninnerText="Click me"onClick={() => {alert("I was clicked")}}/><Button innerText="I do nothing" onClick={() => {}} /></div>)ReactDOM.render(<App />, document.getElementById("root"))
Props
But really this JSX syntax for component attributes is syntactic sugar for a function call with a single object passed in as an argument. React has a special name for this object: props
(short for properties) and its fields are the properties we specify in the tag.
Button({innerText: "Click me",onClick: () => {alert("I was clicked")},})Button({ innerText: "I do nothing", onClick: () => {} })
We can access the values of fields inside our component function by doing props.___
.
const Button = props => (<button onClick={props.onClick}>{props.innerText} </button>)const App = () => (<div><h1> Hello World </h1> <p> Other text </p><ButtoninnerText="Click me"onClick={() => {alert("I was clicked")}}/><Button innerText="I do nothing" onClick={() => {}} /></div>)
We can use these props
to render different versions of our component, for example depending on whether we pass the argument props.innerText
. So every times the props
change, React will re-render the component. Since props
is used by React to determine whether to re-render a component, it is immutable. This is to make sure the component is pure - given the same props
as arguments, it will produce the same rendered output.
You might think this re-rendering is expensive, but this is a re-rendering of the virtual DOM. React handles this re-rendering through its reconciler, which figures out what has changed (similar to when we run git diff
), and then only updates the parts of the actual DOM that have changed.
const Button = props => {if (props.innerText) {return <button onClick={props.onClick}>{props.innerText}</button>}return <button onClick={props.onClick}> Default Text </button>}
The benefits of React
We’ve just outlined the core of React in this blog post. It’s worth taking a step back and highlighting the key advantages of React:
- No mutation or side effects
- Local knowledge of components
- Separates view and application logic
If we look back at the DOM document
API we were using, to update our DOM we had to directly mutate elements the elements, often as side-effects of clicking a button. The issue here is that there’s an implicit state in the DOM that we as developers have to track - which event listeners can mutate this part of the DOM as a side-effect, which order they mutate it etc.
With React, the state a component requires is made explicit with the props
object. The only way to change the rendered output of a component is by passing different props
- there are no side-effects. Within the component itself you don’t have to worry about mutations as the props
are treated as immutable and React will re-render whenever they change. This way you can leave the setting of the props to the application logic, and the rendering of the components to your React logic.
Props also act as a good abstraction boundary for local reasoning. When using a component, you don’t need to know the internals, as the output is solely dependent on the props
you pass in, and as mentioned, within the component you don’t have to worry about how it will be used.
We’ve only just got started with React, in the next post I’d like to look at Hooks from this same foundational perspective. If that’s something that interests you, either follow me on Twitter or sign up via email to be notified when the next post is coming out!