Using React Context

AuthContext

import React, {useState} from "react";

export interface AuthContextType {
    isLoggedIn: boolean,
    logIn: () => void,
    logOut: () => void,
}

const AuthContextDefault: AuthContextType = {
    isLoggedIn: false,
    logIn(): void {
    },
    logOut(): void {
    }
};

const AuthContext = React.createContext(AuthContextDefault);

export const AuthContextProvider = (props: any) => {

    // we need to initialize state from local storage because we should keep users logged in on page refresh
    const [isLoggedIn, setIsLoggedIn] = useState(() => {
        if (localStorage.getItem("isLoggedIn") === "1") {
            return true;
        }

        return false;
    });

    const logInHandler = () => {
        setIsLoggedIn(true);
        // save in local storage
        localStorage.setItem("isLoggedIn", "1");
    };

    const logOutHandler = () => {
        setIsLoggedIn(false);
        // remove from local storage
        localStorage.removeItem("isLoggedIn");
    }

    return <AuthContext.Provider value={{isLoggedIn: isLoggedIn, logIn: logInHandler, logOut: logOutHandler}}>
        {props.children}
    </AuthContext.Provider>
        ;
};

export default AuthContext;

index

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {BrowserRouter} from "react-router-dom";
import {AuthContextProvider} from "./context/AuthContext";

ReactDOM.render(
    <BrowserRouter>
        <React.StrictMode>
            <AuthContextProvider>
                <App/>
            </AuthContextProvider>
        </React.StrictMode>
    </BrowserRouter>
    ,
    document.getElementById('root')
);

reportWebVitals();

Login

import AuthContext from "../context/AuthContext";
import {useContext} from "react";

function Login() {

    const authCtx = useContext(AuthContext);

    return (
        <div className="m-2">
            <div>
                <span>Username : </span>
                <input type="text" className="input input-bordered w-full max-w-xs ml-2"
                       placeholder="Enter Username"/>
            </div>

            <div>
                <span>Password : </span>
                <input type="password" className="input input-bordered w-full max-w-xs ml-2"
                       placeholder="Enter Password"/>
            </div>
            <div className="mt-4">
                <button type="button" className="btn" onClick={authCtx.logIn}>Login</button>
                <button type="button" className="btn ml-2" onClick={authCtx.logOut}>Logout</button>
            </div>
        </div>
    );
}

export default Login;

User

import AuthContext from "../context/AuthContext";
import {useContext} from "react";

function User() {

    const authCtx = useContext(AuthContext);

    return (
        <div>
            {authCtx.isLoggedIn && "Logged in"}
            {!authCtx.isLoggedIn && "Logged out"}
        </div>
    )
}

export default User;

References
https://reactjs.org/docs/context.html

Using the useReducer Hook in React

import {useReducer} from "react";

interface CounterAction {
    type: CounterType,
    data: number
}

enum CounterType {
    INCREASE,
    DECREASE
}

interface CounterState {
    count: number
}

function CounterReducer(state: CounterState, action: CounterAction): CounterState {

    if (action.type === CounterType.INCREASE) {
        return {...state, count: state.count + action.data}
    } else if (action.type === CounterType.DECREASE) {
        return {...state, count: state.count - action.data}
    }

    return {...state};
}

function Counter() {

    // initial value for counter
    const counterInitializerArg: CounterState = {count: 0};
    // counter value
    let counterState: CounterState;
    // this is a function with CounterAction as parameter, it will increase/decrease counter value by 1
    let counterDispatch: (payload: CounterAction) => void;

    [counterState, counterDispatch] = useReducer(CounterReducer, counterInitializerArg);

    return (
        <>
            <div>
                Count : {counterState.count}
            </div>
            <div>
                <button type="button" className="btn"
                        onClick={event => counterDispatch({type: CounterType.INCREASE, data: 1})}>Increase
                </button>
                <button type="button" className="btn"
                        onClick={event => counterDispatch({type: CounterType.DECREASE, data: 1})}>Decrease
                </button>
            </div>
        </>
    );
}

export default Counter;

Specifying the initial state

There are two different ways to initialize useReducer state. You may choose either one depending on the use case. The simplest way is to pass the initial state as a second argument:

const [state, dispatch] = useReducer(
  reducer,
  {count: initialCount}
);

Lazy initialization

You can also create the initial state lazily. To do this, you can pass an init function as the third argument. The initial state will be set to init(initialArg).

It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

References
https://reactjs.org/docs/hooks-reference.html#usereducer
https://dev.to/craigaholliday/using-the-usereducer-hook-in-react-with-typescript-27m1

Use CSS Modules in React

CSS Modules let you use the same CSS class name in different files without worrying about naming clashes.

Button.module.css

.error {
  background-color: red;
}

another-stylesheet.css

.error {
  color: red;
}

Button.js

import React, { Component } from 'react';
import styles from './Button.module.css'; // Import css modules stylesheet as styles
import './another-stylesheet.css'; // Import regular stylesheet

class Button extends Component {
  render() {
    // reference as a js object
    return <button className={styles.error}>Error Button</button>;
  }
}

Result

<!-- This button has red background but not red text -->
<button class="Button_error_ax7yz">Error Button</button>

References
https://create-react-app.dev/docs/adding-a-css-modules-stylesheet/

Conditionally Joining classNames using clsx Library in React

A tiny (228B) utility for constructing className strings conditionally.
Also serves as a faster & smaller drop-in replacement for the classnames module.

npm install --save clsx
import clsx from 'clsx';
 
// Strings (variadic)
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'
 
// Objects
clsx({ foo:true, bar:false, baz:isTrue() });
//=> 'foo baz'
 
// Objects (variadic)
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
//=> 'foo --foobar'
 
// Arrays
clsx(['foo', 0, false, 'bar']);
//=> 'foo bar'
 
// Arrays (variadic)
clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]);
//=> 'foo bar baz hello there'
 
// Kitchen sink (with nesting)
clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya');
//=> 'foo bar hello world cya'

References
https://www.npmjs.com/package/clsx

Conditionally Joining classNames using classnames Library in React

npm install classnames
var classNames = require('classnames');

class Button extends React.Component {
  // ...
  render () {
    var btnClass = classNames({
      btn: true,
      'btn-pressed': this.state.isPressed,
      'btn-over': !this.state.isPressed && this.state.isHovered
    });
    return <button className={btnClass}>{this.props.label}</button>;
  }
}
var btnClass = classNames('btn', this.props.className, {
  'btn-pressed': this.state.isPressed,
  'btn-over': !this.state.isPressed && this.state.isHovered
});
classNames('foo', 'bar'); // => 'foo bar'
classNames('foo', { bar: true }); // => 'foo bar'
classNames({ 'foo-bar': true }); // => 'foo-bar'
classNames({ 'foo-bar': false }); // => ''
classNames({ foo: true }, { bar: true }); // => 'foo bar'
classNames({ foo: true, bar: true }); // => 'foo bar'

// lots of arguments of various types
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'

// other falsy values are just ignored
classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'

References
https://www.npmjs.com/package/classnames

Use Location State in React Router

Location State is a value that persists with a location that isn’t encoded in the URL. Much like hash or search params (data encoded in the URL), but stored invisibly in the browser’s memory.

You set location state in two ways: on <Link> or navigate:

<Link to="/pins/123" state={{ fromDashboard: true }} />;

let navigate = useNavigate();
navigate("/users/123", { state: partialUser });

And on the next page you can access it with useLocation:

let location = useLocation();
location.state;

References
https://reactrouter.com/docs/en/v6/getting-started/concepts#locations

Working with Query Parameters in React Router

import {useLocation, useNavigate} from 'react-router-dom'

function Welcome() {
    const location = useLocation();
    const navigate = useNavigate();
    
    // URLSearchParams defines utility methods to work with the query string of a URL.
    const queryParams = new URLSearchParams(location.search);
    const paramName = queryParams.get("name");

    console.log(location);

    return (
        <div>
            <div>
                <button className={"btn mx-2"} onClick={event => {
                    navigate("/welcome");
                }}>Reset
                </button>

                <button className={"btn mx-2"} onClick={event => {
                    navigate("/welcome?name=Mahmood");
                }}>Mahmood
                </button>

                <button className={"btn mx-2"} onClick={event => {
                    navigate("/welcome?name=Nasim");
                }}>Nasim
                </button>
            </div>

            <div>Hello {paramName}</div>
        </div>
    );
}

export default Welcome;

References
https://reactrouter.com/docs/en/v6/getting-started/concepts#locations

Typechecking With PropTypes in React Typescript

InferPropTypes from @types/prop-types can be used to create type definitions from PropTypes definitions.

npm install --save prop-types
npm install --save @types/prop-types
import PropTypes, {InferProps} from 'prop-types';

function Button(props: InferProps<typeof Button.propTypes>) {
    return (
        <button type="button">
            {props.children}
        </button>
    );
}

Button.propTypes = {
    children: PropTypes.node,
    size: PropTypes.number,
}


Button.defaultProps = {
    size: 1,
}

export default Button;

PropTypes

Here is an example documenting the different validators provided:

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // You can declare that a prop is a specific JS type. By default, these
  // are all optional.
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // Anything that can be rendered: numbers, strings, elements or an array
  // (or fragment) containing these types.
  optionalNode: PropTypes.node,

  // A React element.
  optionalElement: PropTypes.element,

  // A React element type (ie. MyComponent).
  optionalElementType: PropTypes.elementType,

  // You can also declare that a prop is an instance of a class. This uses
  // JS's instanceof operator.
  optionalMessage: PropTypes.instanceOf(Message),

  // You can ensure that your prop is limited to specific values by treating
  // it as an enum.
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // An object that could be one of many types
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // An array of a certain type
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // An object with property values of a certain type
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // An object taking on a particular shape
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),   

  // You can chain any of the above with `isRequired` to make sure a warning
  // is shown if the prop isn't provided.
  requiredFunc: PropTypes.func.isRequired,

  // A required value of any data type
  requiredAny: PropTypes.any.isRequired,

  // You can also specify a custom validator. It should return an Error
  // object if the validation fails. Don't `console.warn` or throw, as this
  // won't work inside `oneOfType`.
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // You can also supply a custom validator to `arrayOf` and `objectOf`.
  // It should return an Error object if the validation fails. The validator
  // will be called for each key in the array or object. The first two
  // arguments of the validator are the array or object itself, and the
  // current item's key.
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

References
https://reactjs.org/docs/typechecking-with-proptypes.html
https://blog.logrocket.com/comparing-typescript-and-proptypes-in-react-applications/

Relative Links in React Router

Relative <Link to> values (that do not begin with a /) are relative to the path of the route that rendered them. The two links below will link to /dashboard/invoices and /dashboard/team because they’re rendered inside of <Dashboard>.

import {
  Routes,
  Route,
  Link,
  Outlet,
} from "react-router-dom";

function Home() {
  return <h1>Home</h1>;
}

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <nav>
        <Link to="invoices">Invoices</Link>{" "}
        <Link to="team">Team</Link>
      </nav>
      <hr />
      <Outlet />
    </div>
  );
}

function Invoices() {
  return <h1>Invoices</h1>;
}

function Team() {
  return <h1>Team</h1>;
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="dashboard" element={<Dashboard />}>
        <Route path="invoices" element={<Invoices />} />
        <Route path="team" element={<Team />} />
      </Route>
    </Routes>
  );
}

References
https://reactrouter.com/docs/en/v6/getting-started/overview#relative-links