Tech Start's React Guide

alt_text

React Main Concepts

JavaScript For React

More React

React Ecosystem

React Main Concepts

React Video

Check out this video as a crash course to React:

https://www.youtube.com/watch ?v=Ke90Tje7VS0

If you find this video confusing, or prefer a different one as a React intro, please let us know :)

If you prefer reading to watching videos, this guide is helpful:

Components

React is built around the concept of components. Components are reusable, self-contained units that encapsulate the UI and behavior of a part of the application. React applications are typically composed of many components.

function MyComponent() {
  return <div>Hello, World!</div>;
}

JSX

JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like code within your JavaScript. It's used in React to define the structure of components.

const element = <h1>Hello, world!</h1>;

Rendering

React renders components into the DOM (Document Object Model). You can use the ReactDOM library to render a component into a specific HTML element.

ReactDOM.render(<MyComponent />, document.getElementById('root'));

Props

Props (short for properties) allow you to pass data from a parent component to a child component. This enables you to create dynamic and reusable components.

function Greeting(props) {
  return <div>Hello, {props.name}</div>;
}

State

State is a way to store and manage data that can change over time. It is used to make components dynamic and interactive.

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
}

Lifecycle Methods

React components have a lifecycle, and you can use lifecycle methods to perform actions at various stages of a component's existence. For example, componentDidMount is called after a component is rendered.

componentDidMount() {
  // Perform initialization after rendering.
}

Handling Events

You can define event handlers in React components to respond to user interactions, such as clicks or input changes.

function Button() {
  function handleClick() {
    console.log('Button clicked');
  }

  return <button onClick={handleClick}>Click me</button>;
}

Conditional Rendering

You can use conditional statements and expressions to conditionally render different parts of a component based on certain conditions.

function Greeting(props) {
  if (props.isLoggedIn) {
    return <div>Welcome, User!</div>;
  } else {
    return <div>Please log in.</div>;
  }
}

Lists and Keys

React provides a way to render lists of elements efficiently and assigns unique keys to each item in the list for optimization.

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => <li key={number}>{number}</li>);

React utilizes several JavaScript features and concepts like arrow functions, classes, callbacks, promises, and async/await to create dynamic and interactive user interfaces. Let's explore each of these in detail and discuss how they are related and integrated in React:

Javascript For React:

Arrow functions, classes, callbacks, promises, and async/await are fundamental JavaScript concepts that are commonly used in React. Let's explore each of these topics and how they are related and integrated in React:

Arrow Functions:

Arrow functions are a concise way to write functions in JavaScript. They are commonly used in React for defining components and functions, as they have a more compact syntax compared to traditional function expressions. Arrow functions capture the this value of the enclosing context automatically, making them suitable for working within React components and event handlers.

Example of an arrow function defining a React component:

const MyComponent = () => {
  return <div>Hello, World!</div>;
};

Classes:

Classes in JavaScript are used to define and create objects with methods and properties. In React, components are often defined as classes, especially when they need to manage component state and lifecycle methods. React class components extend the React.Component class and can have methods like render, componentDidMount, and more for handling component behavior.

Example of a React class component:

class MyComponent extends React.Component {
  render() {
    return <div>Hello, World!</div>;
  }
}

**Note on functional versus class-based components**

When React was first created, class-based components were the standard. But functional components were introduced later, and they eclipse class-based components in every way.

Our advice: Ironically, you should probably never use class-based components!

Stick to functional components. They are more modern and more versatile.

Lots of tutorial content online still uses class-based components. If you stumble upon a tutorial or explanation that uses a class-based component, and you're new to React, please search for a functional-component alternative instead!

Callbacks:

Callbacks are functions that are passed as arguments to other functions and are executed at a later time or in response to an event. React uses callbacks extensively, especially in event handling. For example, you can pass callback functions to event handlers to respond to user interactions.

Example of a callback function for handling a button click:

function handleClick() {
  console.log('Button clicked');
}

<button onClick={handleClick}>Click me</button>

Promises:

Promises are a way to handle asynchronous operations in JavaScript. They represent a value that might be available now, or in the future, or never. Promises have three states: pending, fulfilled, and rejected.

  • Creating Promises: You can create a promise using the Promise constructor. It takes a function with two arguments: resolve and reject. You typically perform an asynchronous operation in this function and call resolve when the operation is successful or reject when it fails.

    const myPromise = new Promise((resolve, reject) => {
      // Asynchronous operation
      if (operationSucceeded) {
        resolve(result);
      } else {
        reject(error);
      }
    });
    
  • Chaining Promises: Promises can be chained together using .then() and .catch() to handle the resolved value or errors. This chaining allows you to compose complex asynchronous operations.

    myPromise
      .then((result) => {
        // Handle success
      })
      .catch((error) => {
        // Handle error
      });
    

async/await:

async and await are modern JavaScript features for working with asynchronous code. They make asynchronous code more readable and maintainable.

  • async Function: An async function is a function that always returns a promise. It allows you to use the await keyword inside the function to pause execution until the promise is resolved.

    async function fetchData() {
      const data = await fetch('https://api.example.com/data');
      return data.json();
    }
    
  • await Keyword: The await keyword can only be used inside an async function. It pauses the execution of the function until the promise is resolved, and it returns the resolved value.

    const result = await myPromise;
    // The code here will not execute until myPromise is resolved.
    

Promises and Aysnc/Await Integration in React:

React applications often involve asynchronous operations, such as fetching data from APIs or making network requests. Promises, async/await, and React's lifecycle methods can be integrated for managing asynchronous tasks effectively:

  • Fetching Data: You can use async/await to fetch data in React components. Typically, you do this inside componentDidMount() or within functional components using the useEffect hook.

    async componentDidMount() {
      try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        this.setState({ data });
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    }
    
  • Updating Component State: Once the data is fetched, you can update the component state to trigger a re-render with the new data.

  • Handling Errors: Use try/catch to handle errors gracefully. You can also integrate error boundaries in React to catch errors in the component tree.

  • Using Promises: React works well with Promises, and you can use .then() and .catch() to manage asynchronous operations. However, async/await is often preferred for its more readable and synchronous-like syntax.

Integrating Promises and async/await in React allows you to manage asynchronous operations in a clean and structured way, providing a better user experience by preventing UI blocking during data retrieval.

More React:

Events in React

Event Handling

In React, events are used to capture and respond to user interactions, such as clicks, input changes, and mouse movements. Event handling in React is similar to handling events in traditional HTML, but there are some differences due to React's synthetic event system.

In React, you define event handlers as functions and attach them to JSX elements using event attributes. Here's an example of how you might handle a click event in React:

function Button() {
  function handleClick() {
    console.log('Button clicked');
  }

  return <button onClick={handleClick}>Click me</button>;
}

The onClick attribute specifies the event handler function, handleClick, which will be executed when the button is clicked. React's synthetic event system provides a consistent API for handling events across different browsers.

Event Object

In React, event handlers are passed an event object as an argument. This object contains information about the event, such as the type of event, target element, and any event-specific data. You can access event properties and methods within your event handler functions.

function handleChange(event) {
  console.log('Input value:', event.target.value);
}

Higher Order Components (HOCs) in React

What Are HOCs?

Higher Order Components are a design pattern in React that allows you to reuse component logic by wrapping one or more components with a higher-order component. HOCs are not a part of the React API; they are a pattern that leverages the composability of components.

How HOCs Work

HOCs are functions that take a component and return a new enhanced component. They can add props, modify behavior, or encapsulate certain functionality. For example, you might create an HOC that provides authentication, access control, or data fetching capabilities to a component.

Here's a simplified example of a higher order component that provides a "loading" indicator to a component:

function withLoadingIndicator(WrappedComponent) {
  return function WithLoadingIndicator(props) {
    if (props.isLoading) {
      return <div>Loading...</div>;
    }
    return <WrappedComponent {...props} />;
  };
}

Using HOCs

You can use an HOC by wrapping your component with it. For instance, suppose you have a component called MyComponent, and you want to add a loading indicator using the withLoadingIndicator HOC:

const MyComponentWithLoading = withLoadingIndicator(MyComponent);

Now, MyComponentWithLoading is a new component that includes the loading indicator logic from the HOC. You can render it as you would with any other component.

<MyComponentWithLoading isLoading={true} />

Benefits of HOCs

HOCs enable you to separate concerns and promote reusability. They help you avoid code duplication by encapsulating common functionality in a separate function. This makes your code more maintainable and flexible, allowing you to compose and extend component behavior as needed.

Relationship Between Events and HOCs

Events and HOCs can work together in a React application. For instance, you might create an HOC that handles common event-related logic, such as tracking user interactions, and then wrap components that need that behavior. This can help centralize event handling logic and make it reusable across multiple components. Additionally, you can pass event handling functions as props when composing components with HOCs, allowing for flexible customization of event behavior.

