Categories
JavaScript Mega-Guides React

React Basics Mega-Guide

Spread the love

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

In this article, we’ll look at how to create simple apps with React.

Getting Started

The easiest way to create a React app is to use the Create React App Node package.

We can run it by running:

npx create-react-app my-app

Then we can go to the my-app and run the app by running:

cd my-app  
npm start

Create React App is useful for creating a single-page app.

React apps don’t handle any backend logic or databases.

We can use npm run build to build the app to create the built app for production.

Creating Our First React App

Once we ran Create React App as we did above, we can create our first app. To create it, we go into App.js and then start changing the code there.

To make writing our app easy, we’ll use JSX to do it. It’s a language that resembles HTML, but it’s actually just syntactic sugar on top of JavaScript.

Therefore, we’ll use the usual JavaScript constructs in JSX.

We’ll start by creating a Hello World app. To do this, we replace what’s there with the following in index.js:

import React from "react";  
import ReactDOM from "react-dom";

function App() {  
  return (  
    <div>  
      <h1>Hello World</h1>  
    </div>  
  );  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the App component, which is just a function. It returns:

<div>  
  <h1>Hello World</h1>  
</div>

which is our JSX code to display Hello World. h1 is a heading and div is a div element.

The code above looks like HTML, but it’s actually JSX.

What we have above is a function-based component since the component is written as a function.

In the last 2 lines, we get the element with ID root from public/index.html and put our App component inside it.

Another way to write the code above is to write:

import React from "react";  
import ReactDOM from "react-dom";

class App extends React.Component {  
  render() {  
    return (  
      <div>  
        <h1>Hello World</h1>  
      </div>  
    );  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

The code above is a class-based component, which has a render method to render the same JSX code into HTML.

The difference between the 2 examples is that one is a function and the other is a class that extends React.Component .

Otherwise, they’re the same. Any component file has to include:

import React from "react";  
import ReactDOM from "react-dom";

Otherwise, we’ll get an error.

React doesn’t require JSX, it’s just much more convenient to use it. A third way to create a Hello World app is to use the React.createElement method.

We can use the method as follows:

import React from "react";  
import ReactDOM from "react-dom";

const e = React.createElement;  
const App = e("h1", {}, "Hello World");

const rootElement = document.getElementById("root");  
ReactDOM.render(App, rootElement);

The first argument of the createElement method is the tag name as a string, the second argument has the props, which are objects that we pass to the component created, and the third argument is the inner text of them element.

We won’t be using this very often since it’ll get very complex if we have to nest components and adding interaction.

Embedding Expressions in JSX

We can embed JavaScript expressions between curly braces. For example, we can write:

import React from "react";  
import ReactDOM from "react-dom";  
function App() {  
  const greeting = "Hello World";  
  return (  
    <div>  
      <h1>{greeting}</h1>  
    </div>  
  );  
}  
const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

Then we see Hello World on the screen again.

Something more useful world calling a function as follows:

import React from "react";  
import ReactDOM from "react-dom";  
function App() {  
  const formatName = user => {  
    return `${user.firstName} ${user.lastName}`;  
  }; 

  const user = {  
    firstName: "Jane",  
    lastName: "Smith"  
  }; 

  return (  
    <div>  
      <h1>{formatName(user)}</h1>  
    </div>  
  );  
}  
const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we defined a formatName function inside the App component that takes a user object and returns user.firstName and user.lastName joined together.

Then we defined a user object with those properties and called the function inside the curly braces.

Whatever’s return will be displayed between the braces. In this case, it’ll be Jane Smith.

Lifting State Up

We should lift any shared state up to their closest common ancestor.

This way, one state can be shared between multiple child components by passing them down via props.

For example, if we want to build a calculator for converting lengths, we can write the following:

import React from "react";  
import ReactDOM from "react-dom";

class LengthInput extends React.Component {  
  constructor(props) {  
    super(props);  
    const { length } = this.props;  
    this.state = { length };  
  }  
  
  handleChange(e) {  
    this.props.onChange(e);  
  } 

  componentWillReceiveProps(props) {  
    const { length } = props;  
    this.setState({ length });  
  } 

  render() {  
    const { unit } = this.props;  
    return (  
      <>  
        <label>Length ({unit})</label>  
        <input  
          value={this.state.length}  
          onChange={this.handleChange.bind(this)}  
          placeholder={this.props.unit}  
        />  
        <br />  
      </>  
    );  
  }  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { lengthInMeter: 0, lengthInFeet: 0 };  
  } 

  handleMeterChange(e) {  
    this.setState({ lengthInMeter: +e.target.value });  
    this.setState({ lengthInFeet: +e.target.value \* 3.28 });  
  } 

  handleFeetChange(e) {  
    this.setState({ lengthInFeet: +e.target.value });  
    this.setState({ lengthInMeter: +e.target.value / 3.28 });  
  } 

  render() {  
    return (  
      <div className="App">  
        <LengthInput  
          length={this.state.lengthInMeter}  
          onChange={this.handleMeterChange.bind(this)}  
          unit="meter"  
        />  
        <LengthInput  
          length={this.state.lengthInFeet}  
          onChange={this.handleFeetChange.bind(this)}  
          unit="feet"  
        />  
      </div>  
    );  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have a length converter than converts meters to feet when we’re typing in the meters field and vice versa.

What we’re doing is that we keep the lengths all in the App component. This is why the principle is called lifting the states up.

Since we’re keeping the lengths in the App component, we have to pass them down to the LengthInput components.

To do that, we pass props to them. Also, we pass in the units, and the change handler functions down to our LengthInput components, so that they can the functions to update the states in the App component.

In the handleFeetChange and handleMeterChange functions, we set the state according to the values entered in the LengthInput components.

We call this.setState in both functions to set the states. Each time setState is called, render will be called, so that the latest state will be passed down to our LengthInput components.

In the LengthInput components, we have the componentWillReceiveProps hook which will get the latest prop values and then set the length state accordingly.

this.state.length is set as the value of the input elements in LengthInput , so the inputted value will be shown.

The advantage of lifting the states up to the parent component is that we only have to keep one copy of the state. Also, we don’t have to repeat the processing of the states in different child components.

The values of the inputs stay in sync because they’re computed from the same state.

Uncontrolled Components

Uncontrolled components are where we use the DOM properties to manipulate form inputs.

We can use a ref to get form values directly from the DOM.

For example, we can write the following:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.input = React.createRef();  
  } 

  handleSubmit(event) {  
    alert(`Name submitted: ${this.input.current.value}`);  
    event.preventDefault();  
  } 

  render() {  
    return (  
      <form onSubmit={this.handleSubmit.bind(this)}>  
        <label>  
          Name:  
          <input type="text" ref={this.input} />  
        </label>  
        <input type="submit" value="Submit" />  
      </form>  
    );  
  }  
}

In the code above we created the this.input ref in the constructor and attached it to the input element.

Then when we type in something and click Submit, we get whatever we typed into the input box displayed in the alert box since we accessed it with this.input.current.value .

This makes integrating React and non-React code easier since we’re using the DOM to get the value.

Default Values

The value attribute on form elements will override the value in the DOM.

With an uncontrolled component, we often want to specify the initial value and leave subsequent updates uncontrolled.

We can set the defaultValue attribute to do this:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.input = React.createRef();  
  } 

  handleSubmit(event) {  
    alert(`Name submitted: ${this.input.current.value}`);  
    event.preventDefault();  
  } 

  render() {  
    return (  
      <form onSubmit={this.handleSubmit.bind(this)}>  
        <label>  
          Name:  
          <input type="text" defaultValue="Foo" ref={this.input} />  
        </label>  
        <input type="submit" value="Submit" />  
      </form>  
    );  
  }  
}

