DEV Community

steffanboodhoo
steffanboodhoo

Posted on

React Quickstart Tutorial

React Quickstart Tutorial

Contents

  1. Inspiration
  2. Prerequisites
  3. Setup
  4. The React Component
  5. Events
  6. State
  7. Hooks
  8. Routing
  9. Extra Bits
  10. Sample Network Request and Render

Inspiration

I like using React, building reuseable blocks of code ( Components ) in your own style is quite a fun and stimulating experience. You may meet several react persons who use the framework but their code can be drastically different ( which can also be a very bad thing ), but I like to find my own way of doing things and react allows that.

Prerequisites

To start you'll need Node, npm and preferably npx, you can skip to the next section if you already have these installed ( next section )

Installing Node

OPTION A: (Recommended NVM ( Node Version Manager)

It's generally recommended that you use nvm to install and manage versions of node. You can see instructions on how to install for you OS here. Definitely use the above link if you can, however if not you can try running these...

install via curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
reload your terminal
source ~/.bashrc
check the install
nvm -v
use nvm to install a version of node ( e.g. 16 )
nvm install 16
OR
use nvm to install the latest version of node
nvm install node

use nvm to use a version of Node it installed ( e.g. 16 )
nvm use 16

OPTION B: Direct Install

You can visit here for installation instructions for your specific OS.

npm and npx

npm and npx are usually installed alongside node, you can test with npm --version and npx --version.

Note: Node, npm and npx are all different things, Node is the execution environment ( basically the thing that runs the code ); npm, Node Package Manager, manages packages for node; npx, Node Package Execute, allows us to run installed node packages. The versions of each of these things are (mostly) independent and therefore when you run npm --version or node --version or npx --version DON'T EXPECT to see the same number.

Depending on which option you chose npx may not be installed, as such you can run the following:

install npx globally ( DO NOT RUN IF YOU ALREADY HAVE npx INSTALLED, again check with npx --version )
npm install -g npx

Setup

Files and Core Dependencies

Let's create a folder react_app, and inside react_app create a src folder and a public folder, inside public create an index.html file and inside src create an index.js file.

Edit index.html and index.js to reflect the following:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>React Tutorial App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

index.js

console.log('Hello I\'m working');
Enter fullscreen mode Exit fullscreen mode

Now let's initialize our package management

npm init -y

Now let's install our core dependencies

npm install --save react react-dom

Your structure should look something like

react_app
  |- /node_modules
    |- ...
  |- package.json
  |- /public
    |- index.html
  |- /src
    |- index.js
Enter fullscreen mode Exit fullscreen mode

React Scripts

react-scripts is a tool we will use to run and build our react code. The browser doesn't actually understand react, we can use react-scripts to create a development server which would transpile and serve our code in the browser while constantly watching for changes we make and reloading those bits. We will also use react-scripts to build a bundled app that we can deploy, for now let's install

npm install --save-dev react-scripts

Now for react scripts to work, it needs at minimum, a specific structure and some specifications in our package.json. For the structure it expects a public folder with an index.html file and a src folder with an index.js. As for the specifications, we have to say which browser(s) we're going to use to develop and build to support. We will add these specifications after the devDependencies section in our package.json

,"browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
}
Enter fullscreen mode Exit fullscreen mode

The development subsection is fairly obvious, however you can read about the browserslist production values here.

Your package.json should look something like this ( exact values WILL DIFFER DO NOT COPY )

{
  "name": "react_app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "react-scripts": "^5.0.1"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
   }
}
Enter fullscreen mode Exit fullscreen mode

Now let's start our development server, navigate to the react_app folder and run

npx react-scripts start

Your browser should open to localhost:3000, with index.html loaded and index.js injected for us, so if you open the console you'll see our log 'Hello I\'m working'.

Note: There is a tool create-react-app that could have been used to automatically create our initial structure however when I first started out it felt a bit overwhelming and often conflated my understanding of things operated. When learning it feels much better to take things one step at a time rather than having to decipher a bunch of boilerplate code.

The React Component

JSX

What is JSX ? Well it stands for Javascript XML but we can basically think of it as Javascript in HTML... in Javascript. As a dumb example, think about how you would put some stored text input text_variable in a <div> tag.

Normally you would do something like add an id to the tag <div id='some_reference'> then grab the element using document.getElementById('some_reference') and then do something like set it's innerHTML to text_variable.

With JSX if we want to put text_variable in a <div>, we just put it

<div>{text_variable}</div>
Enter fullscreen mode Exit fullscreen mode

With JSX we can put any Javascript Expression directly into HTML by putting it into curly braces. ( Javascript Expression = any javascript code that resolves to some value ). How does this concept of Javascript Expressions in HTML helps us ? Well now we can use html almost as a template whose contents are created by our logic and data, this is the basis of a component.

What is a Component

Components are the building blocks of react, a component can be conceptualized as a custom element that you create. This 'custom element' or component usually structured as accepting some data input and returning some JSX ( recall JSX allows us to build a template whose contents we manipulate via javascript ).

As a quick example here's a CalculatorComponent that accepts two parameters; two numbers num_1, num_2 and then returns JSX that displays the sum.

const CalculatorComponent = (params) => {
    const { num_1, num_2 } = params;    // we are pulling out the two arguments we passed in num_1 and num_2, the name params is not important and can be changed
    return (<div>
        {num_1 + num_2}
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Now we can use our component almost like any other element like <CalculatorComponent/> we can pass in our values similar to how normal attributes are passed in to html elements like <CalculatorComponent num_1={3} num_2={4} />. Now that we have some idea about components let's actually put it into action.

Rendering React

Let's finally render our first component, to do that we'll need to use the core react libraries react and react-dom. To render react we need to (1) find a place on the DOM where we want to render our component(s) (2) actually load our component into that place. Let's do it using our CalculatorComponent

edit your index.js to reflect the following:

import React from 'react';
import { createRoot } from 'react-dom/client';

console.log('Hello I\'m working');

const CalculatorComponent = (params) => {
    const { num_1, num_2 } = params;    // we are pulling out the two arguments we passed in num_1 and num_2, the name params is not important and can be changed
    return (<div>
        {num_1 + num_2}
    </div>)
}

const root_element = document.getElementById('root');
const react_root = createRoot(root_element);
react_root.render(<CalculatorComponent num_1={3} num_2={4} />);
Enter fullscreen mode Exit fullscreen mode

Once you have saved you should see a '7' appear on your browser, congratulations you've created your first react app. let's talk a bit about what's going on, first our imports; without getting into the mess of things React from 'react' is used to construct our Component and { createRoot } from 'react-dom/client' is used to load our component onto the page. We then define our component CalculatorComponent using the code from before, grab the empty div identified by root ( see index.html ), create the root or base of our react application then finally render our component using the created root/base.

App Structure

This was a very simple example using one file, however it's not very realistic, let's see how we can split our code across multiple files using some established conventions ( this in most cases is how you should structure and load your app ).

First let's seperate our CalculatorComponent in it's own file Calculator.js inside our src folder and make some minor modifications

import React from 'react';

export const Calculator = (params) => { // no need for the word 'Component' to be attached, it's already understood
    const { num_1, num_2 } = params;    // we are pulling out the two arguments we passed in num_1 and num_2, the name params is not important and can be changed
    return (<div>
        {num_1 + num_2}
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Now let's create a component that will be used as the root of our application where we will load all other React components, we'll call the component App, create a new file App.js inside src and add the foll:

import React from 'react';
import { Calculator } from './Calculator';

export const App = () => {

    return (<div>
        <Calculator num_1={3} num_2={4} />
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Explanation: Our App component imports our Calculator component from Calculator.js and uses it with num_1 as 3 and num_2 as 4

Finally let's modify our index.js to render our root/base component App

import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';

console.log('Hello I\'m working');

const root_element = document.getElementById('root');
const react_root = createRoot(root_element);
react_root.render(<App/>);
Enter fullscreen mode Exit fullscreen mode

Your file structure should look like the foll:

react_app
  |- /node_modules
    |- ...
  |- package.json
  |- /public
    |- index.html
  |- /src
    |- index.js
    |- App.js
    |- Calculator.js
Enter fullscreen mode Exit fullscreen mode

Once saved you should see the result rendered on your page.

Events

DOM and VDOM

The DOM is a representation of an HTML document that facilitates it's manipulation. For example when we call document.getElementById we retrieve a DOM node which we then use to apply changes to the document. Without going into too much depth, react creates it's own version of the DOM called the virtual DOM ( or VDOM ). The VDOM is used to optimize rendering, i.e. instead of replacing the entire DOM, react compares the DOM and it's VDOM and only changes what's needed in the DOM to reflect new changes. This bit is a bit beyond this tutorial, you can read more about these concepts here and here.

Synthethic Events

Since when using react we don't use the DOM directly but a representation of it, we can't use native DOM events (e.g. onclick) but rather synthethic events that react provides for us (e.g. onClick). Secondly since we're using JSX i.e. using components to create HTML in our javascript code, when we pass functions to these events we pass the function itself rather than a string.

Traditionally it may have looked something like this

<button onclick='handleOnClick'> 
    Click 
</button>
Enter fullscreen mode Exit fullscreen mode

In react using JSX we have

<button onClick={handleOnClick}> 
    Click 
</button>
Enter fullscreen mode Exit fullscreen mode

Again, note onclick is the native DOM event which we replaced by react's synthethic event onClick, case being the only difference (lowercase vs cammel case), this is done by design to make things easy to remember yet distinct; and secondly instead of using a string of the function we pass in the function itself ( again JSX ).

State

useState

State, simplified, is variables. State within your app can be thought of then as, all the data currently loaded within your application. Let's zoom in a bit, to the state for a component i.e. the data / variables within the component. State can be thought of as the core react, why ? Components update their content ( or react to ) the data within them. Therefore when working with data within a component i.e. when we create 'variables' or state, we have to do so in a way react can keep track of it. We create these 'variables' or state by calling useState function.

When we call useState there are 3 things to note; (1) a react variable, (2) a function to update this react variable, and (3) what the default value of this react variable should be. Let's see a quick example of useState, we will use it to keep track of a count

const [count, updateCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode

In the example, (1) count is the special react variable, (2) updateCount is the function we use to update the value of count, and 0 is count's initial value.

REACTing to state

In order to fully appreciate how state works, we need to actually use it. let's create a component to count based on user inputs, we'll call it Counter and create it in Calculator.js.

Edit Calculator.js to reflect the foll:

import React, { useState } from 'react';

export const Calculator = (params) => { // no need for the word 'Component' to be attached, it's already understood
    const { num_1, num_2 } = params;    // we are pulling out the two arguments we passed in num_1 and num_2, the name params is not important and can be changed
    return (<div>
        {num_1 + num_2}
    </div>)
}

export const Counter = () => {
    const [count, updateCount] = useState(0);// useState creates our variable 'count' and a function 'updateCount' to update our variable;

    const handleCountBtnClick = (ev) => {
        updateCount(count + 1); // a replacement for count = count + 1
    }

    return (<div>
        Clicked {count} times.
        <button onClick={handleCountBtnClick}> Click</button>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Now all let's add Counter to our App component, edit App.js to reflect the following:

import React from 'react';
import { Calculator, Counter } from './Calculator';

export const App = () => {

    return (<div>
        <Counter />
        <Calculator num_1={3} num_2={3} />
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Your page should automatically refresh with our Counter component loaded, now whenever you click the button, the count should increase.

Hooks

Hooks

Hooks are a bunch of functions that allows us to use react features easily. useState is actually an example of a hook, as seen it allows us to create special react state that our components use update it's content.

useEffect

useEffect is the next most popular hook, it allows us to perform 'effects' between changes in specific states. useEffect has two bits to take note of, (1) the functionality or 'effect' we want run, and (2) the pieces of state we want to run the 'effect' inbetween.

As an example, let us modify our Calculator to take in two user inputs num_1, num_2 and an operator operator, our calculator will work such that if num_1, num_2 or operator changes we will attempt to recalculate the result live. To do this, of course we will use a useEffect, the effect will be calculating a result, and the pieces of state we will be observing will be num_1, num_2 and operator because if any of these changes then we will need to recalculate the result.

import React, { useState, useEffect } from 'react';

export const Calculator = (params) => { // no need for the word 'Component' to be attached, it's already understood
    const [num_1, updateNum1] = useState(0);
    const [num_2, updateNum2] = useState(0);
    const [result, updateResult] = useState('0')
    const [operator, updateOperator] = useState('+');

    const calculate = () => {
        let updated_result = '';
        if (operator == '+')
            updated_result = num_1 + num_2;
        else if (operator == '-')
            updated_result = num_1 - num_2;
        else if (operator == '/')
            updated_result = num_1 / num_2;
        else if (operator == '*')
            updated_result = num_1 * num_2;
        else
            updated_result = 'Invalid Operator';

        updateResult(updated_result);
    }

    useEffect(calculate, [num_1, num_2, operator]);

    const handleOnChange = (ev, field) => {
        const new_value = ev.target.value;
        if (!new_value) // if input changed to nothing / null, then don't update anything
            return;
        if (field == 'num_1')
            updateNum1(parseInt(new_value));
        else if (field == 'num_2')
            updateNum2(parseInt(new_value));
        else
            updateOperator(new_value)
    }

    return (<div>
        <input type='number' defaultValue='0' onChange={ev => handleOnChange(ev, 'num_1')} />
        <input type='character' defaultValue='+' onChange={ev => handleOnChange(ev, 'operator')} />
        <input type='number' defaultValue='0' onChange={ev => handleOnChange(ev, 'num_2')} />
        =
        {result}
    </div>)
}

export const Counter = () => {
    const [count, updateCount] = useState(0);// useState creates our variable 'count' and a function 'updateCount' to update our variable;

    const handleCountBtnClick = (ev) => {
        updateCount(count + 1); // a replacement for count = count + 1
    }

    return (<div>
        Clicked {count} times.
        <button onClick={handleCountBtnClick}> Click</button>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Let's take a minute to dissect what's here, in order;

First we used useState 4 times to create 4 pieces of state, functions to update them, and gave them default values.

    const [num_1, updateNum1] = useState(0);
    const [num_2, updateNum2] = useState(0);
    const [result, updateResult] = useState('0')
    const [operator, updateOperator] = useState('+');
Enter fullscreen mode Exit fullscreen mode

Then we created a calculate function, which uses num_1, num_2 and operator to calculate and update result.

    const calculate = () => {
        let updated_result = '';
        if (operator == '+')
            updated_result = num_1 + num_2;
        else if (operator == '-')
            updated_result = num_1 - num_2;
        else if (operator == '/')
            updated_result = num_1 / num_2;
        else if (operator == '*')
            updated_result = num_1 * num_2;
        else
            updated_result = 'Invalid Operator';

        updateResult(updated_result);
    }
Enter fullscreen mode Exit fullscreen mode

We then used a useEffect to say anytime num_1, num_2, or operator changes run the calculate function, as shown useEffect is a function call that accepts 2 things, (1) the functionality or 'effect' we want run in this case calculate, and (2) the states we want to observe or rather states which affect our 'effect' in this case num_1, num_2, and operator.

useEffect(calculate, [num_1, num_2, operator]);
Enter fullscreen mode Exit fullscreen mode

The rest are things we've already gone over, handleOnChange is a function we create to handle the changing of something, it accepts the actual change event ev as well as some identifying keyword state_name, it uses the event ev to fetch the current entered and based on the keyword state_name we update the relevant piece of state.

    const handleOnChange = (ev, state_name) => {
        const new_value = ev.target.value;
        if (!new_value) // if input changed to nothing / null, then don't update anything
            return;
        if (state_name == 'num_1')
            updateNum1(parseInt(new_value));
        else if (state_name == 'num_2')
            updateNum2(parseInt(new_value));
        else
            updateOperator(new_value)
    }
Enter fullscreen mode Exit fullscreen mode

Finally we have the JSX where we define our inputs to call our handleOnChange function by attaching it to react's synthethic onChange event, however we wrap this function call in an anonymous function so that we can pass a specific keyword for each input.

    return (<div>
        <input type='number' defaultValue='0' onChange={ev => handleOnChange(ev, 'num_1')} />
        <input type='character' defaultValue='+' onChange={ev => handleOnChange(ev, 'operator')} />
        <input type='number' defaultValue='0' onChange={ev => handleOnChange(ev, 'num_2')} />
        =
        {result}
    </div>)
Enter fullscreen mode Exit fullscreen mode

Routing

Why Have Routes ?

Modern frontend frameworks operate on the basis that the entire app operates on a single page ( single page apps ). However, we still like the illusion of routing to different pages ( this can also be useful to the user since they often identify and navigate directly to a specific view by typing in the route ). It is entirely possible ( not recommended ) to build your own routing system however there's also react-router-dom which is the defacto routing solution used for react.

Basic React Router

react-router-dom is a library that provides routing for react. To get started lets install react-router-dom run

npm install react-router-dom

To get started we need to the root of our application in a Component from react-router-dom called BrowserRouter, let's modify our index.js to reflect the foll:

import React from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { App } from './App';

console.log('Hello I\'m working');

const root_element = document.getElementById('root');
const react_root = createRoot(root_element);
react_root.render(<BrowserRouter>
    <App />
</BrowserRouter>);
Enter fullscreen mode Exit fullscreen mode

Now let's modify App to have two routes, /counter for our Counter and /calculator for Calculator, to do this we'll need to use the Routes and Route components from react-router-dom. Routes is where we initialize the routes for our application, it will contain all the Route components for our app. Each Route component is simply a path e.g. /calculator and what to render e.g. <Calculator/> let's edit App.js to see these in action :

import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { Calculator, Counter } from './Calculator';

export const App = () => {

    return (<div>
        <Routes>
            <Route path='/counter' element={<Counter />} />
            <Route path='/calculator' element={<Calculator />} />
        </Routes>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Now when you visit /counter you'll see our Counter component and when you visit /calculator you'll see our calculator component (easy right!).

Remember this is an illusion of different routes, we will not actually serve different pages; visting /calculator loads the same page, the same code, but the Component specific to /calculator; simply put BrowserRouter must read the state of the browser and load the required Component / View. However there are many more things that BrowserRouter does for us right out of the box, a quick example is keeping track of where the user has visited and facilitating backwards and forwards navigation between routes. Again remember these routes aren't real we are never leaving the page so there isn't anyting to go back or forward to. You can read more about react router here.

Routing Necessities

You'll very quickly notice, that the base of our application has nothing loaded i.e. if you go to localhost:3000 you'll see nothing, this is because we do not have a Route for our base path /, therefore nothing will load, there are a few options we will explore

OPTION 1: The most obvious let's just add Route and choose a component e.g. Calculator,

import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { Calculator, Counter } from './Calculator';

export const App = () => {

    return (<div>
        <Routes>
            <Route path='/' element={<Calculator />} />
            <Route path='/counter' element={<Counter />} />
            <Route path='/calculator' element={<Calculator />} />
        </Routes>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

This works fine, Components are meant to be reusable so no problems here, but a bit crude

OPTION 2: If we don't have something for a particular route e.g. / we can redirect them to one, let's redirect / to calculator

import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Calculator, Counter } from './Calculator';

export const App = () => {

    return (<div>
        <Routes>
            {/* <Route path='/' element={<Calculator />} /> */}
            <Route path='/' element={<Navigate to='/calculator' replace={true} />} />
            <Route path='/counter' element={<Counter />} />
            <Route path='/calculator' element={<Calculator />} />
        </Routes>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Again works fine, demonstrates how to render a redirect such within BrowserRouter so BrowserRouter can keep track of where user has been.

OPTION 3: Create a new component that acts as a menu

In src create a new file Menu.js and add the following:

import React from 'react';
import { Link } from 'react-router-dom';

export const Menu = () => {

    return (<div>
        Most desolate menu in the world
        <ul>
            <li>
                <Link to='/calculator'>Calculator (  ʖ̯  ) </Link>
            </li>
            <li>
                <Link to='/counter'>Counter _ </Link>
            </li>
        </ul>

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

Now edit App.js

import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Calculator, Counter } from './Calculator';
import { Menu } from './Menu';

export const App = () => {

    return (<div>
        <Routes>
            {/* <Route path='/' element={<Calculator />} /> */}
            {/* <Route path='/' element={<Navigate to='/calculator' replace={true} />} /> */}
            <Route path='/' element={<Menu />} />
            <Route path='/counter' element={<Counter />} />
            <Route path='/calculator' element={<Calculator />} />
        </Routes>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Once you've saved, the base route will now render our very ugly menu Menu. React Router has a lot more and very good documentation, please give it a read through if you ever find yourself amiss with routing.

Extra Bits

Conventions

A lot of the code I wrote was done to maximize readability however in practice there are some things that are common place.

Param Destructuring

This is how we access properties / parameters for a component, let's look back at the first version of <Calculator\>, for reference

const CalculatorComponent = (params) => {
    const { num_1, num_2 } = params;    // we are pulling out the two arguments we passed in num_1 and num_2, the name params is not important and can be changed
    ...
}
Enter fullscreen mode Exit fullscreen mode

We accepted an object we named 'params' and then proceeded to destructure and pull our num_1 and num_2 however in practice the norm is to destructure in the method signature / parameter list itself like so

const CalculatorComponent = ({num_1, num_2}) => { // we are expecting two properties to be passed, called exactly `num_1` and `num_2`, we can therefore pull them out immediately
    ...
}
Enter fullscreen mode Exit fullscreen mode

useEffect

When we used the useEffect we created a function calculate to pass into the useEffect
reference

const calculate = () => {
        let updated_result = '';
        if (operator == '+')
            updated_result = num_1 + num_2;
        else if (operator == '-')
            updated_result = num_1 - num_2;
        else if (operator == '/')
            updated_result = num_1 / num_2;
        else if (operator == '*')
            updated_result = num_1 * num_2;
        else
            updated_result = 'Invalid Operator';

        updateResult(updated_result);
    }

    useEffect(calculate, [num_1, num_2, operator]);
Enter fullscreen mode Exit fullscreen mode

However the 'effects' or functionality in useEffects are usually only meant to be triggered in the useEffect so people usually use an anonymous function or rather ES6's version, an unassigned arrow function, and write the functionality directly in the body

    useEffect(()=>{
        let updated_result = '';
        if (operator == '+')
            updated_result = num_1 + num_2;
        else if (operator == '-')
            updated_result = num_1 - num_2;
        else if (operator == '/')
            updated_result = num_1 / num_2;
        else if (operator == '*')
            updated_result = num_1 * num_2;
        else
            updated_result = 'Invalid Operator';

        updateResult(updated_result);
    }), [num_1, num_2, operator]);
Enter fullscreen mode Exit fullscreen mode

As you can see the body of the functions are exactly the same, the only difference is we just wrote it directly in the useEffect using an unassigned arrow function.

Sample Network Request and Render

As a quick example of how we can do network requests and render the results I'm going to fetch art pieces using The Art Institute of Chicago API.
Let's begin by installing axios to make making requests easier.

npm install --save axios

Now create an Art.js in src, we'll have two components Art being the main component and ArtPiece being an individual art piece. The code here will be a bit closer to how things would usually be done

import Axios from 'axios';
import React, { useRef, useState } from 'react';

export const Art = () => {
    const [art_data, updateArtData] = useState([]);
    const searchInput = useRef(null); // holds a reference to an element

    const handleSearchArt = (ev) => {
        const title = searchInput.current.value; // similar to title = document.getElementById('search-text-input').value;
        const params = { q: title, limit: 5, fields: 'id,title,image_id,artist_display' }; // sample set of params, limits the number of results to 5, and only returns the id, title, image_id, and artist_display fields    
        Axios.request({
            url: 'https://api.artic.edu/api/v1/artworks/search',
            params
        }).then(res => {
            const { config, data } = res.data;
            const updated_art_data = data.map(artPiece => ({ config, ...artPiece })); // add config to each art piece
            updateArtData(updated_art_data);
        }).catch(err => console.log(err));
    }

    return (<div>
        <input ref={searchInput} id='search-text-input' type='text' />
        <button onClick={handleSearchArt}> search </button>
        {art_data.map(art_piece_data => (<ArtPiece key={art_piece_data.id} {...art_piece_data} />))} 
        {/* Don't be overwhelmed by {...art_piece_data} this is another example of destructuring, each key,value pair is passed down as if it were independent */}
    </div>)
}

// Again we pull out each argument passed by name using destructuring
const ArtPiece = ({ config, title, image_id, id, artist_display }) => {
    return (<div>
        <img src={`${config.iiif_url}/${image_id}/full/843,/0/default.jpg`} />
        <h3>{title}</h3>
        <p>{artist_display}</p>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

useRef is an example of a hook we can use to hold a reference to an element, in this case we used it to hold a reference to our search input element; The id is left in for comparison.
Now we simply need to add a route to load Art, edit App.js

import React from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { Art } from './Art';
import { Calculator, Counter } from './Calculator';
import { Menu } from './Menu';

export const App = () => {

    return (<div>
        <Routes>
            {/* <Route path='/' element={<Calculator />} /> */}
            {/* <Route path='/' element={<Navigate to='/calculator' replace={true} />} /> */}
            <Route path='/' element={<Menu />} />
            <Route path='/counter' element={<Counter />} />
            <Route path='/calculator' element={<Calculator />} />
            <Route path='/art' element={<Art />} />
        </Routes>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

We can access our /art and search for art pieces, feel free to add it to the great menu :P

Final Thoughts

There you have it, a basic yet somewhat comprehensive ( I hope ) guide on react, there are many more concepts, however, I'd say those are much more advanced and will only serve to conflate someone's understanding if they're new to react. Not to worry you WILL eventually encounter them, as for me I may or may not make an advanced guide, I suppose it depends on demand either way, let me know in the comments, and thank you very very much for reading this far <3 (◠﹏◠).

Edit: to include css, import your css file in index.html as you normally would ( doing a webpack config is a bit too much for introductory purposes ).

Discussion (11)

Collapse
suchintan profile image
SUCHINTAN DAS

Thanks steffanboodhoo, for sharing the amazing post !

This really has a huge chunk of information for any beginner to get stared with React. I really appreciate the efforts you put up while writing the whole thing out 👍.

Collapse
steffanboodhoo profile image
steffanboodhoo Author

thank you, really appreciate the appreciation 😁

Collapse
andrewbaisden profile image
Andrew Baisden

This was a good react introduction.

Collapse
steffanboodhoo profile image
steffanboodhoo Author

thank you !

Collapse
dannym_d_boy profile image
Daniel Mendez

Best react tutorial in the game.

Collapse
steffanboodhoo profile image
steffanboodhoo Author

Many thanks ! many thanks !

Collapse
yongchanghe profile image
Yongchang He

This is amazing and thanks!

Collapse
steffanboodhoo profile image
steffanboodhoo Author

thank you ! 😁

Collapse
jevonalexis profile image
jevonalexis

Thanks so much for this. Simple yet comprehensive post for beginners like myself.

Collapse
lukeshiru profile image
Luke Shiru

I have a few suggestions:

  • Why did you chose the manual installation instead of using create-react-app, vite or similar? You could simply do:
npm create react-app@latest my-app
# or
npm create vite@latest my-app
Enter fullscreen mode Exit fullscreen mode

Which is an actual great starting points for folks new to React.

  • Why did you use the word params instead of the more standard props or maybe even properties?
const CalculatorComponent = props => {
    const { num_1, num_2 } = props;
    return <div>{num_1 + num_2}</div>;
};
Enter fullscreen mode Exit fullscreen mode
  • Ideally you should extract props directly in the header of your component, and those props should be camelCase instead of snake_case:
const CalculatorComponent = ({ num1, num2 }) => <div>{num1 + num2}</div>;
Enter fullscreen mode Exit fullscreen mode
  • For React 18, you should be using hydrateRoot instead of createRoot. You also don't need to import React, and ideally should be using the StrictMode:
import { StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { App } from "./App.js";

hydrateRoot(
    document,
    <StrictMode>
        <App />
    </StrictMode>,
);
Enter fullscreen mode Exit fullscreen mode
  • Ideally you should name your state tuple x and setX, instead of x and updateX (this is a common practice):
const [count, setCount] = useState(0);
Enter fullscreen mode Exit fullscreen mode
  • The example for useEffect is far from ideal. Values that are derived from state or properties can be just a constant or an inlined operation, and if they are computationally expensive, you can use useMemo for that. Also you should be using value instead of defaultValue for inputs, so they keep in sync, and being that you only have handlers for some operations, is better to use a select instead of an input for that:
import { useState } from "react";

export const Calculator = () => {
    const [num1, setNum1] = useState(0);
    const [num2, setNum2] = useState(0);
    const [operator, setOperator] = useState("+");

    return (
        <div>
            <input
                type="number"
                value={num1}
                onChange={({ currentTarget }) =>
                    setNum1(parseFloat(currentTarget.value))
                }
            />
            <select
                value={operator}
                onChange={({ currentTarget }) =>
                    setOperator(currentTarget.value)
                }
            >
                {["+", "-", "/", "*"].map(operator => (
                    <option key={operator} value={operator}>
                        {operator}
                    </option>
                ))}
            </select>
            <input
                type="number"
                value={num2}
                onChange={({ currentTarget }) =>
                    setNum2(parseFloat(currentTarget.value))
                }
            />
            ={{
                "+": () => num1 + num2,
                "-": () => num1 - num2,
                "/": () => num1 / num2,
                "*": () => num1 * num2,
            }[operator]() ?? 0}
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode
  • Finally: The idea with React Router is not to use it to refresh the entire page with every route change, but to actually update portions of it. So instead of doing this:
<Routes>
    <Route path="/" element={<Menu />} />
    <Route path="/counter" element={<Counter />} />
    <Route path="/calculator" element={<Calculator />} />
</Routes>
Enter fullscreen mode Exit fullscreen mode

Ideally you should be doing this:

<Menu />
<Routes>
    <Route path="counter" element={<Counter />} />
    <Route path="calculator" element={<Calculator />} />
</Routes>
Enter fullscreen mode Exit fullscreen mode

Even if you don't want to make that change, at least you should be using nested routes for this particular case:

<Routes>
    <Route path="/" element={<Menu />}>
        <Route path="counter" element={<Counter />} />
        <Route path="calculator" element={<Calculator />} />
    </Route>
</Routes>
Enter fullscreen mode Exit fullscreen mode

Hope that helps!
Cheers!

Collapse
steffanboodhoo profile image
steffanboodhoo Author

hi hi, thanks for the feedback, I'll address these one at a time

@using something like create-react-app,

I mentioned in the post, i'll reiterate here, sometimes the amount of boilerplate code and files can feel overwhelming to someone who's new to the an ecosystem, in my own experience when struggling to learn things, I feel as if I understand a lot better when things are done from 'scratch' almost.

@using props instead of params,
the initial thinking behind it was to relate it to a function however I think on this you're right, it will be easier to transition between tutorials if I used the more standard props

@destructuring in the method signature
I did it for readability but at the end of the article in conventions section as well as in the more network example, I used destructuring in the signature and explained that this is convention

@using strictMode
You're right this would definitely help with debugging, I may edit and add in

@using 'set' vs 'update'
I've seen both across many codebases

@useEffect example
The example isn't meant to be optimal for efficiency rather for showcasing the different parts of useEffect, same goes for things like useMemo, again this is meant to be someone's introduction, as I described above things are made needlessly verbose at times just demonstration and explanatory purposes.

@react router
Again, key word being simplicity and yes the point of any routing system in a frontend framework is to not refresh the page which is why we even bother using NavLink instead of a normal anchor tag

thanks again, I shall make some of the suggested changes once I feel they keep understanding from a beginner's POV optimized.