React JS

Hooks

Objectives

  1. Introduction to Hooks
  2. State Hook
  3. Effect Hook
  4. useMemo
  5. useCallback
  6. useReducer
  7. Rules of Hooks
  8. Custom Hooks

1. Introduction to Hooks

React Hooks were introduced to address some of the limitations of functional components and to provide a more cohesive way to handle state and side effects in React applications.

Benefits

  • Reusable Stateful Logic:
    With Hooks, you can extract and reuse stateful logic across multiple components using custom Hooks.

Benefits

  • Simplified Component Logic: Hooks allow you to write functional components without needing to convert them into class components just for state management.

Throw  Classes

Benefits

  • Improved Readability: Functional components using Hooks often have cleaner and more readable code.

Popularity

Widely adopted by major companies and developers worldwide

2. State Hook

The useState Hook is used for adding state to functional components.

const [state, setState] = useState(initialState);
import React, { useState } from 'react';

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

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

Example 1: Counter

import React, { useState } from 'react';

function ToggleButton() {
  const [isOn, setIsOn] = useState(false);

  const toggle = () => {
    setIsOn(!isOn);
  };

  return (
    <div>
      <button onClick={toggle}>
        {isOn ? 'Turn Off' : 'Turn On'}
      </button>
      <p>Switch is {isOn ? 'on' : 'off'}.</p>
    </div>
  );
}

Example 2: Toggling a Boolean State

import React, { useState } from 'react';

function InputField() {
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
        placeholder="Type something..."
      />
      <p>You typed: {inputValue}</p>
    </div>
  );
}

Example 3: Input Field with Controlled State

import React, { useState } from 'react';

function TemperatureConverter() {
  const [celsius, setCelsius] = useState(0);
  const [fahrenheit, setFahrenheit] = useState(32);

  const handleCelsiusChange = (event) => {
    const newCelsius = event.target.value;
    const newFahrenheit = (newCelsius * 9) / 5 + 32;
    setCelsius(newCelsius);
    setFahrenheit(newFahrenheit);
  };

  const handleFahrenheitChange = (event) => {
    const newFahrenheit = event.target.value;
    const newCelsius = ((newFahrenheit - 32) * 5) / 9;
    setFahrenheit(newFahrenheit);
    setCelsius(newCelsius);
  };
  // return part
}

Example 4: Multiple States part 1

Example 4: Multiple States part 2

return (
    <div>
      <label>
        Celsius:
        <input type="number" 
    		value={celsius} 
			onChange={handleCelsiusChange}/>
      </label>
      <br />
      <label>
        Fahrenheit:
        <input type="number" 
		value={fahrenheit} 
		onChange={handleFahrenheitChange}/>
      </label>
    </div>
  );

Example 5: Managing an Object State part 1

import React, { useState } from 'react';

function UserProfile() {
  const [user, setUser] = useState({
    firstName: '',
    lastName: '',
    email: '',
  });

  const handleInputChange = (event) => {
    const { name, value } = event.target;
    setUser({ ...user, [name]: value });
  };

  // return part
}

Example 5: Managing an Object State part 2

  return (
    <div>
      <input type="text" name="firstName" value={user.firstName} 
  		onChange={handleInputChange} placeholder="First Name" />
      <input type="text" name="lastName" value={user.lastName} 
		onChange={handleInputChange} placeholder="Last Name"/>
      <input type="email" name="email" value={user.email} 
		onChange={handleInputChange} placeholder="Email" />
      <p>{`Hello, ${user.firstName} ${user.lastName}`}</p>
      <p>Email: {user.email}</p>
    </div>
  );

3. Effect Hook

The useEffect Hook in React is used to manage side effects in functional components. It allows you to perform tasks that can't or shouldn't happen within the regular rendering process. Common use cases include data fetching, DOM manipulation, and subscribing to external data sources.

Basic Syntax

useEffect(() => {
  // Your side effect code here
  return ()=>{
    // clean up of the component
  }
}, [dependencies]);
  • The first argument is a function that contains the code you want to run as a side effect.
  • The second argument is an optional array of dependencies.

Dependencies

useEffect(() => {
  // Your side effect code here
}, [dependencies]);
  • []: empty array means this effect runs only on the mount of the component.
  • [list_of_dependencies]: every time one of these dependencies changes the effect will run.
  • no array: will run on every runder.

Example 1 : On Mount

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('Component is mounted.');
    
    return () => {
      console.log('Component is unmounted.');
    };
  }, []); // An empty dependency array means this effect runs only on mount
  return <div>My Component Content</div>;
}
export default MyComponent;

Example 2 : Fetching Data

import React, { useEffect } from 'react';

function MyComponent() {
  const [data,setData]=useState([])
  useEffect(() => {
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((data) => {
        setData(data);
      })
      .catch((error) => {
        // Handle errors
      });
  }, []);
  return <div>{data}</div>;
}
export default MyComponent;

Example 3 : Update DOM

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
  	// Update the title of the document
    document.title = `New Page Title`;
  }, []);

  return <div>My component</div>;
}
export default MyComponent;

Example 3 : Subscriptions and Event Listeners part 1

// eventEmitter.js
import { EventEmitter } from 'events';

const eventEmitter = new EventEmitter();

export const subscribe = (event, callback) => {
  eventEmitter.on(event, callback);
  return () => {
    eventEmitter.off(event, callback);
  };
};

export const publish = (event, data) => {
  eventEmitter.emit(event, data);
};

Example 3 : Subscriptions and Event Listeners part 2

function Publisher() {
  const [input, setInput] = useState('');

  const handlePublish = () => {
    publish('message', input);
    setInput('');
  };

  return (
    <div>
      <h2>Publisher</h2>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={handlePublish}>Publish</button>
    </div>
  );
}