In the code above, we set the defaultValue attribute to Foo , which is the value of this.input.current.value if we don’t change the input value.

The file input Tag

File inputs are always uncontrolled components because its value can only be set by a user and not programmatically.

Therefore, we have to use a ref to access its value.

For example, we can write the following:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.fileRef = React.createRef();  
  } 

  handleSubmit(event) {  
    alert(`Filename: ${this.fileRef.current.files[0].name}`);  
    event.preventDefault();  
  } 

  render() {  
    return (  
      <form onSubmit={this.handleSubmit.bind(this)}>  
        <input type="file" ref={this.fileRef} />  
        <input type="submit" value="Submit" />  
      </form>  
    );  
  }  
}

In the code above, we created a ref called fileRef and attached it to the file input. Then we get the files[0].name property that has the name of the selected file.

We can use the File API to do things with the selected file.

Lists and Keys

We can transform lists into HTML by calling the array’s map method.

For example, if we want to display an array of numbers as a list, we can write:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
  } 

  render() {  
    return (  
      <div>  
        {[1, 2, 3, 4, 5].map(num => (  
          <span>{num}</span>  
        ))}  
      </div>  
    );  
  }  
}

In the code above, we called map on the [1, 2, 3, 4, 5] array. In the map method’s callback, we returned a span with the number inside and we do the same for each element.

