Input Forms, React Lifecycle and Hooks

Input Forms

React provides a way to handle forms and inputs in a seamless and efficient manner. Here's an easy guide on how to handle forms and input fields in React.

Controlled Components

In React, controlled components are form elements that are controlled by the state of the component. This means that the form data is handled by the component’s state.

Handling Form Submission

Handling form submission involves preventing the default form behavior and then using the form data, typically by updating the component’s state or calling an external API.

const handleSubmit = (event) => {
  event.preventDefault();
  // Handle form data here
};

Handling Text Input

import React, { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  const handleChange = (event) => {
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    // Prevent page to reload after submisstion.
    event.preventDefault();

    alert('A name was submitted: ' + name);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" value={name} onChange={handleChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default NameForm;

Handling Multiple Inputs

When you have multiple input fields, you can manage them by using a single state object.

import React, { useState } from 'react';

function UserForm() {

    const [user, setUser] = useState({name: "", age: ""});

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

    const handleAge = (event) => {
        setUser({...user, age: event.target.value});
    }

  return (
    <div>
        <input type="text" value={user.name} onChange={handleName} placeholder='Enter name'/>
        <input type="number" value={user.age} onChange={handleAge} placeholder='Enter age' />
        <p>Name: {user.name}, Age: {user.age}</p>
    </div>
  );
}

export default UserForm;

Handling Textarea and Select Elements

Textarea:

import React, { useState } from 'react';

function EssayForm() {
  const [essay, setEssay] = useState('');

  const handleChange = (event) => {
    setEssay(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert('An essay was submitted: ' + essay);
  };

  return (
        <form onSubmit={handleSubmit}>
            <label>
                Essay:
                <textarea
                    value={essay}
                    onChange={handleChange}
                    placeholder="Please write an essay about your favorite DOM element."
                    cols={30}
                    rows={5}
                />
            </label>
            <button type="submit">Submit</button>
        </form>
  );
}

export default EssayForm;

Select:

import React, { useState } from 'react';

function FlavorForm() {
  const [flavor, setFlavor] = useState('coconut');

  const handleChange = (event) => {
    setFlavor(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert('Your favorite flavor is: ' + flavor);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Pick your favorite flavor:
        <select value={flavor} onChange={handleChange}>
          <option value="grapefruit">Grapefruit</option>
          <option value="lime">Lime</option>
          <option value="coconut">Coconut</option>
          <option value="mango">Mango</option>
        </select>
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default FlavorForm;

Uncontrolled Components

While controlled components rely on React state to manage form data as we have seen so far, uncontrolled components store their own state internally using refs.

import React, { useRef } from 'react';

function UncontrolledForm() {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    alert('A name was submitted: ' + inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={inputRef} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;
  • Controlled Components: Form elements whose value is controlled by React state.

  • Handling Multiple Inputs: Use a single state object to manage multiple input fields.

  • Textarea and Select: Managed similarly to text inputs.

  • Form Submission: Prevent default form behavior and handle data through state or external APIs.

  • Controlled vs. Uncontrolled: Controlled components use React state, while uncontrolled components use refs to manage form data.

Using controlled components is the recommended way in React because it provides more control over form data and better integration with the component’s state and lifecycle.


React Lifecycle

React components have a lifecycle, which consists of several phases: mounting, updating, and unmounting. React provides lifecycle method hooks in functional components that allow you to execute code at specific points during a component’s lifecycle.

With the introduction of hooks in React 16.8, functional components, React lifecycle management is handled using hooks, primarily the useEffect hook. The useEffect hook can replicate the behavior of several lifecycle methods found in class components, including componentDidMount, componentDidUpdate, and componentWillUnmount.

The useEffect Hook

The useEffect hook takes two arguments:

  1. A function that contains the side effect logic.

  2. An optional array of dependencies that determine when the side effect should be run.

Syntax

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

function MyComponent() {
  useEffect(() => {
    // Side effect logic here
  }, []); // Dependency array
}

Replicating Class Component Lifecycle Methods

componentDidMount

To replicate componentDidMount, use useEffect with an empty dependency array. This ensures that the effect runs only once after the initial render.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('Component mounted');

    // Perform any setup work here, such as API calls

    return () => {
      // Cleanup work (if any)
    };
  }, []); // Empty dependency array

  return (
    <div>
      <p>Component Content</p>
    </div>
  );
}

export default MyComponent;

componentDidUpdate

To replicate componentDidUpdate, include variables in the dependency array that should trigger the effect when they change.

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

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

  useEffect(() => {
    console.log('Component updated with count:', count);

    // Perform update-related work here

  }, [count]); // Dependency array

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

export default MyComponent;

componentWillUnmount

To replicate componentWillUnmount, return a cleanup function from the useEffect hook. This function runs when the component is unmounted.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    console.log('Component mounted');

    return () => {
      console.log('Component will unmount');
      // Perform any cleanup work here, such as cancelling timers or subscriptions
    };
  }, []); // Empty dependency array

  return (
    <div>
      <p>Component Content</p>
    </div>
  );
}

export default MyComponent;

Combining Lifecycle Methods

You can combine the behaviors of multiple lifecycle methods by including multiple useEffect hooks or by using a single useEffect with conditional logic.

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

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

  // componentDidMount and componentWillUnmount
  useEffect(() => {
    console.log('Component mounted');

    return () => {
      console.log('Component will unmount');
    };
  }, []);

  // componentDidUpdate
  useEffect(() => {
    console.log('Component updated with count:', count);
  }, [count]);

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

export default MyComponent;
  • Mounting (componentDidMount): Use useEffect(() => {}, []).

  • Updating (componentDidUpdate): Use useEffect(() => {}, [dependency]).

  • Unmounting (componentWillUnmount): Return a cleanup function from useEffect.

Using useEffect, you can handle side effects in functional components efficiently, covering all the aspects of the React component lifecycle found in class components. This makes functional components a powerful and flexible option for building React applications.


React Hooks

React hooks provide a way to use stateful logic and lifecycle features in functional components. Two essential hooks are useRef and useEffect.

useRef

The useRef hook creates a mutable object that persists for the lifetime of the component. It can be used to access DOM elements directly, store a mutable value that doesn’t trigger a re-render when updated, and maintain any mutable object across renders.

To create a ref, call useRef and pass the initial value.

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

function MyComponent() {
  const inputRef = useRef(null);

  useEffect(() => {
    // Focus the input field when the component mounts
    inputRef.current.focus();
  }, []);

  return (
    <div>
      <input ref={inputRef} type="text" />
    </div>
  );
}

export default MyComponent;

Storing Mutable Values

Refs can store any mutable value without causing a re-render when updated.

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

function Timer() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  useEffect(() => {
    countRef.current = count; // Keep ref updated with state
  }, [count]);

  const handleAlertClick = () => {
    setTimeout(() => {
      alert('Count: ' + countRef.current);
    }, 3000);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={handleAlertClick}>Show alert in 3 seconds</button>
    </div>
  );
}

export default Timer;

useEffect

The useEffect hook allows you to perform side effects in function components. As told before it combines the behavior of several lifecycle methods from class components: componentDidMount, componentDidUpdate, and componentWillUnmount.

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

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

  useEffect(() => {
    console.log('Component mounted or updated');
  });

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

export default MyComponent;

Effect with Cleanup

To perform cleanup (similar to componentWillUnmount), return a function from the effect.

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

function MyComponent() {
  useEffect(() => {
    console.log('Component mounted');
    return () => {
      console.log('Component will unmount');
    };
  }, []); // Empty dependency array to run only on mount and unmount

  return <div>My Component</div>;
}

export default MyComponent;

Conditional Effects

To run the effect conditionally (similar to componentDidUpdate), pass dependencies as the second argument.

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

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

  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]); // Run only when 'count' changes

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

export default MyComponent;

useRef

  • Creating Refs: const ref = useRef(initialValue);

  • Accessing DOM Elements: Attach ref to a DOM element to directly manipulate it.

  • Storing Mutable Values: Use refs to store values that don’t trigger re-renders.

useEffect

  • Basic Side Effects: Run code after every render.

  • Conditional Effects: Run code only when specified dependencies change.

  • Cleanup: Perform cleanup by returning a function from the effect.

Combining useRef and useEffect

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

function Stopwatch() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef(null);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setTime(prevTime => prevTime + 1);
    }, 1000);
    return () => clearInterval(intervalRef.current);
  }, []);

  return (
    <div>
      <p>Time: {time} seconds</p>
      <button onClick={() => clearInterval(intervalRef.current)}>Stop</button>
    </div>
  );
}

export default Stopwatch;

In this example, useEffect starts an interval timer when the component mounts, and useRef stores the interval ID to clear it when the component unmounts or when the user clicks the "Stop" button. This demonstrates the power of combining useRef and useEffect for managing side effects and mutable values in functional components.