In summary, events in React are essential for capturing and responding to user interactions, while Higher Order Components are a design pattern that promotes reusability and composability of component logic. You can use HOCs to encapsulate and extend event-related logic, making it easier to manage event handling across your React application.

Props vs. State

Props and state are two fundamental concepts in React, and they play distinct roles in how components work.

Props

Props (short for properties) are a mechanism for passing data from a parent component to a child component. Props are read-only, meaning that the child component cannot modify the props it receives. They are used to customize or configure child components based on data from their parent.

Example of using props:

function Greeting(props) {
  return <div>Hello, {props.name}</div>;
}

State

State is a way to store and manage data that can change over time within a component. State is used to make components dynamic and interactive. Unlike props, state is mutable, and components can change their internal state using the setState method. State is often used for data that the component needs to keep track of, such as user input or UI state.

Example of using state:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

How They Relate and Are Integrated

Props and state are often used together to create dynamic and interactive React applications. Here's how they relate and are integrated:

  1. Passing Data: Props are used to pass data from a parent component to its child components. This data can be initial data that a child component uses to render itself.

  2. Updating Data: State is used to manage data that can change within a component. Components can have state and use it to keep track of user interactions, input, or changes in data.

  3. Reactivity: When a parent component passes props to a child component, any changes to those props in the parent will re-render the child. This allows for dynamic updates.

  4. State Management: State is local to the component that owns it, and changes in state trigger re-renders of that component, updating the UI as needed.

  5. Lifting State: Sometimes, you might need to manage state at a higher level in the component tree and pass it down as props to child components. This is called "lifting state up."

In summary, props are for passing data from parent to child, while state is for managing data that can change within a component. Together, they enable you to build interactive and data-driven React applications. Hooks, especially the useState hook, make it easier to manage local state in functional components, further enhancing the capabilities of React functional components.

Hooks in Functional Components

React introduced Hooks in version 16.8 as a way to add state and side-effects to functional components, which were previously limited to stateless rendering.

In React, "stateless rendering" refers to the practice of creating functional components (also known as stateless functional components) that are purely responsible for rendering UI based on the input data provided through props. These components do not manage or maintain any internal state.

Out with the old in with the new KING, hooks allow you to reuse stateful logic and side-effects across components, making functional components more powerful and flexible.

Some of the most commonly used hooks include:

1. useState

This one might sound familiar from above, useState hook allows functional components to manage local state. It takes an initial state value and returns an array with the current state and a function to update it.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

2. useEffect

The useEffect hook enables you to perform side-effects in functional components. It takes a function that will be executed after every render, and you can specify dependencies to control when the effect should run.

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

function Example() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // Fetch data from an API and update the state
    fetchData().then((result) => setData(result));
  }, []); // Empty dependency array runs the effect only once
}

3. Other Built-in Hooks

React provides several other built-in hooks, such as useContext and useReducer, which allow you to manage context, state transitions, and references in functional components, respectively.

  • useContext: Allows you to access the context API within functional components. It's useful for sharing data across the component tree without prop drilling.
import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Themed Button</button>;
}
  • useReducer: This hook is used for more complex state management and state transitions. It's similar to setState but offers more control over how state updates occur.
import React, { useReducer } from 'react';

const initialState = { count: 0 };

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}> Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}> Decrement</button>
    </div>
  );
}

4. Custom Hooks

You can create your own custom hooks to encapsulate and share component logic across different components. Custom hooks promote code reuse and maintainability.

// Custom hook for handling form input state
import { useState } from 'react';

function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue);

  const handleChange = (e) => {
    setValue(e.target.value);
  };

  return {
    value,
    onChange: handleChange,
  };
}

In summary, hooks in React are a way to manage state and side-effects in functional components. They integrate seamlessly with functional components, making it easier to write and maintain complex logic and enabling better code reuse. React's built-in hooks and the ability to create custom hooks provide a powerful toolset for building dynamic and interactive applications.

Great Resources(videos): https://www.youtube.com/watch?v=Jl4q2cccwf0&ab_channel=TheNetNinja

  1. useState: https://www.youtube.com/watch?v=4qVNaohzDWU&ab_channel=LogRocket
  2. useEffect:https://www.youtube.com/watch?v=gv9ugDJ1ynU&ab_channel=TheNetNinja
  3. useRef: https://www.youtube.com/watch?v=yCS2m01bQ6w&ab_channel=Codevolution
  4. useCallback: https://www.youtube.com/watch?v=-Ls48dd-vJE&ab_channel=BenAwad

React Ecosystem

Calling APIs

Calling APIs is a key part of any React App. It's what enables your app to communicate with the outside world - including, presumably, your backend.

Here's a helpful tutorial on the best practices for calling APIs in React:

https://www.youtube.com/watch?v=bYFYF2GnMy8

There's also a part 2:

https://www.youtube.com/watch?v=1tfd6ANaNRY

The best ways to fetch data are using the popular package Axos (https://www.npmjs.com/package/axios) or the vanilla JS _fetch _function (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch)

Once you have parsed the data, you'll probably want to store it in your state somehow (using useState or a redux store).

It is also good practice to encapsulate your entire API call into a custom hook, and name the hook according to what it does (ex. useFetchPokemonStats). This is also much easier to do if you use a state-management system like Redux, which is described below.

The methods described above let you call an API immediately upon rendering a certain component. But what happens if you want to manually trigger an API call (ex. after a certain action or event)?

Your API call should still use useEffect, and should look mostly like the calls you learned about in Part 1. The 1 difference is you need to guard the useEffect wisely in its dependency array.

As you recall, the dependency array of a useEffect contains everything that the useEffect depends upon - if any of its dependencies change, it will have a _side effect _of rerunning the useEffect.

So, you can define a specific variable which only changes when you want your API call to run. You can put that variable in the dependency array of the useEffect. When the action or event that you want to trigger the API call occurs, you should change the trigger variable. This change will trigger the useEffect to run. The trigger variable should never change except when you want the useEffect to run.

I personally like making my trigger variable an object which also contains any subvariables that my API call needs. So for example, if I was coding a call to a search API that included a text query and target price, my trigger object would contain those.

Here is an example of a very basic React application that calls an API to perform a search with a custom hook and a useEffect guarded by a trigger object as described above: https://github.com/Tech-Start-UCalgary/react-api-examples/tree/main/js-no-redux

useSWR

alt_text

useSWR is a useful React hook for data fetching published by Vercel (creators of Next.js).

SWR stands for stale-while-revalidate. It allows for smart data fetching and encapsulates a lot of advanced fetching logic (like how often should you refetch? should you have a cache?) in a single line of code.

You should read more about useSWR here:

https://swr.vercel.app/

You can watch a video tutorial here:

https://www.youtube.com/watch?v=f7yjEdXgGiM

React Router

React Router is a popular library used for routing in React applications. Routing is the process of determining which UI components should be displayed based on the current URL or path. React Router helps you create single-page applications with multiple views and navigate between them without requiring full page reloads.

Here's an elaboration on React Router and its integration with React:

React Router Features:

  1. Declarative Routing: React Router uses a declarative approach, where you define the routes and their corresponding components in a clear and organized manner. You specify what component should be rendered when a particular URL is matched.

  2. Nested Routing: React Router supports nested routes, allowing you to create complex and hierarchical UI structures. This is especially useful for building multi-level menus or complex application layouts.

  3. Route Parameters: You can define route parameters in your routes, allowing you to extract dynamic data from the URL. For example, a route like /users/:id can capture the id as a parameter.

  4. Programmatic Navigation: React Router provides a set of functions for programmatic navigation. You can change the route, push to the browser's history, or replace the current route using these functions. This is useful for actions like form submissions or after successful authentication.

  5. Route Guards: React Router allows you to implement route guards for protecting routes based on user authentication or other conditions. This ensures that users can only access certain routes if they meet specific criteria.

Integration with React:

React Router is typically integrated into a React application as a separate library. Here's how it's commonly done:

  1. Installation: You start by installing React Router as a package in your React project. You can use either react-router-dom (for web applications) or react-router-native (for mobile applications).

    npm install react-router-dom
    
  2. Router Component: You wrap your entire application (or a part of it) with a <BrowserRouter> or <HashRouter> component provided by React Router. This component manages the application's navigation state and listens to changes in the URL.

    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
    
    function App() {
      return (
        <Router>
          {/* Define your routes here */}
        </Router>
      );
    }
    
  3. Route Configuration: Inside the <Router>, you define your routes using the <Route> component. Each <Route> component specifies a path and the component to render when the path matches.

    <Route path="/home" component={Home} />
    <Route path="/about" component={About} />
    
  4. Route Navigation: To navigate between routes, you use the <Link> component to create links or the history object to programmatically navigate.

    <Link to="/home">Home</Link>
    // OR
    history.push('/home');
    
  5. Route Parameters: You can use route parameters to capture dynamic values from the URL. These parameters are accessible as props in the routed components.

    <Route path="/user/:id" component={UserProfile} />
    
  6. Nested Routes: You can nest routes by defining routes within the components rendered by other routes. This allows for hierarchical routing structures.

    <Route path="/dashboard" component={Dashboard}>
      <Route path="/dashboard/profile" component={Profile} />
    </Route>
    

By integrating React Router into your React application, you can create well-organized, client-side routing that provides a seamless and efficient user experience for navigating different parts of your application.

React with Redux

Redux is a widely used state container for javascript apps. As soon as your app reaches any level of data complexity, it makes a ton of sense to start using Redux to manage your state.

A fair warning: Redux will seem complicated at the beginning. That's completely expected! Push through that initial discomfort and you'll get used to it in no time :)

Here is a great introductory video to React-Redux:

https://www.youtube.com/watch?v=CVpUuw9XSjY

I highly recommend using **createSlice to setup your Redux Store. **It is a simple way to encapsulate creating actions and slicers in a simple, easy-to-read, easy-to-understand way. Here is a short video that does a great job explaining how to use createSlice:

https://www.youtube.com/watch?v=e0MEtFaQTZk

To access your Redux state and update your Redux state in React, I highly recommend using the twin hooks **useSelector **and useDispatch respectively. They are simple, easy, and elegant.

https://www.youtube.com/watch?v=3zoIigieur0

Lastly, Next.JS

Next.js is a framework built on top of React, designed to simplify and enhance the development of web applications. It provides several features and benefits while seamlessly integrating with React. Let's explore these aspects interactively:

Q1: What is Next.js?

Next.js is a framework for building web applications that are built on top of the React library. It simplifies many aspects of React development and adds capabilities for server-side rendering, routing, and more.

Q2: How does Next.js relate to React?

Next.js is an extension of React. It leverages React's component-based structure and allows you to build React applications while providing additional tools and features for server-side rendering, routing, and other optimizations.

Q3: What are some key features of Next.js?

Next.js offers several key features:

  • Server-Side Rendering (SSR): Next.js enables server-side rendering, which improves performance and SEO by rendering pages on the server before sending them to the client.

  • Routing: It includes a built-in routing system, so you can easily define and navigate between pages.

  • File-Based Routing: Routes are created based on the file structure, making it intuitive and easy to organize your application.

  • Automatic Code Splitting: It automatically splits your JavaScript code into smaller, more manageable chunks, optimizing loading times.

  • Static Site Generation (SSG): Next.js allows you to generate static HTML files at build time for even better performance and SEO.

Q4: How do you create a new Next.js app?

To create a new Next.js app, you can use the following commands:

npx create-next-app my-nextjs-app
cd my-nextjs-app
npm run dev

This will set up a new Next.js application and start the development server.

Q5: Can you explain the pages and routing in Next.js?

In Next.js, you create pages by simply adding files to the pages directory. Each file becomes a route. For example, if you create pages/about.js, you will have a route at /about.

Q6: How does Next.js handle data fetching?

Next.js allows you to fetch data during server-side rendering using the getServerSideProps function. This data can be injected into your React components as props.

export async function getServerSideProps() {
  // Fetch data from an API or database
  const data = await fetchData();

  return {
    props: { data },
  };
}

Q7: What's the advantage of server-side rendering in Next.js?

Server-side rendering in Next.js improves the initial loading speed of your application and helps with search engine optimization (SEO). It also ensures that users see content more quickly.

Q8: How can I build a static website with Next.js?

Next.js provides static site generation (SSG) via the getStaticProps function. You can generate static HTML files for your pages at build time.

export async function getStaticProps() {
  // Fetch data from an API or database
  const data = await fetchData();

  return {
    props: { data },
  };
}

Q9: Are there any limitations or trade-offs with Next.js?

While Next.js offers many advantages, it may introduce complexity to smaller projects. It requires server-side rendering, which might not be necessary for all applications.

Q10: Can I deploy Next.js applications to various hosting platforms?

Yes, you can deploy Next.js applications to a wide range of hosting platforms, including Vercel, Netlify, and AWS. These platforms often provide built-in support for Next.js, making deployment straightforward.

Next.js is a powerful tool that enhances React applications by providing features like server-side rendering, routing, and automatic code splitting. It simplifies the development process and improves performance. If you have more specific questions or need further information about Next.js, feel free to ask!