Then we get:

12345

displayed on the screen.

We can assign it to a variable and then pass it into the ReactDOM.render method as follows:

import React from "react";  
import ReactDOM from "react-dom";
const nums = [1, 2, 3, 4, 5].map(num => <span>{num}</span>);
const rootElement = document.getElementById("root");  
ReactDOM.render(nums, rootElement);

We’ll get the same items displayed.

Also, we can use the same code inside the render method:

import React from "react";  
import ReactDOM from "react-dom";

class App extends React.Component {  
  constructor(props) {  
    super(props);  
  } 

  render() {  
    const nums = [1, 2, 3, 4, 5].map(num => <span>{num}</span>);  
    return <div>{nums}</div>;  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

Keys

When we render lists, we should provide a value for key prop for each rendered element so that React can identify which items have changed, added, or removed.

It gives elements a stable identity. We should pick a key by using a string that uniquely identifies a list item among its siblings.

A key should be a string value.

For example, if we want to render a list of to-do items, we should pick the id field as the key ‘s value as follows:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map(todo => (  
          <p key={todo.id.toString()}>{todo.text}</p>  
        ))}  
      </div>  
    );  
  }  
}

In the code above, we have the key={todo.id.toString()} prop to set the key to the todo ‘s id converted to a string.

If we have no stable identity for our items, we can use the index for the entry as a last resort:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [{ text: "eat" }, { text: "drink" }, { text: "sleep" }]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map((todo, index) => (  
          <p key={index.toString()}>{todo.text}</p>  
        ))}  
      </div>  
    );  
  }  
}

index is always available and it’s unique for each array element, so it can be used as a value for the key prop.

Extracting Components with Keys

If we render components, we should put the key prop in the component rather than the element that’s being rendered.

For example, the following is incorrectly using the key prop:

function TodoItem({ todo }) {  
  return <p key={todo.id.toString()}>{todo.text}</p>;  
}

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map(todo => (  
          <TodoItem todo={todo} />  
        ))}  
      </div>  
    );  
  }  
}

In the TodoItem component, we have:

<p key={todo.id.toString()}>{todo.text}</p>;

with the key prop. We don’t want this there because we don’t need to identify a unique li since it’s isolated from the outside already. Instead, we want to identify a unique TodoItem .

Instead, we should write:

function TodoItem({ todo }) {  
  return <p>{todo.text}</p>;  
}class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      todos: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        {this.state.todos.map(todo => (  
          <TodoItem todo={todo} key={todo.id.toString()} />  
        ))}  
      </div>  
    );  
  }  
}

We should add keys to items return with map ‘s callback.

Keys Only Need to Be Unique Among Siblings

Keys only need to be unique among sibling elements.

For example, we can write:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = {  
      posts: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ],  
      comments: [  
        { id: 1, text: "eat" },  
        { id: 2, text: "drink" },  
        { id: 3, text: "sleep" }  
      ]  
    };  
  } 

  render() {  
    return (  
      <div>  
        <div>  
          {this.state.posts.map(post => (  
            <p key={post.id.toString()}>{post.text}</p>  
          ))}  
        </div>  
        <div>  
          {this.state.comments.map(comment => (  
            <p key={comment.id.toString()}>{comment.text}</p>  
          ))}  
        </div>  
      </div>  
    );  
  }

Since posts and comments aren’t rendered in the same div , the key values can overlap.

The key prop doesn’t get passed to components. If we need the same value in a component we have to use a different name.

Embedding map() in JSX

We can embed the expression that calls map straight between the curly braces.

For example, we can write:

class App extends React.Component {  
  constructor(props) {  
    super(props);  
  } 

  render() {  
    return (  
      <div>  
        {[1, 2, 3, 4, 5].map(num => (  
          <span key={num.toString()}>{num}</span>  
        ))}  
      </div>  
    );  
  }  
}

useState

The useState hook lets us manage the internal state of a function component. It takes an initial value as an argument and returns an array with the current state and a function to update it.

It returns the initial state when the component is initially rendered.

We can pass in a function to update the value if the new value is computed using the previous state.

For example, we can write the following to update the value based on a previous one:

function App() {  
  const [count, setCount] = React.useState(0);  
  return (  
    <>  
      Count: {count}  
      <button onClick={() => setCount(0)}>Reset</button>  
      <button onClick={() => setCount(prevCount => prevCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(prevCount => prevCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we have:

setCount(prevCount => prevCount - 1)}

and:

setCount(prevCount => prevCount + 1)}

which decrements the count and increment it respectively by passing in functions that take the previous count as the parameter and return the new count.

Otherwise, we can just pass in the new value to the state update function as we did in:

setCount(0)

useState doesn’t automatically merge update objects. We can replicate this behavior with the spread syntax:

function App() {  
  const [nums, setNums] = React.useState({});  
  return (  
    <>  
      <p>{Object.keys(nums).join(",")}</p>  
      <button  
        onClick={() =>  
          setNums(oldNums => {  
            const randomObj = { [Math.random()]: Math.random() };  
            return { ...oldNums, ...randomObj };  
          })  
        }  
      >  
        Click Me  
      </button>  
    </>  
  );  
}

In the code above, we have:

setNums(oldNums => {  
            const randomObj = { [Math.random()]: Math.random() };  
            return { ...oldNums, ...randomObj };  
          })

to create a randomObj object with a random number as the key and value, and we merge that into another object with the old value then return it.

Then we display it with:

Object.keys(nums).join(",")

by getting the keys and joining them together.

Lazy initial state

We can pass in a function to useState if we want to delay the setting of the initial state.

It’ll be ignored after the initial render if we pass in a function.

This is useful if the initial state is computed from some expensive operation.

For example, we can write:

function App() {  
  const [count, setCount] = React.useState(() => 0);  
  return (  
    <>  
      Count: {count}  
      <button onClick={() => setCount(() => 0)}>Reset</button>  
      <button onClick={() => setCount(prevCount => prevCount - 1)}>  
        Decrement  
      </button>  
      <button onClick={() => setCount(prevCount => prevCount + 1)}>  
        Increment  
      </button>  
    </>  
  );  
}

In the code above, we have:

React.useState(() => 0)

which is a function that just returns 0.

We’ll see the same results as before.

Bailing out of a state update

If we update a state hook with the same value as the current. React will skip updating the state without rendering the children or firing effects.

React uses Object.is() to compare the current and new states, which is close to the === operator except that +0 and -0 are treated to be not equal and NaN is equal to itself.

React may still render before bailing out but it won’t go deeper into the tree if it finds that the old and new values are the same.

useEffect

We can use the useEffect hook to do various operations that aren’t allowed inside the main body of the function component, which is anything outside the rendering phase.

Therefore, we can use this to do any mutations, subscriptions, setting timers, and other side effects.

It takes a callback to run the code.

We can return a function inside to run any cleanup code after each render and also when the component unmounts.

The callback passed into useEffect fires after layout and paint, during a deferred event.

This makes this suitable for running operations that shouldn’t block the browser from updating the screen.

Code that must be run synchronously can be put into the callback of the useLayoutEffect hook instead, which is the synchronous version of useEffect .

It’s guaranteed to fire before any new renders. React will always flush the previous render’s effects before starting a new update.

Conditionally firing an effect

We can pass in a second argument to useEffect with an array of values that requires an effect to be run when they change.

For example, we can use it to get data from an API on initial render as follows:

function App() {  
  const [joke, setJoke] = React.useState({});  
  useEffect(() => {  
    (async () => {  
      const response = await fetch("https://api.icndb.com/jokes/random");  
      const res = await response.json();  
      console.log(res);  
      setJoke(res);  
    })();  
  }, []);  
  return (  
    <>  
      <p>{joke.value && joke.value.joke}</p>  
    </>  
  );  
}

Passing an empty array as the second argument will stop it from loading in subsequent renders.

We can pass in a value to the array to watch the value in the array change and then run the callback function:

function App() {  
  const [joke, setJoke] = React.useState({});  
  const [id, setId] = React.useState(1);  
  useEffect(() => {  
    (async () => {  
      const response = await fetch(`https://api.icndb.com/jokes/${id}`));  
      const res = await response.json();  
      console.log(res);  
      setJoke(res);  
    })();  
  }, [id]);  
  return (  
    <>  
      <button onClick={() => setId(Math.ceil(Math.random() * 100))}>  
        Random Joke  
      </button>  
      <p>{joke.value && joke.value.joke}</p>  
    </>  
  );  
}

In the code above, when we click the Random Joke button, setId is called with a new number between 1 and 100. Then id changes, which triggers the useEffect callback to run.

Then joke is set with a new value, then the new joke is displayed on the screen.

useContext

We can use the useContext hook to read shared data shared from a React context. It accepts the context object returned from React.createContext as an argument and returns the current context value.

The current context value is determined by the value prop of the nearest context provider.

We can use it as follows:

const ColorContext = React.createContext("green");function Button() {  
  const color = React.useContext(ColorContext);  
  return <button style={{ color }}>button</button>;  
}function App() {  
  return (  
    <>  
      <ColorContext.Provider value="blue">  
        <Button />  
      </ColorContext.Provider>  
    </>  
  );  
}

In the code above, we created a new React context with:

const ColorContext = React.createContext("green");

Then in App , we wrapped out Button with the ColorContext.Provider with the value prop set to blue .

Then in Button , we have:

const color = React.useContext(ColorContext);

to get the value passed in from the ColorContext.Provider and set it to color .

Finally, we set the color style of the button with the color ‘s value.

A component calling useContext will always re-render when the context value changes. If re-rendering is expensive, then we can optimize it with memoization.

useContext is the React hooks version of Context.Consumer .

useReducer

This hook is an alternative to useState . It accepts a reducer function of type (state, action) => newState .

useReducer is preferable to useState when we have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

It also lets us optimize performance for components that trigger deep updates because we can pass dispatch down instead of callbacks.

For example, we can write:

const INCREMENT = "INCREMENT";  
const DECREMENT = "DECREMENT";function reducer(state, action) {  
  switch (action.type) {  
    case INCREMENT:  
      return { count: state.count + 1 };  
    case DECREMENT:  
      return { count: state.count - 1 };  
    default:  
      throw new Error();  
  }  
}

function App() {  
  const [state, dispatch] = React.useReducer(reducer, { count: 0 });  
  return (  
    <>  
      Count: {state.count}  
      <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>  
      <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>  
    </>  
  );  
}

In the code above, we have our reducer which returns the new state depends on the action.type ‘s value. In this case, it’s either 'INCREMENT' or 'DECREMENT' .

If it’s ‘INCREMENT’ , we return { count: state.count + 1 } .

If it’s ‘DECREMENT’ , we return { count: state.count — 1 } .

Otherwise, we throw an error.

Then in App , we call useReducer by passing in a reducer as the first argument and the initial state as the second argument.

Then we get the state object, which has the current state object and a dispatch function, which we can call with an action object, which has the type property with the value being one of ‘INCREMENT’ or ‘DECREMENT' .

We used the dispatch function in the buttons to update the state.

Finally, we display the latest state in state.count .

Lazy initialization

We can pass in a function to the 3rd argument of useReducer to initialize the state lazily.

The initial state will be set to init(initialArg) .

For instance, we can rewrite the previous example as follows:

const init = initialCount => {  
  return { count: initialCount };  
};

const INCREMENT = "INCREMENT";  
const DECREMENT = "DECREMENT";

function reducer(state, action) {  
  switch (action.type) {  
    case INCREMENT:  
      return { count: state.count + 1 };  
    case DECREMENT:  
      return { count: state.count - 1 };  
    default:  
      throw new Error();  
  }  
}
function App() {  
  const [state, dispatch] = React.useReducer(reducer, 0, init);  
  return (  
    <>  
      Count: {state.count}  
      <button onClick={() => dispatch({ type: DECREMENT })}>Decrement</button>  
      <button onClick={() => dispatch({ type: INCREMENT })}>Increment</button>  
    </>  
  );  
}

First, we have:

const init = initialCount => {  
  return { count: initialCount };  
};

to return the initial state.

And instead of writing:

React.useReducer(reducer, { count: 0 });

We have:

React.useReducer(reducer, 0, init);

0 is passed in as the initialCount of init .

Then the rest of the code is the same as before.

Bailing out of a dispatch

If the same value is returned from a Reducer hook is the same as the current state, React will bail out without rendering the children or firing effects.

The comparison is done using the Object.is() algorithm.

If we’re doing expensive operations while rendering, we can optimize it with useMemo .

Conclusion

We can create a React app with the Create React App Node package.

Then we can add components as a function, class, or with React.createElement .

The first 2 ways are used most often since they return JSX in the function or the render method of the component class respectively.

JSX is much more convenient than createElement for writing JavaScript code with React, especially when our app gets complex.

We can embed JavaScript expressions in between curly braces.

Hooks are used to change internal state, commit side effects, or hold any other logic.

React Router lets us navigate between different pages,

The Context API lets us share data between any components.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *