DEV Community

Cover image for Getting Started with React: A Beginner's Complete Guide
vaatiesther
vaatiesther

Posted on

Getting Started with React: A Beginner's Complete Guide

This post is a bit long. If you'd prefer the complete version in a 60-page PDF, you can download it here.

In this Guide, we will cover the following:

  • React basics and its component-based architecture
  • JSX syntax and how it differs from HTML
  • Creating and using components
  • Managing state with the useState hook
  • Handling user interactions and events
  • Conditional rendering and list rendering
  • Working with forms in React
  • Lifting state up for better component communication
  • Managing side effects with the useEffect hook

What is React?

React is an open-source JavaScript library developed by Facebook in 2013 for building user interfaces, particularly for web applications. Facebook developed React because they had a problem with the state of messages where read messages would still display the unread count.

React comes with its own rules, and to understand React, you need to adhere to these rules and understand how React renders when an application loads. Once you understand these concepts, learning React becomes a breeze

đź’ˇWhy React

React is one of the most popular frameworks and one of the most in-demand libraries. It also relies heavily on JavaScript, so if you are proficient in JavaScript, learning React will prove to be easier.

React is also an evolving library with a strong vibrant, active community who are continuously improving its features.

React operates on the principle of reusable components. Unlike in a traditional setting where you would have an index.html file with all the code, React allows you to have components for each section, for example, you can have a Header component, a Sidebar component, a Main component, and a Footer component and so on.

đź’ˇGetting Started with React

While it's common to build in React by using a framework, you can use a CDN (Content Delivery Network) to start building in with React. This approach allows you to understand how React works under the hood.

Step1: Set up Your HTML File

Create an HTML file with the basic structure, including <! DOCTYPE html>, <html> </html>, <head> </head>, and <body> </body> and the necessary scripts and the CDN links.

Load both React and ReactDOM with CDN links. By using a CDN, you can start building with React without installing any extra tools on your local development environment.

In this example, we will use the unpkg to load React.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React CDN Example</title>
    <!-- React Library -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <!-- React DOM Library -->
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
    <div id="root"></div>
    <script>
        // Your React code will go here
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

In the head section, we have two script tags for loading React and ReactDom from a CDN. Then a div with the id = root. This is where our react application will be rendered.

What is CreateElement

CreateElement is a function used by React to create React elements. React elements are the building blocks of React. The basic syntax of creating a React element with the createElement function looks like this:

React.createElement(type, [props], [...children])
Enter fullscreen mode Exit fullscreen mode
  • type - this can be a string for HTML elements (i.e. “div”, “h1”), etc.

  • props : this is an object containing the properties to be passed to the element. For example {id:” container”} might be passed to a div element.

  • children : this is the content inside the element.

Create and Render an Element

Let’s start by creating a simple H1 heading with the content “ Hello React “. In the script tag, add the code below.

 const element = React.createElement('h1', {},"Hello World!");
 ReactDOM.render(element, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

When you open the index.html file with your browser, you can see the content rendered on your browser.

react app

Congratulations, you have just created your first React App.

Adding Properties.

Our H1 element at the moment does not have any properties, let's fix that, add an id == header

const element = React.createElement('h1', {id :"header"},"Hello World!");
ReactDOM.render(element, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

Now, if you inspect with Developer tools, you can see the ID has been applied.

react app

Nesting Elements

You can also nest createElement call to create more elements. For example, let's add more elements and enclose them with a div.

const element = React.createElement('h1', {id :"header"},"Hello World!");
const element2 = React.createElement('p', {id :"paragraph"}, "This is a paragraph.");

const divElement = React.createElement('div', {id :"container"},element,element2);

ReactDOM.render(divElement, document.getElementById('root'));


const divElement = React.createElement('div', {id: "container"}, element, element2); 
Enter fullscreen mode Exit fullscreen mode

This creates a <div> element with the ID "container". Inside this div, we are passing the 2 elements as children.

Here is the final code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React CDN Example</title>
    <!-- React Library -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <!-- React DOM Library -->
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
</head>
<body>
    <div id="root"></div>
    <script>

       const element = React.createElement('h1', {id :"header"},"Hello World!");
       const element2 = React.createElement('p', {id :"paragraph"}, "This is a paragraph.");
       const divElement = React.createElement('div', {id :"container"},element,element2);
       ReactDOM.render(divElement, document.getElementById('root'));
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

This method of creating a React application is a great way to understand how React works under the hood but for larger applications, you would typically use JSX. JSX syntax is the recommended way to create React applications

JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside JavaScript files. JSX is easier to read and write because it utilizes pure JavaScript. So if you have a solid foundation in JavaScript, Learning React will not be that difficult.

For example, if we were to use a component, the previous code we have just written in JSX, we will have something like this;

function MyComponent() {
  return (
    <div id="container">
      <h1 id="header">Hello World!</h1>
      <p id="paragraph">This is a paragraph.</p>
    </div>
  );
}

ReactDOM.render(<MyComponent />, document.getElementById('root'));
Enter fullscreen mode Exit fullscreen mode

As you can see, this code is more concise

What is JSX

Normally, when creating web applications, you write all your HTML in one file and the JavaScript logic in a .js file. However, this is not the case with JSX. With JSX, all the HTML and Javascript live in one file

So let's convert our code to use JSX

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React CDN Example</title>
    <!-- React Library -->
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <!-- React DOM Library -->
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script >
      const name = "World";
      const message = "This is a dynamic paragraph.";

      const element = (
        <div id="container">
          <h1 id="header">Hello !</h1>
          <p id="paragraph">Hello</p>
        </div>
      );

      ReactDOM.render(element, document.getElementById("root"));
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

This code will not work because the JSX code needs to be compiled. Compiling is important because browsers don't understand JSX code, so tools like Babel transform the code into a format suitable for browsers to read.

To fix this issue, include a compiler such as Babel and specify that the JSX code should be transpiled. Update the code as follows:

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>React CDN Example</title>
   <!-- React Library -->
   <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
   <!-- React DOM Library -->
   <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
   <!-- Babel for JSX -->
   <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
 </head>
 <body>
   <div id="root"></div>
   <script type="text/babel">
     const name = "World";
     const message = "This is a dynamic paragraph.";

     const element = (
       <div id="container">
         <h1 id="header">Hello {name}!</h1>
         <p id="paragraph">{message}</p>
       </div>


     );

     ReactDOM.render(element, document.getElementById("root"));
   </script>
 </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The line <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> loads Babel, which allows JSX to be compiled in the browser.
Change the script tag containing JSX to type="text/babel". This ensures that Babel knows how to process it.

Now our code is working as expected.

You might have noticed when we injected the javascript variable inside our HTML markup, we used curly braces.

<h1 id="header">Hello {name}!</h1>
<p id="paragraph">{message}</p>
Enter fullscreen mode Exit fullscreen mode

In JSX, if you need to add JavaScript expressions or variables inside your HTML-like markup, you need to wrap the JS code inside curly braces . The curly braces tell JSX that the code inside the braces is Javascript code and not plain text.

Setting up a React environment

We have seen how to create a React app using a CDN, along with createElement, ReactDOM, and Babel. However, this method is not recommended for production applications, as it does not scale well for larger projects.

Luckily, several frameworks and tools exist that provide built-in support for React and abstract away the complexities of setting up a build process. These tools enable developers to focus on writing code rather than managing configurations.

Recommended Tools for React Development:

  • Vite:Vite is a modern build tool that provides a fast and efficient development environment for React applications.

  • Create React App (CRA):Create React App is a widely used tool that sets up a new React project with a solid default configuration.

If you don't fancy a local development environment, you can use https://codesandbox.io to host your code.

Head over to https://codesandbox.io and create an account. Once you are logged in. On your dashboard, create a new React application.

codesandbox

In the next screen, select React

codesandbox

Provide a name for your project and click the Create Sandbox button.

codesandbox

If you prefer a local environment, then you should have Node.js installed. React requires Node.js to run its development server and build the project. You can download the latest stable version of Node.js from Node.js official website.

Start a New React Project with Create-react app

To create a new project, issue the npx create-react app command.

npx create-react-app hello-react
Enter fullscreen mode Exit fullscreen mode

You should see something like this:

create react app

Once the installation is complete, cd into the app and issue the npm start command

cd hello-react
npm start
Enter fullscreen mode Exit fullscreen mode

Your app should now be running at http://localhost:3000/

Application Structure

The app structure looks like this:

react app structure

Whether you use a local development environment or an online tool like CodeSandbox, the structure of the application will be the same. Both methods provide a similar setup, with files like index.js, App.js, and index.html acting as the foundation of your React app.

In this guide, we will use CodeSandbox but for larger applications, a local development environment offers more control and is preferred.

In the public folder, we have an index.html file which looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="#000000">
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
    <title>React App</title>
</head>
<body>
    <noscript>
        You need to enable JavaScript to run this app.
    </noscript>
    <div id="root"></div>

</body>
</html>
Enter fullscreen mode Exit fullscreen mode

The index.html file is a standard HTML file, except that it includes a . In React, this serves as the mounting point where React renders your application.

All the content will be injected into this div. Unlike in a traditional app, where you would select each element using methods like getElementById or querySelector, React uses the ReactDOM.render() method to render components into this div. This process is handled in the index.js file.

In your **index.js **file, you have the code below

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

Here, we have imported StrictMode from react and createRoot from react-dom/client. StrictMode is a feature in React used to catch potential bugs and errors in your application. It helps enforce best practices like warning about deprecated features, checking for side effects, and verifying component lifecycle methods.

const rootElement = document.getElementById("root"); is a reference to the div with the id == root from the index.html. Unlike JavaScript where we use a DOM, React uses a virtual DOM to render components.

The virtual DOM allows React to make updates more efficiently by calculating changes in memory and then updating only the necessary parts of the actual DOM.

Once we get a reference to the root, we call createRoot to create a React root. The React root will display content in the browser. Lastly, we render the App component.

This is the App component in the app.js file.

import "./styles.css";

export default function App() {
 return (
   <div className="App">
     <h1>Hello CodeSandbox</h1>
     <h2>Start editing to see some magic happen!</h2>
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

The App component introduces us to the JSX (JavaScript XML) syntax. React uses JSX syntax to describe the UI. Most of the code in React applications is written in JSX.

As you can see, JSX looks similar to regular HTML. Behind the scenes, JSX is transformed into JavaScript function calls (e.g., React.createElement) before being rendered to the DOM.

At the top of the file, we can see import "./styles.css"; statement that imports a CSS file that styles the component.

In a regular HTML file, we use class to apply style, but in JSX, the equivalent is className . The className="App" is used to apply CSS styles defined in the styles.css file. In JSX, class is a reserved keyword in JavaScript, so it can't be used as an attribute name.

To prevent conflicts with the JavaScipt language, JSX uses className instead of class to define CSS classes on elements.

A React component is a regular javascript function or class that returns JSX. In the return statement, we have the JSX code that describes the UI. The UI has a div element with two headings: an <h1> and an <h2>.

The export default keyword is important because it allows us to export a component (like App) as the default export of a file. This means that when another file imports App, it can do so without using curly braces around the component name.

In our case, we have seen the App component imported in the index.js file.

// index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import App from "./App";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

If you don't use export default, then you would then have to use a named export like this

import { App } from './App';
Enter fullscreen mode Exit fullscreen mode

JSX Expressions

Earlier, we saw that we need to enclose any Javascript code with curly braces { }

Let’s do more practice.

Create the following variables in the **App.js **file.

greeting ("Hello codeSandox")
message (e.g., "Start editing to see some magic happen
.")
Enter fullscreen mode Exit fullscreen mode

Inject them into h1 and p tags respectively. The elements should be contained in a div element. The App component now looks like this.

import "./styles.css";

export default function App() {
 const greeting = "Hello CodeSandbox";
 const message = "Start editing to see some magic happen! ";
 return (
   <div className="App">
     <h1>{greeting}</h1>
     <h2>{message}</h2>
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

Let’s duplicate the div with the className = App

import "./styles.css";

export default function App() {
 const greeting = "Hello CodeSandbox";
 const message = "Start editing to see some magic happen! ";
 return (
   <div className="App">
     <h1>{greeting}</h1>
     <h2>{message}</h2>
   </div>
    <div className="App">
    <h1>{greeting}</h1>
    <h2>{message}</h2>
  </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

When we do this, we run into some errors, The error message “Adjacent JSX elements must be wrapped in an enclosing tag. “ pops up

This error occurs because, in React, elements must wrapped in a single parent element. To fix this error, we can use a div as a wrapper for all the elements or use a JSX fragment.

What is jsx Fragment

A JSX fragment is a tool in React that lets you wrap multiple elements without adding any additional div elements. Instead of creating a wrapper dic for all the divs, let's create a JSX fragment.

The syntax for a fragment looks like this:

shorthand syntax: <>...</>
full syntax: <React.Fragment>...</React.Fragment>

Let’s use the shorthand syntax in our code.

import "./styles.css";

export default function App() {
 const greeting = "Hello CodeSandbox";
 const message = "Start editing to see some magic happen! ";
 return (
   <>
   <div className="App">
     <h1>{greeting}</h1>
     <h2>{message}</h2>
   </div>
    <div className="App">
    <h1>{greeting}</h1>
    <h2>{message}</h2>
  </div>
  </>
 );
}
Enter fullscreen mode Exit fullscreen mode

Now the errors disappear and when we inspect the elements using browser tools, we can see something like this;

react app

Components

React is a component-based framework. A component is a function that returns React elements. Components make up the UI of any React Application. A component can be as small as a button or as big as a Navbar or Main content.

React comes with two kinds of components, class-based components and **function-based **components. Class-based components are considered legacy, and it's recommended to use function-based components.

You can think of a component as a JavaScript function that returns HTML.

A single component contains content, styles, and javascript of that feature.

We already have the App component.

Create Your First Component

The first step to creating interactive user interfaces in React is to create components since they are the building blocks of any React application. Components can be created as standalone or inside other components. For example, in the App.js file, we can add more components to it.

To create a standalone file, you need to create a new file in the src directory. When naming components, ensure you use PascalCasing.

Create a new file named Menu.js . In the new component, let’s define the structure and what it should display.

export default function Menu() {
 return (
   <div>
     <h2>Hello there what are you having</h2>
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

To make this component part of your app, let's import it into the **App.js **file at the top as follows:

To render the Menu component as part of our UI, we need to render it as <Menu/> .

Update the App.js file as shown below.

import "./styles.css";
import Menu from "./Menu";

export default function App() {
 return (
   <div className="App">
     <h1>JOT RESTAURANT</h1>
     <Menu />
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

Now your app looks like this.

react components

The good thing about React is that, since it uses a virtual DOM,(a lightweight in-memory representation of the real DOM) every time you make changes to your code, the UI updates quickly without any page reload

Styling in React

You have probably seen the import "./styles.css"; import at the top of the App.js file. This is responsible for importing all the CSS styles to be applied to the App component elements.

React also allows us to add inline styles just as we would in HTML. However, the way we do it in React is a bit different. Instead of strings, React expects the styles to be contained in an object for the style attribute, with the CSS properties written in camelCase. Here’s an example:

const styles = {
  color: 'blue',
  fontSize: '20px',
  marginTop: '10px'
};

function MyComponent() {
  return <div style={styles}>This is a styled text!</div>;
}
Enter fullscreen mode Exit fullscreen mode

Notice how font-size becomes fontSize, and values are written as strings or numbers where appropriate.

Let’s add some styles to our application.

import "./styles.css";

const styles = {
  color: "blue",
  fontSize: "20px",
  marginTop: "10px",
};

export default function App() {
  return (

      <div className="App">
        <h2 style={styles}>JOT RESTAURANT</h2>
      </div>


  );
}
Enter fullscreen mode Exit fullscreen mode

These styles will be added to the h2 tag and you will see that the heading has a blue color

Props

You know how functions in JavaScript have arguments, and React components have props. Props allow us to pass data from one component to another, typically from a parent component to a child component.

In React, data can only be passed from parent to child components, not the other way around, for example, we have a Menu and an App component, and since the App is the parent component, we can only pass data downwards to its children.

Props are also read-only and shouldn’t be modified within a component. If a child component needs to communicate changes or pass data back up to the parent, it can do so using callback functions passed as props.

In the Menu component we we want to add a few dishes, update like this:

// menu.js
import React from "react";

const Menu = () => {
  return (
    <div className="menu-container">
      <div className="burger-item">
        <h3>Beef Burger</h3>
        <p>A classic beef burger with all the trimmings.</p>
      </div>
      <div className="burger-item">
        <h3>Cheeseburger</h3>
        <p>Juicy cheeseburger with melted cheddar cheese.</p>
      </div>
      <div className="burger-item">
        <h3>Bacon Burger</h3>
        <p>Delicious bacon burger with crispy bacon strips.</p>
      </div>
      <div className="burger-item">
        <h3>Veggie Burger</h3>
        <p>A hearty veggie burger with fresh ingredients.</p>
      </div>
      <div className="burger-item">
        <h3>Double Patty Burger</h3>
        <p>A massive burger with two juicy patties.</p>
      </div>
      <div className="burger-item">
        <h3>Spicy Jalapeño Burger</h3>
        <p>A spicy burger with jalapeños and pepper jack cheese.</p>
      </div>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Our app now looks like this:

react

In the Menu component, we have at least 5 items displayed, each item is contained in its div and the div has the title and description.

If we had images, we would also add them there. But there is a problem, this data is static. If we had a list of 100 items to display, it would be very cumbersome to create a div for each item, and that's where props come to save the day

Props (properties) allow us to pass data from parent to child components. We can use props to pass data therefore making components more reusable and dynamic.

Create a new component called Menu . Inside this file, we will create an instance of one burger, ie one item.

export default function Burger(props) {
 return (
   <div>
     <div className="burger-item">
       <h3>Beef Burger</h3>
       <p>A classic beef burger with all the trimmings.</p>
     </div>
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

You can see we have added the props in the function definition. Now rather than hardcoding the name of the burger and the description, we will simply use props.name and props.descriprtion

export default  function Burger(props) {
 return (
   <div>
     <div className="burger-item">
       <h3>{props.name}</h3>
       <p>{props.description}</p>
     </div>
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

Now in the Menu component, we need to pass as many Burger instances as there are burgers, so if we have 5 burgers in the menu, we will pass 5 Burger component instances like this:

Let's start by adding one instance of the Burger component. First import the Burger component at the top of the Menu.js file.

import Burger from "./Burger";
Enter fullscreen mode Exit fullscreen mode

Update the Menu.js file as shown below:

// menu.js
import Burger from "./Burger";
export default function Menu() {

  return (
    <div className="menu-container">
      <Burger />
      {/* the rest of the code */}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

The app now looks like this;

react app

As you can see the first item is empty, that's because even though we have injected an instance of the Burger component, we haven't defined the values, the Burger component expects a name and a description.

This is how data is added via props:

 <Burger name = "Beef Burger" description = "A classic beef burger with all the trimmings."/>
Enter fullscreen mode Exit fullscreen mode

Now we don't need the hard-coded data, since we have a reusable component. let's' replace the rest of the data

const Menu = () => {
  return (
    <div className="menu-container">
      <Burger
        name="Beef Burger"
        description="A classic beef burger with all the trimmings."
      />
      <Burger
        name="Cheeseburger"
        description="Juicy cheeseburger with melted cheddar cheese."
      />
      <Burger
        name="Bacon Burger"
        description="Delicious bacon burger with crispy bacon strips."
      />
      <Burger
        name="Veggie Burger"
        description="A hearty veggie burger with fresh ingredients."
      />
      <Burger
        name="Double Patty Burger"
        description="A massive burger with two juicy patties."
      />
      <Burger
        name="Spicy Jalapeño Burger"
        description="A spicy burger with jalapeños and pepper jack cheese."
      />
    </div>
  );
};


}
Enter fullscreen mode Exit fullscreen mode

We have successfully passed props from parent to child.

Destructuring Props

When we define props as we have done above, we have no idea of what the props object contains. Luckily with destructuring, we can destructure the properties {name, description} and then rather than referencing props.name or props.description, we do something like this;

export default function Burger({name,description}) {
  return (
    <div>
      <div className="burger-item">
        <h3>{name}</h3>
        <p>{description}</p>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

List Items

If we were getting a large amount of data from an API or database, it would take us all day to directly add the burger components in the JSX. To improve reusability and maintainability, the best approach would be to define an array of burgers, and then iterate over the array and create a component for each burger.

Create an array named burgers and add all the burger names. The array should be defined at the top level, outside the function.

const burgers = [
  { name: "Beef Burger" },
  { name: "Cheeseburger" },
  { name: "Bacon Burger" },
  { name: "Veggie Burger" },
  { name: "Double Patty Burger" },
  { name: "Spicy Jalapeño Burger" },
  { name: "BBQ Burger" },
  { name: "Mushroom Swiss Burger" },
  { name: "Chicken Burger" },
  { name: "Fish Burger" },
  { name: "Impossible Burger" },
];
Enter fullscreen mode Exit fullscreen mode

Keeping the array outside the component ensures the data is only created once when the module is loaded. It is also good for performance since it can lead to unnecessary performance overhead especially if the dataset is large., if we keep it inside the component, it means that it will be rendered every time the component mounts.

Keeping the data outside the component also prevents redundant initializations since the data is static.

Iterating with the Map() Method

We have worked with a small set of data so far, suppose that our data is coming from an API or a database, the same approach we did so far will not be suitable.

The good thing about iterating in React is that you use pure JavaScript, so your Javascript skills of mapping over an array with the map () method will come in handy

To recap, if we wanted to map over an array, we would do something like this:

const burgers = ["Beef Burger", "Cheeseburger", "Bac Burger"];

const burgerList = burgers.map((burger) => {
  return `<div>
            <h3>${burger}</h3>

          </div>`;
});
Enter fullscreen mode Exit fullscreen mode

The burgerList array will now contain an array of HTML strings for each burger. The output will contain an array of HTML strings for each burger.

[
  '<div><h3>Beef Burger</h3> </div>',
  '<div><h3>Cheeseburger</h3> </div>',
  '<div><h3>Bac Burger</h3> </div>'
]
Enter fullscreen mode Exit fullscreen mode

Luckily react does a good job of unpacking arrays, So for example, if we injected this data in you our JsX syntax like this:

 {   [
  '<div><h3>Beef Burger</h3> </div>',
  '<div><h3>Cheeseburger</h3> </div>',
  '<div><h3>Bac Burger</h3> </div>'
]}
Enter fullscreen mode Exit fullscreen mode

This would be the output

map() javascript

We can see that react renders the data as required, all we need to do is apply the necessary styles.

Create an array of burger objects

const burgers = [
 {
   name: "Beef Burger",
   description: "A classic beef burger with all the trimmings.",
 },
 {
   name: "Cheeseburger",
   description: "Juicy cheeseburger with melted cheddar cheese.",
 },
 {
   name: "Bacon Burger",
   description: "Delicious bacon burger with crispy bacon strips.",
 },
 {
   name: "Veggie Burger",
   description: "A hearty veggie burger with fresh ingredients.",
 },
 {
   name: "Double Patty Burger",
   description: "A massive burger with two juicy patties.",
 },
 {
   name: "Spicy Jalapeño Burger",
   description: "A spicy burger with jalapeños and pepper jack cheese.",
 },
];
Enter fullscreen mode Exit fullscreen mode

Since the map() method must return something, in this case we want to create a Burger component for each of the items in the burgers array and then pass the name and description. Inside the menu-container div, call burgers.map and return a Burger component for each element.

import React from "react";

const burgers = [
  {
    name: "Beef Burger",
    description: "A classic beef burger with all the trimmings.",
  },
  {
    name: "Cheeseburger",
    description: "Juicy cheeseburger with melted cheddar cheese.",
  },
  {
    name: "Bacon Burger",
    description: "Delicious bacon burger with crispy bacon strips.",
  },
  {
    name: "Veggie Burger",
    description: "A hearty veggie burger with fresh ingredients.",
  },
  {
    name: "Double Patty Burger",
    description: "A massive burger with two juicy patties.",
  },
  {
    name: "Spicy Jalapeño Burger",
    description: "A spicy burger with jalapeños and pepper jack cheese.",
  },
];

const Menu = () => {
  return (
    <div className="menu-container">
      {burgers.map((burger) => (
        <Burger name={burger.name} description={burger.description} />
      ))}
    </div>
  );
};

export default Menu;
Enter fullscreen mode Exit fullscreen mode

Once you update this, we can see an error that looks like this:

react error

This error occurs because react needs each item in a list to have a unique key. To resolve the error, we need to add a key to each element. Since the index is unique, let's add it as the key.

Although using the index as a key is not recommended because it can lead to issues especially if any of the list items is removed or shifted in position you might want to generate a unique key for each burger.

 <div className="menu-container">
      {burgers.map((burger,index) => (
        <Burger key = {index}name={burger.name} description={burger.description} />
      ))}
    </div>
Enter fullscreen mode Exit fullscreen mode

Conditional Rendering

Conditional rendering lets you render data based on certain conditions, for example, suppose we wanted to render certain burgers only if they are available or spicy, let's add additional properties to each burger namely is_available(boolean) and isSpicy(boolean).

const burgers = [
  { name: "Beef Burger", description: "A classic beef burger with all the trimmings.", available: true, isSpicy: false },
  { name: "Cheeseburger", description: "Juicy cheeseburger with melted cheddar cheese.", available: true, isSpicy: false },
  { name: "Bacon Burger", description: "Delicious bacon burger with crispy bacon strips.", available: false, isSpicy: false },
  { name: "Veggie Burger", description: "A hearty veggie burger with fresh ingredients.", available: true, isSpicy: false },
  { name: "Double Patty Burger", description: "A massive burger with two juicy patties.", available: true, isSpicy: false },
  { name: "Spicy Jalapeño Burger", description: "A spicy burger with jalapeños and pepper jack cheese.", available: true, isSpicy: true }
];
Enter fullscreen mode Exit fullscreen mode

Now we can conditionally render items based on these properties. This time, we will use the filter method to render only the spicy burgers.

const availableBurgers = burgers.filter((burger) => burger.available);
Enter fullscreen mode Exit fullscreen mode

In the code above , .filter(burger => burger.isSpicy) ensures that only burgers where available: trueare passed to the .map() function for rendering.

Let’s add the spicy Badge based on the isSpicy component within the burger component.

export default function Burger({ name, description, isSpicy }) {
  return (
    <div>
      <div className="burger-item">
        <h3>{name}</h3>
        <p>{description}</p>

        {isSpicy && <span className="spicy-badge">Spicy 🌶️</span>}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

{isSpicy && <span className="spicy-badge">Spicy 🌶️</span>}This code is one of the ways of applying conditional logic in React. This essentially means if isSpicy is true, render a span element.

Lastly, update the map method in the Menu component to use the available array rather than the burgers array.

const Menu = () => {
  return (
    <div className="menu-container">
      {availableBurgers.map((burger, index) => (
        <Burger
          key={index}
          name={burger.name}
          description={burger.description}
          isSpicy={burger.isSpicy}
        />
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now your app looks like this:

react app

We can see that the spicy badge has been added to the spicy burgers and now we are only showing the available burgers.

Other examples of Conditional Rendering

If the burgers array didn't have any data, we would have a blank page and this is not a good user experience. To prevent that we can create a condition that checks if the length of the burgers array is equal to 0 and if it is, we just display a message like “ No burgers available at the moment. Please check back later!

const Menu = () => {
  if (availableBurgers.length === 0) {
    return <p>No burgers available at the moment. Please check back later!</p>;
  }
  return (
    <div className="menu-container">
      {availableBurgers.map((burger, index) => (
        <Burger
          key={index}
          name={burger.name}
          description={burger.description}
          isSpicy={burger.isSpicy}
        />
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Even though JSX looks similar to HTML markup, there are some key differences you will encounter when working with different elements. For example, let’s look at the input and image elements.

<>
  <img src=" " alt="A cute brown cat on white snow" />
  <input type="text" placeholder="Type something..." />
</>
Enter fullscreen mode Exit fullscreen mode

JSX Tags must be Closed

Both <img> and <input> are examples of self-closing tags. In HTML, it is okay to leave self-closing tags open e.g (<img> ), however in JSX, you must explicitly close them by adding a slash (/) before the closing >.

If you don't close these elements, you will get an error like this:

Ireact error

// Incorrect:
<img>

// Correct:
<img />
Enter fullscreen mode Exit fullscreen mode

JSX Attributes must use CamelCase

When you need to respond to an event on a button, you typically pass the function as shown below for an onclick event.

<button onclick="handleClick()">Click me</button>
Enter fullscreen mode Exit fullscreen mode

In HTML, onclick is written in all lowercase. When a user clicks the button, the handleClick function will be called.

However, in JSX, attributes that are Multi-word or part of the JavaScript language must be written in camelCase. So the equivalent JSX will look like this:

function handleClick() {
  alert("Hello");
}

export default function App() {
  return (
    <button onClick={() => handleClick("Hello")}>Click me</button>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the code above, onClick is written in camelCase instead of onclick, and the function handleClick is wrapped in curly braces to indicate that it is a JavaScript expression.

You might also have noticed that the function handleClick has not been invoked when passed to the onClick attribute. This is because when the component renders, we don't want to invoke the function immediately.

In JSX, when handling event handlers, the function should be passed as a reference without parentheses to avoid immediate invocation on component mount.

What if you need to pass arguments to the function? In this case, you use an arrow function to call the handler with the desired arguments.

function handleClick(message) {
  alert(message);
}

export default function App() {
  return (
    <button onClick={() => handleClick("Button clicked!")}>Click me</button>
  );
}

Enter fullscreen mode Exit fullscreen mode

Conditionals in JSX

In JSX, conditional statements like if, for, switch, etc. are not allowed directly inside JSX markup. If you need to perform conditional rendering, you can use ternary operators or logical operators to conditionally render based on certain conditions.

// Incorrect:
if (isLoggedIn) {
  return <h1>Welcome</h1>;
}

// Correct (using ternary operator):
{isLoggedIn ? <h1>Welcome</h1> : <h1>Please Log In</h1>}
Enter fullscreen mode Exit fullscreen mode

State Management in React

In this section, we will learn about state and how React uses it to update all areas of the application."

We will start with a simple counter example. The purpose of this counter is to increment and decrement numbers when a button is clicked.

Create a new React application and in the App.js file, add the code below.

import "./styles.css";
import { useState } from "react";

export default function App() {
 const [count, setCount] = useState(0);

 return (
   <div className="counter">
     <button>-</button>
     <span className="counter-number">{count}</span>
     <button>+</button>
   </div>
 );
}
Enter fullscreen mode Exit fullscreen mode

The counter now looks like this:

counter app react

In React, state is used to manage dynamic content. To create a state variable, we use the useState function. Whose syntax looks like this:

const [value, setValue] = useState(initialValue)
Enter fullscreen mode Exit fullscreen mode

Where value is the current state, setValue is the function used to update the state, and initialValue is the initial value of the state. The initial value can be empty, an array, an object, or a string.

In our example, count is the current value of the state, and its initial value is 0. setCount is the function used to update the state.

To update the counter, we need to add onClick event handlers to the buttons. The left button will call a function to decrease the counter, while the right button will call a function to increase the counter by 1.

To update the new state when a button is clicked, you can do so directly by calling the setter function as follows:

setValue(value)
Enter fullscreen mode Exit fullscreen mode

Use the State in JSX

Let's update the state directly within our button using the setCount function within the onClick event handler.

<div className="counter">
    <button onClick={() => setCount(count - 1)}>-</button>
    <span className="counter-number">{count}</span>
    <button onClick={() => setCount(count + 1)}>+</button>
</div>
Enter fullscreen mode Exit fullscreen mode

When you click any of the buttons, the counter will change. Under the hood, when the state changes, it causes the component to re-render. Even though you don't see the page refresh, React does a good job of updating only the parts of the DOM that need to change.

State management in Forms

Working with forms can be a challenging process. You need to create the correct inputs for the data, perform validation on both the front end and back end, and ensure that the submitted data is valid and adheres to the required format and length.

In a traditional HTML form, data is typically collected from the input fields after the user clicks the submit button. However, working with forms in React is a bit different and slightly more complex. In React, every change in the input data must be captured and maintained in the component's state.

By the time the user clicks the submit button, your application already has the input data stored in its state.

This approach offers some benefits mainly:

  • Immediate feedback: Since the state handles the submitted data, validation can be done immediately before the user even submits the form.

  • State controlled: In React, we often hear the terms controlled vs uncontrolled input. In controlled input, as the name suggests, the input value is derived from the component's state meaning that every change in the input updates the state.

A simple input markup looks like this:

  <input type="text" id="name" name="name" value="John">
Enter fullscreen mode Exit fullscreen mode

In this traditional HTML form, we have the value “John,” which has been hardcoded. However, in the React context, inputs are controlled elements, meaning that their values are managed by the state. Therefore, we will bind the input value to a state variable and handle changes to the value through state.

In React, the input will look like this:

 <input type="text" id="name" name="name" value={name} onChange={(e) => setName(e.target.value)} />
Enter fullscreen mode Exit fullscreen mode

Assuming we have a name state

const[name, setName] = useState()
Enter fullscreen mode Exit fullscreen mode

Let’s create a simple Login form with username and password fields in the App.js file.

import "./styles.css";

export default function App() {
  return (
    <div className="login-container">
      <form className="login-form">
        <h2>Login</h2>
        <div className="input-group">
          <label htmlFor="username">Username</label>
          <input type="text" id="username" name="username" />
        </div>
<div className="input-group">
          <label htmlFor="password">Password</label>
          <input type="password" id="password" name="password" />
        </div>
        <button type="submit" className="login-button">
          Login
        </button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

How State Works in Forms

In React, the value attribute of an input field is controlled by the component's state. This means that whatever is in the initial state will be the value shown in the input field. For example, let's create a state for both of our input fields:

First, import the useState function at the top of the file.

import { useState } from "react";
Enter fullscreen mode Exit fullscreen mode

Intitialise state variables for username and password.

const [username,setUsername] = useState("john2024")
const [password,setPassword] = useState("1234")
Enter fullscreen mode Exit fullscreen mode

Bind the value to the state

<input type="text" id="username" name="username" 
value = {username}  placeholder = "username"/>


<input type="password" id="password" name="password"
value = {password}
placeholder = "password"/>
Enter fullscreen mode Exit fullscreen mode

Once we bind the value to the state, we can see that the values shown on the input fields are from the state.

The next step is to update the state when the input value changes. This is done by adding the onChange event handler which listens for changes to the input field. As the user types in the input, the event is triggered and the state is updated by the setter function.

This update happens in real-time and is reflected in the input field

Let's add onChange handlers to both our input fields:

<input
  type="text"
  id="username"
  name="username"
  value={username}
  placeholder="username"
  onChange={(e) => setUsername(e.target.value)}
/>

<input
  type="password"
  id="password"
  name="password"
  value={password}
  placeholder="password"
/>
Enter fullscreen mode Exit fullscreen mode

It’s recommended to set the initial value of the state for an input field to an empty string, so let's do that.

const [username, setUsername] = useState(" ");
const [password, setPassword] = useState(" ");
Enter fullscreen mode Exit fullscreen mode

Now the input fields are controlled by the state, and we are in a position to submit form data. Let's do that. Add an onClick event handler to the submit button.

<button onClick={submitData} type="submit" className="login-button">
       Login
</button>
Enter fullscreen mode Exit fullscreen mode

Create the submitData function and log the form data to the console.

function submitData(e) {
   e.preventDefault();
// send data to server

   console.log(`My username is ${username} and password
   is ${password} `);
 }
Enter fullscreen mode Exit fullscreen mode

Here is the output:

react state

In React, it's important to understand that state updates are asynchronous and are typically batched together for performance optimization

Lifting State in React

Since React is a component-based framework, it’s common to create independent components that use their own state. However, in many cases, multiple components need to share or synchronize data. When this happens, the state must be lifted up to a common parent component so that it can be passed down as props to child components. This process is known as lifting state.

An example that comes to mind is a Modal, If we create an independent modal, but the button for opening the modal lives in the parent component, we have to lift the state on the modal into the parent component so as to effectively manage it.

Suppose we have a Modal component that looks like this:

import React, { useState } from "react";
import styles from "./Modal.module.css";
export default function Modal(props) {
const [isOpen, setIsOpen] = useState(false);

 const handleOpen = () => {
   setIsOpen(true);
 };

 const closeModal = () => {
   setIsOpen(false);
 };

 return (
   <>
     <button onClick={handleOpen}>Open Modal</button>
     <div>
       {isOpen && (
         <div className={styles.overlay}>
           <div className={styles.modal}>
             <h2 className={styles.title}>Simple Modal</h2>
             <p className={styles.content}>
               This is a simple modal component  </p>
             <button onClick={closeModal} className={styles.closeButton}>
               Close Modal
             </button>
           </div>
         </div>
       )}
     </div>
   </>
 );
}
Enter fullscreen mode Exit fullscreen mode

In this code, we have a state that controls the opening and closing of a modal. When isOpen is true, the modal is shown; if it is false, the modal is not displayed.

However, we have a problem: we want the button for opening the modal to reside in the App.js component.

The App component cannot change the state of the modal since it has no access to the state. In such a case, we have to lift the state from the Modal component and put it in the App component.

This will allow us to add an Onclick event which will trigger a state change.

Let's start by moving the isOpen state and the functions that control the modal to the App component.

function App() {
 const [isOpen, setIsOpen] = useState(false);
const handleOpen = () => {
   setIsOpen(true);
 };

 const closeModal = () => {
   setIsOpen(false);
 };


 return (
   <>
     <button >Open Modal</button>
     <Modal isOpen={isOpen} closeModal={closeModal} />

   </>
 );
}
Enter fullscreen mode Exit fullscreen mode

The next step is to modify the Modal component so that it receives the state (isOpen) and the closeModal function from the App component as props.

export default function Modal({isOpen, closeModal }) {

 return (
   <>

     <div>
       {isOpen && (
         <div className={styles.overlay}>
           <div className={styles.modal}>
             <h2 className={styles.title}>Simple Modal</h2>
             <p className={styles.content}>
               This is a simple modal component.
             </p>
             <button onClick={closeModal} className={styles.closeButton}>
               Close Modal
             </button>
           </div>
         </div>
       )}
     </div>
   </>
 );
}
Enter fullscreen mode Exit fullscreen mode

Lastly, attach an event listener to the open Modal function so that it will be responsible for opening the modal.

<button onClick={handleOpen}>Open Modal</button>
Enter fullscreen mode Exit fullscreen mode

Now that the App component controls the state of the Modal, and is also the single source of truth for the state of the Modal, it allows us to share the state with multiple child elements and also control the state from different parts of our application.

Managing Side Effects with the useEffect Hook

Side effects in React are operations that affect other components and for which React’s built-in rendering logic can't handle on its own. Some common examples of these side effects include

  • API requests
  • Local storage
  • Event listeners
  • Subscriptions such as web sockets, e.t. c

Let's look at a few examples.

API Requests

When you need to fetch data from an API in React, it creates a side effect since it involves asynchronous operations. Since React components render on mount, an asynchronous operation may not be completed in time before the component's initial render. In such cases, we use the useEffect hook to perform API interactions.

Suppose you need to fetch data from an API, such as a placeholder that returns some mock data. Let’s create a useEffect for that. In your App.js file, start by importing the useEffect hook."

import { useState,useEffect } from "react";
Enter fullscreen mode Exit fullscreen mode

Next, define it

useEffect(()=>{

//put side effect code here
])
Enter fullscreen mode Exit fullscreen mode

So for example in our case, we need to do a fetch request, let's do that inside the useEffect hook.

function App() {

 useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/todos/1')
     .then((res) => res.json())
     .then((data) => {
       console.log(data);
     });
 });
Enter fullscreen mode Exit fullscreen mode

Now, every time the app renders, the data will be fetched and logged to the console.

The dependency array

React offers another argument to the useEffect hook, which is the dependency array. The dependency array determines what causes the useEffect to run. If you don’t want the useEffect to run on every re-render, you can specify the dependency array.

For example, if we want the useEffect to run on component mount, we will specify an empty dependency array, as shown below

useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/todos/1')
     .then((res) => res.json())
     .then((data) => {
       console.log(data);
     });
 },[]);
Enter fullscreen mode Exit fullscreen mode

If you want the use Effect to run when something like state changes, you will add the state to the dependency array,

For example, if we have a user state and we want it to control when the useEffect runs, we would have something like this:

function App() {

 const [user, setUser] = useState({"name":"Ankit","age":25});


 useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/todos/1')
     .then((res) => res.json())
     .then((data) => {
       console.log(data);
     });
 },[user]);
Enter fullscreen mode Exit fullscreen mode

Local Storage

Reading and writing to local storage is a side effect and should be handled in a similar manner using the useEffect hook.

Consider this example where we have a simple note-taking app where a user adds notes, and the notes are then displayed on the page.

function App() {
  const[note,setNote]=useState("");
  const[notes,setNotes]=useState([]);
  console.log(notes);

  return (
    <>
     <input type="text"  value={note} onChange={(e)=>setNote(e.target.value)}/>
     <button onClick={()=>setNotes([...notes,note])}>Add Note</button>

     {notes? notes.map((note,i)=>{
       return <div key={i}>{note}</div>
     }):null}

    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

When a user refreshes the app, all the notes will disappear. To ensure that all the notes are displayed even after a refresh, we will store the notes in local storage.

Add a useEffect hook to save the current notes to local storage whenever the note state changes. In this case, our dependency array will be note. This means that every time a user adds a new note, the note will be saved to local storage.

useEffect(()=>{
  localStorage.setItem("notes",JSON.stringify(notes));
},[notes])
Enter fullscreen mode Exit fullscreen mode

Then we need to set the initial state of the notes to be the notes from local storage.

const[notes,setNotes]=useState(localStorage.getItem("notes")? JSON.parse(localStorage.getItem("notes")):[]);
Enter fullscreen mode Exit fullscreen mode

Here we are using a ternary operator which will first check if the item notes exists in local storage and if it does, the array will be used as the initial value, if it doesnt exist, the initial state will be an empty array.

Here is the full code for managing side effects in React

import { useState,useEffect } from "react";
import "./App.css";


function App() {
 const[note,setNote]=useState("");
 const[notes,setNotes]=useState(localStorage.getItem("notes")? JSON.parse(localStorage.getItem("notes")):[]);

 useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/todos/1')
     .then((res) => res.json())
     .then((data) => {
       console.log(data);
     });
 },[]);

useEffect(()=>{
  localStorage.setItem("notes",JSON.stringify(notes));
},[notes])
function saveNote() {
 if (note.trim()) {
   setNotes((prevNotes) => [...prevNotes, note]);
   setNote("");
 }
}
 return (
   <>

    <input type="text"  value={note} onChange={(e)=>setNote(e.target.value)}/>
    <button onClick={saveNote}>Add Note</button>

    {notes? notes.map((note,i)=>{
      return <div key={i}>{note}</div>
    }):null}
   </>
 );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You've just completed an introductory journey into the world of React.

Top comments (0)