Skip to content

Instantly share code, notes, and snippets.

@sivadass
Last active June 1, 2020 15:42
Show Gist options
  • Save sivadass/6e48218a6deba1758bd055d05d659ae4 to your computer and use it in GitHub Desktop.
Save sivadass/6e48218a6deba1758bd055d05d659ae4 to your computer and use it in GitHub Desktop.
React

React Basics

What is JSX?

JSX is a form of markup used in React. It looks very similar to HTML, but is converted to JavaScript behind the scenes. You are not required to use JSX, but JSX makes it easier to write React applications.

With JSX
const welcomeElement = <h1>Hey man!</h1>;
ReactDOM.render(welcomeElement, document.getElementById('root'));
Without JSX
const welcomeElement = React.createElement('h1', {}, 'Hey man!');
ReactDOM.render(welcomeElement, document.getElementById('root'));

You can learn more about how JSX works from this blog post.

What is a Component?

Components are the heart and soul of React. Most simply component is just a function that accept some properties and return a UI element.

Components take raw data and render it as HTML in the DOM. It describes what to render. They utilize properties (props for short) and states which contribute to this raw data. Both props and states are plain Javascript objects.

Simple Button Component
const Button = (props) => {
    const { label, onClick, loading } = props;
	return <button onClick={(e) => onClick(e)} disabled={loading}>{label}</button>
}

Class Components and Functional Components?

With React, you can make components using either classes or functions. Originally, class components were the only components that could have state. But since the introduction of React's Hooks API, you can add state and more to function components.

Class based Component
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
Function Component
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

What are Props and State?

State : This is data maintained inside a component. It is local or owned by that specific component. The component itself will update the state using the setState function.

Props : Data passed in from a parent component. props are read-only in the child component that receives them. However, callback functions can also be passed, which can be executed inside the child to initiate an update.

The difference is all about which component owns the data. State is owned locally and updated by the component itself. Props are owned by a parent component and are read-only. Props can only be updated if a callback function is passed to the child to trigger an upstream change.

The state of a parent component can be passed a prop to the child. They are referencing the same value, but only the parent component can update it.

Component Lifecycle

You can think of React lifecycle methods as the series of events that happen from the birth of a React component to its death.

  • Mounting – Birth of your component
  • Update – Growth of your component
  • Unmount – Death of your component

render()

The render() method is the most used lifecycle method. You will see it in all React classes. This is because render() is the only required method within a class component in React.

As the name suggests it handles the rendering of your component to the UI. It happens during the mounting and updating of your component.

componentDidMount()

Now your component has been mounted and ready, that’s when the next React lifecycle method componentDidMount() comes in play.

componentDidMount() is called as soon as the component is mounted and ready. This is a good place to initiate API calls, if you need to load data from a remote endpoint.

componentDidUpdate()

This lifecycle method is invoked as soon as the updating happens. The most common use case for the componentDidUpdate() method is updating the DOM in response to prop or state changes.

You can call setState() in this lifecycle, but keep in mind that you will need to wrap it in a condition to check for state or prop changes from previous state. Incorrect usage of setState() can lead to an infinite loop.

componentWillUnmount()

As the name suggests this lifecycle method is called just before the component is unmounted and destroyed. If there are any cleanup actions (removing event listeners, etc) that you would need to do, this would be the right spot.

Uncommon React Lifecycle Methods

  • shouldComponentUpdate()
  • static getDerivedStateFromProps()
  • getSnapshotBeforeUpdate()

You can learn more about the above lifecycle metheds from this page.

Managing the state

In React we can manage application state using different ways, most used ways are:

  • Local Component State (setState)
  • Hooks
  • Redux
  • Context
Local Component State (setState)

React itself provides some useful methods for setting component states using setState() and adding a “local state” to a class.

With this option, you can call these methods for your components. setState() tells React that this component and its children (sometimes delayed and grouped into a single batch) should be re-rendered with the most updated state, often bases on user-triggered events. setState() will always lead to a re-render as long as an update is available.

state = {
    test: 1
}

inc() {
	console.log('before: ' + this.state.test);
	this.setState({
		test: this.state.test+1
   	});
   	console.log('after: ' + this.state.test);
}

render() {
	return <button onClick={this.inc}>Click to update</button>
}

// click!
before: 1
after: 1

// click!
before: 2
after: 2

Hooks

The new stable hooks introduced in React 16.8.0. let you create a custom hook to manage states inside functional components. Hooks states work pretty much like like class component states, while every instance of the component has its own state.

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

