JSX
In our previous discussion, we delved into React's fundamentals, such as creating and adding elements to the page. React's approach is more about declaring what you want, starkly contrasting to the imperative element creation we're accustomed to in regular JavaScript.
But let's be honest: writing React.createElement
for every single element can be a pain, especially when compared to the simplicity of HTML. This article will explore a more flexible and familiar way to create and position elements, making life easier for developers and unlocking us to create better products for our clients.
What is JSX?
#
JSX is a fantastic feature in React that lets us write JavaScript more HTML-like. You can build a whole website using these top-level API calls, but it will be tricky. Working with JSX makes the code easier to write, read, and understand.
For instance, check out the difference between creating a <span>
tag using React API calls vs the JSX notation:
1//Using React API calls
2const element = React.createElement('span', {
3 key: 'element-key',
4 style: { color: 'blue' },
5 children: 'Hi there',
6})
7
8//Using JSX
9<span key="element-key" style={ color: blue }>
10 Hi there
11</span>
We can use HTML tags when writing JSX code. However, this notation differs from JavaScript or HTML, even if it looks identical. So, how does the browser make sense of it if it's neither HTML or JS?
The short answer is it does not. We need some translator in between, so our JSX code runs React API calls that execute pure JavaScript functions which can be understood by the browser. We simply communicate what needs to be created and where to set it on-screen.
The Interpreter
#
As of this writing, Babel is the most popular JSX compiler available. This tool acts as a middleman between our JSX code and the browser, compiling it so the browser can understand. It functions like a black box, using our JSX code as an entry and outputting React code.
Sound mysterious? Try out this Babel Simulator and experiment with how it works. On the left side of the screen, you can write your JSX code and see how Babel translates it into React calls on the right side.
If you are unsure of how to start, do not worry. Try copying the JSX code from the example above and paste it to verify the React code matches. See how the React calls nest when you add other basic HTML tags such as <div>
or <li>
. If you go with the list, you should find something similar to the solution of the previous extra mile.
Some good to know's
#
JSX can be written in different ways and obtain the same output:
1const children = "Hello"
2const key = "element"
3const style = { color: 'blue' }
4
5const props = {key, style, children}
6
7const defineChildren = () => {
8 return "Hello"
9};
10
11//All properties have fixed values
12<span key="element" style={{ color: 'blue' }}>Hello</span>
13
14//We can also use variables and constants
15<span key={key} style={style}>{children}</span>
16
17//The children prop is always available and allow us to omit the closing tag
18<span key={key} style={style} children={children} />
19
20//We can asign a value without defining a variable
21<span key={key} style={style} children={`${children}`} />
22
23//Also values returned from functions are expected
24<span key={key} style={style} children={defineChildren()} />
25
26//We can spread objects when its attributes matches with span's properties
27<span {...props} />
All those elements will render the same blue βHelloβ message on the screen (here a sandbox to play around with the code).
This is starting to look useful to me, and I hope you feel the same. We can declare variables and perform various complex operations to calculate a value and use it immediately in a JSX tag. Additionally, we can still define how our elements will be displayed on the screen, just like we do with HTML.
There are many more features that we can benefit from right out of the box:
JSX helps to prevent XSS attacks from escaping the values contained in our variables to strings
Notice how
children
can be used as part of the opening tag or the contentUsing the spread operator allows us to write shorter components by coalescing all props under a single expression. In other words and regarding the above example,
{...props}
is equivalent to{key='element', style={{ color: 'blue' }}, children='Hello'}
Function's return values can be assigned to the tag props, as we do with
defineChildren
. Babel will remove the{}
around the expression and leave it free to be executed by the browser as plain JavaScript (remember that behind every JSX notation, we will have anReact.createElement
execution)If wrapping no content into a tag, we can use the
<tag />
notation. Note that we can not do that in HTML
Custom Styling
#
We can inline style our tags to change their colour or use any other property that we can work with in CSS. The major difference, I would say, is that we can assign an object to style
instead of a string
, as we would normally do.
Below is an example of how Babel will translate to React code in both cases:
1//First we go with an object, the approach proposed in JSX
2<span style={color: 'blue'}>Hello there</span>
3
4//Babel compiles the span above to:
5React.createElement('span', {style: {color: 'blue'}}, "Hello there")
6
7//**----**//
8
9//And finally, this is what the browser would get in case we attempt to pass
10//a string as the style value:
11<span style="color: blue">Hello there</span>
12
13//Babel compiles to:
14React.createElement('span', {style: "color: blue"}, "Hello there")
It looks pretty similar, ey? However, inline styles as a string will not work, and the browser will give an error message regardless of Babel not complaining.
Fear not, there is an alternative that makes the string work. In JSX, every tag contains some default properties, such as children
. The one we can use to apply inline styles using a string is className
, which works as the style
property we are used to, with the difference that we can assign class names into it, and not specific CSS styles as we do in HTML. Here an example:
1//In a global.css file
2.custom = {
3 color: 'blue';
4 backgroundColor: 'red';
5}
6
7//Our JSX code
8<span className="custom">Message</span>
If you want to inline define your styles, your alternative is to adapt them to an object
and provide it instead of the string
to the style
property. Although keep an eye to the name of those styles, it does says backgroundColor
not background-color
. Those naming conventions are subtly different, and help to describe some boundaries between the notations available for both syntaxes.
HTML notation for style properties follows the kebab-case notation (e.g.: background-color
, margin-top
), while in JSX we use the camel-case notation (e.g.: backgroundColor
, marginTop
). If we try to apply a red background for our Hello message, the following would do the trick:
1//In HTML
2<span style="color: blue, background-color: red">Hello there</span>
3
4//In JSX
5<span style={{ color: 'blue', backgroundColor: 'red' }}>Hello there</span>
Custom Components
#
Until now, JSX hasn't shown many capabilities that would make us switch from the familiar HTML. However, it's worth noting that behind the scenes, the tag name is the first parameter of the React.createElement
function call. This parameter expects a tag name or a function.
A function? Can we declare a function that behaves as we need for our specific case? For example, can we create a tag that, when called, renders a <div>
with a <p>
and a <span>
nested inside? And can we reuse them throughout our codebase? Now we're talking!
A React component is a function that may or may not return a renderable value (JSX). Until now, we have been writing <span>
tags that will compile to React.createElement("span")
. What if we declare a function called CustomSpan()
that, in its returned value, renders a conventional <span>
tag?
1//Conventional span tag
2<span>Hello there</span>
3
4//Our custom React component
5const CustomSpan = () => {
6 return <span style={{ color: 'blue', backgroundColor: 'red' }}>Hello there</span>
7}
The win here is that we no longer need to define the colour for our custom <span>
. All we need to do is to use the <CustomSpan>
component!
1//We can use the full notation
2<CustomSpan></CustomSpan>
3
4//Or more accurately as we are not wrapping other content
5<CustomSpan />
When creating custom components in React, it's essential to follow specific rules to make the most of it. For example, we capitalise our tag name to match the function component. Babel is smart enough to figure out if the user entered a lowercase value as the first parameter of React.createElement
, and it will compile to React.createElement('span')
. It will compile React.createElement(CustomSpan)
when a capitalised tag name is provided, which executes our CustomSpan
function and renders its returned value (notice there are no quotes markdown).
Iterate to Generalise
#
Right now, our custom component could look better. It would be awesome to have a more customisable message and styles so that we can reuse it in more places. Also, I'm not cool with the name containing specific details of its implementation, like having "span" in the name:
1//Custom component declaration
2const Typography = ({content, style}) => {
3 return <span style={style}>{content}</span>
4}
5
6//This is how we use it
7<Typography
8 content="Customisable Message"
9 style={ color: 'blue', backgroundColor: 'red' }
10/>
11
12//Babel compiles to
13React.createElement(
14 Typography,
15 {
16 content: "Customisable Message",
17 style={ color: 'blue', backgroundColor: 'red' }
18 }
19)
Our new <Typography>
component can show any text we want by passing the value to the content
property and style it as we please in an easy to use over and over again way!
Nonetheless, be careful when rendering multiple components at a root level. We can not do that as we can not have other elements at the same level as our <html>
tag on a regular website page:
1<Typography content="I'm the first message" />
2<Typography content="I'm the the second one" />
Do you think you can explain why this will not work?
Hereβs why. ReactDOM will try to attach those two components to the root element simultaneously, as we did in previous examples:
1const rootElement = document.getElementById('root')
2
3const element = (
4 <Typography content="I am the first message" />
5 <Typography content="I am the the second one" />
6)
7
8ReactDOM.render(element, rootElement);
The ReactDOM.render
function expects to receive a single component as element
, but we're passing through two. The alternative is to wrap them into a common higher-order component (or parent). By doing so, we can create more complex structures and UI for our users.
Info
For the example above, using a <div>
tag to wrap our content could work, but for better performance (i.e., to avoid creating an extra DOM node), we can use the Fragment
component, which comes available out of the box. Here is more info about it.
Whereas, the element
constant can be initialised as follows:
1const element = (
2 <Fragment>
3 <Typography content="I am the first message" />
4 <Typography content="I am the the second one" />
5 </Fragment>
6)
7
8//or using the simplified version
9
10const element = (
11 <>
12 <Typography content="I'm the first message" />
13 <Typography content="I'm the the second one" />
14 </>
15)
Summary
#
Today, we discussed why using JSX in React is so beneficial. Creating and positioning elements is much easier than using the React API directly. With JSX, we can write code that looks like HTML but works like JavaScript, which makes it more readable and developer-friendly.
Compilers like Babel turn JSX into React API calls that execute pure JavaScript functions, ensuring the code works well on different browsers. Remember that style properties use the camelCase
notation, which differs from HTML's kebab-case
.
Finally, we touched on custom components, showing how functions that return JSX can encapsulate and reuse complex structures. This is a key concept we will be revisiting all the time.
The Extra Mile
#
How do you feel about JSX so far? Do you think it's the way to go, or is it too complicated?
Remember the list of items we made last time? You can find it here. What do you think about showing the same result but incorporating the <Typography>
component we played with instead of plain text? What are the pros of this approach? Think of it like Lego pieces clicking together, and take a look at my solution if it helps.
And how about, as an extra practice, updating your code to use a new <List>
custom component you can create? It can receive through props
the collection of items to display and maybe also the styles. Good luck! π
Next time, we will dive into forms and learn how to work with them using custom components, handle inputs, and manage submit flows.