Display Active Links in React Router using NavLink

It’s very common, especially in navigation lists, to display the link as the active link the user is looking at. Let’s add this treatment to our invoices list by swapping out Link for NavLink.

import { NavLink, Outlet } from "react-router-dom";
import { getInvoices } from "../data";

export default function Invoices() {
  let invoices = getInvoices();
  return (
    <div style={{ display: "flex" }}>
      <nav
        style={{
          borderRight: "solid 1px",
          padding: "1rem",
        }}
      >
        {invoices.map((invoice) => (
          <NavLink
            style={({ isActive }) => {
              return {
                display: "block",
                margin: "1rem 0",
                color: isActive ? "red" : "",
              };
            }}
            to={`/invoices/${invoice.number}`}
            key={invoice.number}
          >
            {invoice.name}
          </NavLink>
        ))}
      </nav>
      <Outlet />
    </div>
  );
}

We changed the color of our link by looking at the isActive value that NavLink passed to our styling function.

You can do the same thing with className on NavLink:

// normal string
<NavLink className="red" />

// function
<NavLink className={({ isActive }) => isActive ? "red" : "blue"} />

References
https://reactrouter.com/docs/en/v6/getting-started/tutorial#active-links

Defining Routes in React Router

index.js

import * as React from "react";
import * as ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);

App.js

import * as React from "react";
import { Routes, Route, Link } from "react-router-dom";
import "./App.css";

function App() {
  return (
    <div className="App">
      <h1>Welcome to React Router!</h1>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="about" element={<About />} />
      </Routes>
    </div>
  );
}

References
https://reactrouter.com/docs/en/v6/getting-started/installation#create-react-app

Run Code after React Component using Effect Hook

Effects Without Cleanup
we can run them and immediately forget about them.

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

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

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

Effects with Cleanup

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

We don’t have to return a named function from the effect. We called it cleanup here to clarify its purpose, but you could return an arrow function or call it something different.

Use Multiple Effects to Separate Concerns

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Optimizing Performance by Skipping Effects

You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument.

Always add everything you refer to inside of useEffect() as dependency.

References
https://reactjs.org/docs/hooks-effect.html

HTML Form Elements in React

import React, {useState} from "react";

function Info() {
    const [name, setName] = useState("");

    return (
        <div>
            <div>
                <label>
                    Name:
                    <input type="text" placeholder="Enter your name" value={name}
                           onChange={event => {
                               setName(event.target.value)
                           }}/>
                </label>

            </div>
            <div>
                Result : Hello {name}
            </div>

        </div>
    );
}

export default Info;

In most cases, it’s recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.

To write an uncontrolled component, instead of writing an event handler for every state update, you can use a ref to get form values from the DOM.

References
https://reactjs.org/docs/forms.html
https://reactjs.org/docs/uncontrolled-components.html
https://reactjs.org/docs/refs-and-the-dom.html

Install Tailwind CSS in React

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Add the paths to all of your template files in your tailwind.config.js file.

module.exports = {
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Add the @tailwind directives for each of Tailwind’s layers to your ./src/index.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;

Run your build process with npm run start.

npm run start

References
https://tailwindcss.com/docs/guides/create-react-app

Create Events for React Component

Counter.tsx

import {useState} from "react";

function Counter(props: CounterProp) {
    const [count, setCount] = useState(0)

    const buttonClicked = () => {
        setCount(prevState => {

            var result = prevState + 1;

            if (props.onCounterChanged != null) {
                props.onCounterChanged(result);
            }

            return result;
        })
    };

    return (
        <div>
            <div>Counter Value : {count}</div>
            <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
                    onClick={buttonClicked}>Next
            </button>
        </div>
    );
}

export interface CounterProp {
    onCounterChanged?: (value: number) => void;
}

export default Counter;

App.tsx

import React from 'react';
import Counter from "./components/Counter";

function App() {
    return (
        <div className={"px-1"}>
            <Counter onCounterChanged={(value => console.log(value))}></Counter>
        </div>

    );
}

export default App;

References
https://www.tutorialsteacher.com/typescript/typescript-interface
https://stackoverflow.com/questions/39713349/make-all-properties-within-a-typescript-interface-optional

Handling Events on React

import {useState} from "react";

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

    const buttonClicked = () => {
        setCount(prevState => prevState + 1)
    };

    return (
        <div>
            <div>Counter Value : {count}</div>
            <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
                    onClick={buttonClicked}>Next
            </button>
        </div>
    );
}

export default Counter;

Passing Arguments to Event Handlers

import {useState} from "react";

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

    const buttonClicked = (e: React.MouseEvent<HTMLButtonElement>) => {
        console.log(e);
        setCount(prevState => prevState + 1)
    };

    return (
        <div>
            <div>Counter Value : {count}</div>
            <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
                    onClick={(e) => buttonClicked(e)}>Next
            </button>
        </div>
    );
}

export default Counter;

Prevent default behavior

import {useState} from "react";

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

    const buttonClicked = (e: React.MouseEvent<HTMLButtonElement>) => {
        if (e.cancelable)
        {
            e.preventDefault();
        }

        setCount(prevState => prevState + 1)
    };

    return (
        <div>
            <div>Counter Value : {count}</div>
            <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
                    onClick={(e) => buttonClicked(e)}>Next
            </button>
        </div>
    );
}

export default Counter;

The preventDefault() method cancels the event if it is cancelable, meaning that the default action that belongs to the event will not occur.

For example, this can be useful when:

  • Clicking on a “Submit” button, prevent it from submitting a form
  • Clicking on a link, prevent the link from following the URL

Note: Not all events are cancelable. Use the cancelable property to find out if an event is cancelable.

Note: The preventDefault() method does not prevent further propagation of an event through the DOM. Use the stopPropagation() method to handle this.

References
https://reactjs.org/docs/handling-events.html
https://www.w3schools.com/jsref/event_preventdefault.asp
https://stackoverflow.com/questions/32298064/preventdefault-not-working-in-on-input-function