Example 3 : Subscriptions and Event Listeners part 3

function Subscriber() {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const unsubscribe = subscribe('message', (data) => {
      setMessage(data);
    });

    // Cleanup subscription on unmount
    return () => {
      unsubscribe();
    };
  }, []);

  return (
    <div>
      <h2>Subscriber</h2>
      <p>Message: {message}</p>
    </div>
  );
}

4. Rules of Hooks

React enforces strict rules when working with Hooks to ensure consistency and avoid common issues.

Rules

  • Only Call Hooks at the Top Level: Hooks must be called in the same order in every render, and they must not be called conditionally or in loops.
  • Call Hooks from React Functions: Hooks can only be called from within functional components or custom Hooks.

5. useReducer

  • useReducer is a hook that is used for managing state in React components, similar to useState.
  • It is particularly useful for state logic that involves multiple sub-values or when the next state depends on the previous one.
  • When you have complex state logic that can’t be easily managed with `useState`.
  • When the state depends on previous state values.
  • When you want to centralize state logic outside of your component.

When to use useReducer?

  • Reducer function: A function that determines the new state based on the action dispatched.
  • Current state: The current state value managed by the reducer.
  • Action: An object that tells the reducer how to change the state. Usually has a type field and optionally other fields for payload.

Basic Concepts

const [state, dispatch] = useReducer(reducer, initialState);

Example: Counter with useReducer part 1

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

Example: Counter with useReducer part 2

import React, { useReducer } from 'react';

function Counter() {
  const initialState = { count: 0 };

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

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

export default Counter;

Example 2: To-Do List part 1

function todoReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, { id: Date.now(), text: action.text, completed: false }];
    case 'toggle':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    case 'remove':
      return state.filter(todo => todo.id !== action.id);
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

Example 2: To-Do List part 2

function TodoApp() {
  const initialState = [];

  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [text, setText] = useState('');

  const handleAddTodo = () => {
    dispatch({ type: 'add', text });
    setText('');
  };
  return (
    <div>
      <h2>To-Do List</h2>
      <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {state.map(todo => (
          <li key={todo.id} onClick={() => dispatch({ type: 'toggle', id: todo.id })}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
            <button onClick={() => dispatch({ type: 'remove', id: todo.id })}>
              Remove
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

6. useMemo

  • useMemo is a React hook that memoizes the result of a computation, caching the value and recalculating it only when one of its dependencies changes.
  • It is used to optimize performance by preventing unnecessary re-computation of expensive functions.
  • When you have an expensive computation that you don't want to run on every render.
  • When the result of a computation is stable as long as certain dependencies do not change.

When to use useMemo?

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • computeExpensiveValue: A function that performs an expensive computation.
  • [a, b]: An array of dependencies. The computation will only re-run if one of these dependencies' changes.

Example: Filtering a List part 1

const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];

function FilterComponent() {
  const [filter, setFilter] = useState('');

  const filteredItems = items.filter(item => item.toLowerCase().includes(filter.toLowerCase()));

  return (
    <div>
      <input type="text" value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="Filter items" />
      <ul>
        {filteredItems.map((item, index) => ( <li key={index}>{item}</li> ))}
      </ul>
    </div>
  );
}

export default FilterComponent;

Example: Filtering a List part 2

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

const items = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig', 'Grape'];

function FilterComponent() {
  const [filter, setFilter] = useState('');

  const filteredItems = useMemo(() => {
    return items.filter(item => item.toLowerCase().includes(filter.toLowerCase()));
  }, [filter]);

  return (
    <div>
      <input type="text" value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="Filter items" />
      <ul>
        {filteredItems.map((item, index) => ( <li key={index}>{item}</li> ))}
      </ul>
    </div>
  );
}

export default FilterComponent;

7. useCallback

  • useCallback is a React hook that returns a memoized version of a callback function.
  • It ensures the function identity remains the same between renders as long as the dependencies do not change.
  • This can be useful for passing stable references to callback functions to child components, which can help prevent unnecessary re-renders.
  • When you pass callback functions to optimized child components that rely on reference equality to prevent unnecessary re-renders (e.g., React.memo).
  • When the function depends on state or props that are stable and do not change on every render.

When to use useMemo?

const memoizedCallback = useCallback(() => {
  // Your callback function logic
}, [dependencies]);
  • callback: The function to be memorized.
  • dependencies: An array of dependencies. The callback will only be recreated if one of these dependencies' changes.

Example: Dependency Management

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

  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  const reset = useCallback(() => {
    setCount(0);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

5. Custom Hooks

Custom Hooks are functions that allow you to extract and reuse stateful logic across components.

Rules

  • Custom Hooks are named with the use prefix (e.g., useCustomHook).
  • They can use other Hooks if needed.
  • They should return data or functions that enable component customization.

Example 1 : useCounter

import { useState } from 'react';

function useCounter(initialCount) {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount(count + 1);
  };

  return { count, increment };
}

export default useCounter;

Example 2 : useDarkMode

import { useState, useEffect } from 'react';

function useDarkMode() {
  const [isDarkMode, setIsDarkMode] = useState(false);

  useEffect(() => {
    // Check local storage for user preference
    const storedValue = localStorage.getItem('darkMode');
    if (storedValue) {
      setIsDarkMode(JSON.parse(storedValue));
    }
  }, []);

  useEffect(() => {
    // Update the DOM and local storage when isDarkMode changes
    localStorage.setItem('darkMode', JSON.stringify(isDarkMode));
    document.body.classList.toggle('dark-mode', isDarkMode);
  }, [isDarkMode]);

  return [isDarkMode, setIsDarkMode];
}

export default useDarkMode;

More

Made with Slides.com