DEV Community

Cover image for React Fundamentals
Bhanu Teja Pachipulusu
Bhanu Teja Pachipulusu

Posted on • Updated on • Originally published at blog.bhanuteja.dev

React Fundamentals

In this article, I will discuss very basic React fundamentals. Knowing basic javascript is good enough to follow along with the article. Even though you have been working with React for some time, you might learn some new things or justifications for certain things that you already know from this article.

This article is HEAVILY based on React Fundamentals workshop by Kent C Dodds which will also be a part of Epic React course soon to be released. If you are more like a learning by doing type of person similar to me, head over to the React Fundamentals repo and follow the instructions in the README. This article is more or less a compilation of things in that repo with my own explanations. I will add some more things to this article in the future based on feedback.

Table of Contents

Basic JavaScript-rendered Hello World

Let's see how to render Hello World using basic javascript.

My HTML contains a single div with id as root

<div id='root'></div>
Enter fullscreen mode Exit fullscreen mode

Now, I want to add <div class='container'>Hello World</div> to that root div.

We can do that using javascript's document API. Let's see how

// Fetching the root div element
const rootElement = document.getElementById('root')

// Creating a new div as per our requirements
const divElement = document.createElement('div')
divElement.textContent = 'Hello World'
divElement.className = 'container'

// Appending newly created div element to the root element.
rootElement.append(divElement)
Enter fullscreen mode Exit fullscreen mode

What we are doing here is very simple.

  1. Get a reference to the actual DOM root element
  2. Create a new div element using document.createElement and then set its class and textContent
  3. Append this newly created element to the root div element.

This produces the following HTML markup.

<div id='root'>
    <div class='container'>Hello World</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Intro to raw React APIs

Now let's try to use React's raw APIs to create markup that we need instead of (vanilla) javascript.

We need two important APIs to achieve our task. In vanilla javascript. they are:

document.createElement()
rootElement.append(domElement)
Enter fullscreen mode Exit fullscreen mode

The React's equivalent of these two APIs are:

React.createElement()
ReactDOM.render(reactElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

Let's see React.createElement() in more detail.

React.createElement()

This accepts three parameters

  1. Component or Tag to use to create the element
  2. Props for the component
  3. Children

The API looks like React.createElement(component, props, ...children)

So, to create an element like <div class='container'>Hello World</div>, you would do

React.createElement('div', { className: 'container' }, 'Hello World')
Enter fullscreen mode Exit fullscreen mode

Our HTML will have

<div id="root"></div>
Enter fullscreen mode Exit fullscreen mode

Now, to append <div class='container'>Hello World</div> to the root element using React, we do:

const rootElement = document.getElementById('root')

const divElement = React.createElement('div', {className: 'container'}, 'Hello World')

ReactDOM.render(divElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

Can you see how similar the React's API is to the normal vanilla js document API?

Note that, you can even create nested elements using this.

For example, let's try to create the following markup.

<div class='container'>
    <span>Hello</span>
    <span>World</span>
</div>
Enter fullscreen mode Exit fullscreen mode

To create the above markup

const rootElement = document.getElementById('root')

const helloElement = React.createElement('span', null, 'Hello')
const worldElement = React.createElement('span', null, 'World')
const divElement = React.createElement('div', {className: 'container'}, helloElement, worldElement)

ReactDOM.render(divElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

You can even use a special children prop to add the children like the following

React.createElement('div', {className: 'container', children: [helloElement, worldElement]})
Enter fullscreen mode Exit fullscreen mode

The above code is same as the below code

React.createElement('div', {className: 'container'}, helloElement, worldElement)
Enter fullscreen mode Exit fullscreen mode

Using JSX

If you have already been using React or saw the React code any time, you most probably would not have seen React.createElement being used. Instead, you might have seen some code that looks similar to HTML. Let's see what it is.

JSX is HTML-like syntactic sugar on top of raw React APIs.

Let's see an example.

const divElement = <div id='container'>Hello World</div>
Enter fullscreen mode Exit fullscreen mode

The above code is equivalent to

const divElement = React.createElement('div', {id: 'container'}, 'Hello World')
Enter fullscreen mode Exit fullscreen mode

But JSX is not a valid javascript code, so we use a compiler called Babel to convert JSX code to its corresponding React.createElement code.

Now, let's see how easy it is to create the following markup using JSX.

<div class='container'>
    <span>Hello</span>
    <span>World</span>
</div>
Enter fullscreen mode Exit fullscreen mode
const rootElement = document.getElementById('root')

const divElement = <div className='container'><span>Hello</span><span>World</span></div>

ReactDOM.render(divElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

Note that there are some subtle differences between JSX and HTML.

For example, in HTML to add class to an element, we add it like class='container', whereas in JSX, we need to write className='container'.

There are some other differences when using JSX which I will explain later in the post.

Interpolation in JSX

Since JSX is written in javascript itself, there are some very interesting things which you can do. One of those is using JSX interpolation. It basically gives us the ability to use javascript inside of JSX. Whenever you do interpolation, you would surround it with { and }. This tells the Babel compiler that interpolation is being used here.

For example, take the following code

const divElement = <div className='container'>Hello World</div>
Enter fullscreen mode Exit fullscreen mode

Now, you want the class name and text content to be dynamic, you can do something like

const divClassName = 'container'
const divTextContent = 'Hello World'

const divElement = <div className={divClassName}>{divTextContent}</div>
Enter fullscreen mode Exit fullscreen mode

Can you see the flexibility that interpolation gives us?

Conditionals and Loops

You can even add conditionals and loops in JSX

{ condition ? <div>Hello World</div> : <div>Goodbye World</div> }
Enter fullscreen mode Exit fullscreen mode

As you can see above, to use conditionals in JSX, you would make use of the tertiary operator.

{items.map((item) => <div key={item.id}>{item.title}</div>)}
Enter fullscreen mode Exit fullscreen mode

To use loops, you will use the map function.

You can even use template literals in JSX like

const element = <div id={`item-${itemId}`}>{itemContent}</div>
Enter fullscreen mode Exit fullscreen mode

To learn more about JSX, head over to JSX in Depth - React's official docs

Creating custom components

Consider the following JSX code

<div className='container'>
  <div className='message'>Hello World</div>
  <div className='message'>Goodbye World</div>
</div>
Enter fullscreen mode Exit fullscreen mode

Here you can see the code <div className='message'></div> is duplicated at two places.

To avoid duplication, the simplest thing we can do is to create a function and then call it instead.

function message(text) {
    return <div className='message'>{text}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className='container'>
    {message('Hello World')}
    {message('Goodbye World')}
</div>
Enter fullscreen mode Exit fullscreen mode

Let's refactor this a bit.

function message({children}) {
    return <div className='message'>{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className='container'>
    {message({children: 'Hello World'})}
    {message({children: 'Goodbye World'})}
</div>
Enter fullscreen mode Exit fullscreen mode

Let's refactor this even more to use React.createElement

function message({children}) {
    return <div className='message'>{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className='container'>
    {React.createElement(message, null, 'Hello World')}
    {React.createElement(message, null, 'Goodbye World')}
</div>
Enter fullscreen mode Exit fullscreen mode

Previously in all the examples that we have seen, the first argument of React.createElement() is a string like 'span' or 'div'.

But React.createElement also accepts a function that returns something renderable like JSX, some string, number etc.

That's why the above code works.

Now, let's convert the above code to JSX

function message({children}) {
    return <div className='message'>{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className='container'>
    <message>Hello World</message>
    <message>Goodbye World</message>
</div>
Enter fullscreen mode Exit fullscreen mode

The above code seems to be perfect, right? Actually it's not. The above code will not work as intended. The reason being how babel compiles the JSX code to it's corresponding React.createElement() code.

<message /> is compiled by babel to React.createElement('message'). But what we want is React.createElement(message). In the first case, the first argument is a string, in the second case, it's a function.

For the babel to convert it into what we needed, we have to make the message function name uppercase.

function Message({children}) {
    return <div className='message'>{children}</div>
}
Enter fullscreen mode Exit fullscreen mode
<div className='container'>
    <Message>Hello World</Message>
    <Message>Goodbye World</Message>
</div>
Enter fullscreen mode Exit fullscreen mode

Now, this <Message>Hello World</Message> will be compiled to React.createElement(Message, {children: 'Hello World'}), which is exactly what we needed.

Check the below examples to see how Babel compiles each of the JSX formats.

JSX React.createElement()
<Capitalized /> React.createElement(Capitalized)
<property.access /> React.createElement(property.access)
<Property.Access /> React.createElement(Property.Access)
<Property['Access'] /> SyntaxError
<lowercase /> React.createElement('lowercase')
<kebab-case /> React.createElement('kebab-case')
<Upper-Kebab-Case /> React.createElement('Upper-Kebab-Case')
<Upper_Snake_Case /> React.createElement(Upper_Snake_Case)
<lower_snake_case /> React.createElement('lower_snake_case')

So, we can see that the component name needs to be UpperCamelCased for it to work as intended.

PropTypes

Let's slightly change the previous message component so that it accepts name prop.

function Message({name}) {
    return <div className='message'>Hi, your name is {name}.</div>
}
Enter fullscreen mode Exit fullscreen mode
<Message name='foo' />
<Message />
Enter fullscreen mode Exit fullscreen mode

This produces

Hi, your name is foo.
Hi, your name is .
Enter fullscreen mode Exit fullscreen mode

This doesn't look good. Does it? So what if there is a way we can enforce that the name needs to be passed and it needs to be a string.

Luckily, React gives us a way to do that using PropTypes.
Let's create a PropType to enforce the type of name to be string.

const PropTypes = {
    string(props, propName, componentName) {
        if (typeof props[propName] !== 'string') {
            return new Error(`In component ${componentName}, ${propName} needs to be a string, but it was of type ${typeof props[propName]}`)
        }
    },
}

function Message({name}) {
    return <div className='message'>Hi, your name is {name}.</div>
}

Message.propTypes = {
    name: PropTypes.string,
}
Enter fullscreen mode Exit fullscreen mode

Now every time you pass anything other than string for name prop, it throws an error.

Since cases like these are so common, the React team developed a library called prop-types which you can use in a similar way - PropTypes.string.isRequired. Check out the repo for more details.

Note that PropTypes will be disabled in a production environment for performance reasons.

React Fragments

<div id='root'></div>
Enter fullscreen mode Exit fullscreen mode

Let's consider the following use case.
You have to add <span>Hello</span> and <span>World</span> to the rootElement using React.

In the end, the markup should look like

<div id='root'>
    <span>Hello</span>
    <span>World</span>
</div>
Enter fullscreen mode Exit fullscreen mode

Let's see if we can do this.

const rootElement = document.getElementById('root')

const elementOne = React.createElement('span', null, 'Hello')
const elementTwo = React.createElement('span', null, 'World')

ReactDOM.render(?????, rootElement)
Enter fullscreen mode Exit fullscreen mode

Now, what should be in the place of ????? in the last line. It can neither be elementOne nor elementTwo, because we want both of them to be rendered (not one). But ReactDOM.render() takes only one react element as an argument and then appends it to rootElement.

One way to achieve this is if we can wrap both of the elements in a new element.

const rootElement = document.getElementById('root')

const elementOne = React.createElement('span', null, 'Hello')
const elementTwo = React.createElement('span', null, 'World')

const combinedElement = React.createElement('div', null, elementOne, elementTwo)

ReactDOM.render(combinedElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

The above code may look fine, but it produces different HTML than what we needed.

<div id='root'>
    <div>
        <span>Hello</span>
        <span>World</span>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

This is the reason why you can't do something like the following in your code.

function Message() {
    return <span>Hello</span><span>World</span>
}
Enter fullscreen mode Exit fullscreen mode

Because there is no way for babel to be able to convert this to a single React.createElement()

React Fragments are introduced in React v16.2.0 exactly to solve this problem. Now you can return multiple elements by just wrapping them around with React.Fragment.

For example,

function Message() {
    return (
        <React.Fragment>
            <span>Hello</span>
            <span>World</span>
        </React.Fragment>
    )
}
Enter fullscreen mode Exit fullscreen mode

React will ignore this React.Fragment when rendering.

So the previous problem can be solved now in the following way.

const elementOne = React.createElement('span', null, 'Hello')
const elementTwo = React.createElement('span', null, 'World')

const combinedElement = React.createElement(React.Fragment, null, elementOne, elementTwo)

ReactDOM.render(combinedElement, rootElement)
Enter fullscreen mode Exit fullscreen mode

There is a short hand representation for React.Fragment.

Instead of writing <React.Fragment>{childrent}</React.Fragment>, you can write something like <>{children}</>. Both yield absolutely same result.

Styling

There are two general ways to style React components.

  1. Inline CSS
  2. Regular CSS

Let's see inline CSS first

Inline CSS

In normal HTML too, you can add inline styles to your HTML elements by adding your styles as a string to style attribute.

<div style="color: red; font-style: italic;">Red Italic Text</div>
Enter fullscreen mode Exit fullscreen mode

In React also you would add your styles to style prop, but instead of a string, style prop accepts an object.

For example,

const elementStyle = {
    color: 'red',
    fontStyle: 'italic'
}
Enter fullscreen mode Exit fullscreen mode
<div style={elementStyle}>Red Italic Text</div>
Enter fullscreen mode Exit fullscreen mode

You can even inline elementStyle if you like

<div style={{ color: 'red', fontStyle: 'italic' }}>
    Red Italic Text
</div>
Enter fullscreen mode Exit fullscreen mode

Another difference with styles in React from that of HTML is property names needs to be camelCased instead of kebab-cased. For example, in React styles, background-color will become backgroundColor, font-style will become fontStyle, etc.

Also the value of the style property is always string or number(since style needs to be a proper javascript object, things like #fff or 20px are not proper javascript values). So you cannot write something like fontSize: 20px, instead you need to write fontSize: '20px'. Similarly you cannot write color: #fff, you need to write color: '#fff'.

Regular CSS

Using regular css is straight forward. You just add the classNames and ids that you need and then style your elements using those accrodingly.

Forms

Consider the following form

<form>
  <div>
    <label htmlFor="usernameId">Username:</label>
    <input id="usernameId" type="text" name="username" />
  </div>
  <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Now handling forms in React is very similar to how we do in normal javascript. You just define a submit handler and then assign it to the onSubmit event of the form.

function handleSubmit(event) {
    event.preventDefault()
    // You can get the value of username in one of the following ways.
    // event.target.elements[0].value
    // event.target.elements.usernameId.value
    // event.target.elements.username.value
   // Do whatever you want with the username
}
Enter fullscreen mode Exit fullscreen mode

Using Refs

There is another way to get the reference to an element in React - using Refs.
Refs are special objects in react that stays consistent between rerenders of the component and also changing it will not cause the component to rerender.

You can create a Ref using React.useRef()

const myRef = React.useRef()
Enter fullscreen mode Exit fullscreen mode

Refs will have a current property which contains the value of ref. If you assign a ref to a React element, ref.current will automatically have the reference to the object.

For example

<input ref={myRef} />
Enter fullscreen mode Exit fullscreen mode

Now myRef.current will have reference to that input element.

Let's make use of ref to get the username in our form.

function UsernameForm() {
  const usernameInputRef = React.useRef()

  function handleSubmit(event) {
    event.preventDefault()
    // usernameInputRef.current.value will have the value of the input
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="usernameInput">Username:</label>
        <input id="usernameInput" type="text" ref={usernameInputRef} />
      </div>
      <button type="submit">Submit</button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

Go through useRef - official docs to learn more about refs.

There is more to learn about React Forms. Go through official docs to learn more.

This article is HEAVILY based on React Fundamentals workshop by Kent C Dodds which will also be a part of Epic React course soon to be released. If you are more like a learning by doing type of person similar to me, head over to the React Fundamentals repo and follow the instructions in the README. This article is more or less a compilation of things in that repo with my own explanations. I will add some more things to this article in the future based on the feedback that I receive.

Did you learn anything new from this article? Tell me in the comments.

Links and References

Other articles of mine that you might like

ko-fi

Top comments (0)