You can learn more about hooks from this page.

Context API

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. For example, in the code below we manually thread through a “theme” prop in order to style the Button component:

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

Using context, we can avoid passing props through intermediate elements:

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Assign a contextType to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
Redux

Redux also allow us to share global state across the application and provides some utility functions to update state at every level in an more organized way than Context API.

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

You can use Redux together with React, or with any other view library.

Three Principles
  1. Single sourceof truth: The state of your whole application is stored in an object tree within a single store.
  2. State is read-only: The only way to change the state is to emit an action, an object describing what happened.
  3. Changes are made with pure functions: To specify how the state tree is transformed by actions, you write pure reducers.
Store

A store is an object that holds the application's state tree. There should only be a single store in a Redux app, as the composition happens on the reducer level.

  • dispatch(action) is the base dispatch function described above.
  • getState() returns the current state of the store.
  • subscribe(listener) registers a function to be called on state changes.
  • replaceReducer(nextReducer) can be used to implement hot reloading and code splitting. Most likely you won't use it.
type Store = {
  dispatch: Dispatch
  getState: () => State
  subscribe: (listener: () => void) => () => void
  replaceReducer: (reducer: Reducer) => void
}
Action

An action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.

Actions must have a type field that indicates the type of action being performed. Types can be defined as constants and imported from another module. It's better to use strings for type than Symbols because strings are serializable.

Other than type, the structure of an action object is really up to you. If you're interested, check out Flux Standard Action for recommendations on how actions should be constructed.

Reducer

A reducer (also called a reducing function) is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value.

Reducers are not unique to Redux—they are a fundamental concept in functional programming. Even most non-functional languages, like JavaScript, have a built-in API for reducing. In JavaScript, it's Array.prototype.reduce().

In Redux, the accumulated value is the state object, and the values being accumulated are actions. Reducers calculate a new state given the previous state and an action. They must be pure functions—functions that return the exact same output for given inputs. They should also be free of side-effects. This is what enables exciting features like hot reloading and time travel.

Reducers are the most important concept in Redux.

Do not put API calls into reducers.

Will see the more detailed explanation of Redux in the next section.

Redux

Redux also allow us to share global state across the application and provides some utility functions to update state at every level in an more organized way than Context API.

It helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience, such as live code editing combined with a time traveling debugger.

You can use Redux together with React, or with any other view library.

Three Principles
  1. Single sourceof truth: The state of your whole application is stored in an object tree within a single store.
  2. State is read-only: The only way to change the state is to emit an action, an object describing what happened.
  3. Changes are made with pure functions: To specify how the state tree is transformed by actions, you write pure reducers.
Store

A store is an object that holds the application's state tree. There should only be a single store in a Redux app, as the composition happens on the reducer level.

  • dispatch(action) is the base dispatch function described above.
  • getState() returns the current state of the store.
  • subscribe(listener) registers a function to be called on state changes.
  • replaceReducer(nextReducer) can be used to implement hot reloading and code splitting. Most likely you won't use it.
type Store = {
  dispatch: Dispatch
  getState: () => State
  subscribe: (listener: () => void) => () => void
  replaceReducer: (reducer: Reducer) => void
}
Action Types

An action type is a string that simply describes the type of an action. They're commonly stored as constants or collected in enumerations to help reduce typos and because programmers love organization. I've found it helpful to structure my action types like this:

export const Actions = {
  GET_USER_DETAILS_REQUEST: 'GET_USER_DETAILS_REQUEST',
  GET_USER_DETAILS_SUCCESS: 'GET_USER_DETAILS_SUCCESS',
  GET_USER_DETAILS_FAILURE: 'GET_USER_DETAILS_FAILURE',
};
Action

An action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.

Actions must have a type field that indicates the type of action being performed. Types can be defined as constants and imported from another module. It's better to use strings for type than Symbols because strings are serializable.

Other than type, the structure of an action object is really up to you. If you're interested, check out Flux Standard Action for recommendations on how actions should be constructed.

type Action = {
    type: string;    // Actions MUST have a type
    payload?: any;   // Actions MAY have a payload
};

(The question marks mean the property is optional. So you only need the type property at a minimum).

An action to fetch the user named Dan might look something like this

{
    type: 'GET_USER_DETAILS_REQUEST',
    payload: 'Dan'
}
Action Creator

As you might expect, an action creator is a function that creates and returns an action. Okay well... this is not always true. But for the purposes of learning Redux, you can pretend it's true for now.

Simple action creators look like this:

export const getUserDetailsRequest = id => ({
  type: Actions.GET_USER_DETAILS_REQUEST,
  payload: id,
});

When writing basic Redux, an action creator simply returns an action. You would typically dispatch the action to your store immediately.

Later on, you'll have to deal with asynchronous actions such as making an AJAX call to the server. If you choose to use redux-thunk to manage your asynchronous code then you'll have complex action creators that dispatch multiple actions directly to the store rather than returning a single action for you to dispatch.

Reducer

A reducer is a function which takes two arguments, the current state and an action -- and returns based on both arguments a new state. In a pseudo function it could be expressed as:

(state, action) => newState

The reducer function is a pure function without any side-effects, which means that given the same input (e.g. state and action), the expected output (e.g. newState) will always be the same. This makes reducer functions the perfect fit for reasoning about state changes and testing them in isolation. You can repeat the same test with the same input as arguments and always expect the same output:

The action is normally defined as an object with a type property. Based on the type of the action, the reducer can perform conditional state transitions:

const counterReducer = (count, action) => {
  if (action.type === 'INCREASE') {
    return count + 1;
  }
  if (action.type === 'DECREASE') {
    return count - 1;
  }
  return count;
};

The state processed by a reducer function is immutable. That means the incoming state -- coming in as argument -- is never directly changed. Therefore the reducer function always has to return a new state object. If you haven't heard about immutability, you may want to check out the topic immutable data structures.

Redux Thunk
What is thunk?

A thunk is a function that acts as a wrapper in that it wraps an expression to delay its evaluation.

// calculation of 1 + 2 is immediate
// x === 3
let x = 1 + 2;

// calculation of 1 + 2 is delayed
// foo can be called later to perform the calculation
// foo is a thunk!
let foo = () => 1 + 2;

In the context of Redux, Redux-Thunk middleware allows you to write action creators that return a function instead of the typical action object. The thunk can then be used to delay the dispatch of an action until the fulfillment of an asynchronous line of code.

The reason that we need to use a middleware such as Redux-Thunk is because the Redux store only supports synchronous data flow. Thus, middleware to the rescue! Middleware allows for asynchronous data flow, interprets anything that you dispatch and finally returns a plain object allowing the synchronous Redux data flow to resume. Redux middleware can thus solve for many critical asynchronous needs.

export const fetchTasksStarted = () => ({
  type:  "FETCH_TASKS_START"
});
export const fetchTasksSuccess = tasks => ({
  type: "FETCH_TASKS_SUCCESS",
  payload: tasks
});
export const fetchTasksError = errorMessage => ({
  type: "FETCH_TASKS_ERROR",
  payload: errorMessage
});
const fetchTasks =  () => async dispatch => {
    dispatch(fetchTasksStarted())
    try{
        const TaskResponse = await fetch("API URL")
const task = await taskResponse.json()
        dispatch(fetchTasksSuccess(tasks))
    }catch(exc){
        dispatch(fetchTasksError(error.message))
    }
}

fetchTasks() might look strange at the beginning, but it's a function that returns another function that has a parameter dispatch. Once dispatch gets called, the control flow will move to the reducer to decide what to do. In the above case, it only updates the application state, if the request has been successful.

Redux Saga

Redux-Saga aims to make application side effects (e.g., asynchronous actions such as fetching data) easier to handle and more efficient to execute.

The idea is that a saga is similar to a separate thread in your application that’s solely responsible for side effects. However, unlike Redux-Thunk, which utilizes callback functions, a Redux-Saga thread can be started, paused and cancelled from the main application with normal Redux actions. Like Redux-Thunk, Redux-Saga has access to the full Redux application state and it can dispatch Redux actions as well.

To do this, Redux-Saga utilizes a new ES6 feature called generators. Generators are functions which can be exited and later re-entered.

import { takeLatest, put } from "redux-saga/effects";
function* fetchTasksSaga(){
 try {
const taskResponse = yield fetch("API URL")
const tasks = yield taskResponse.json()
yield put(fetchTasksSuccess(tasks));
} catch (error) {
yield put(fetchTasksError(error.message));
  }
}
export default function* watchFetchTasksSaga(){
    yield takeLatest("FETCH_TASKS_START", fetchTasksSaga)
}

More detailed explanation for Redux Saga can be found here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment