Sharing State Between Two Components in React JavaScript

Avatar

By squashlabs, Last Updated: April 7, 2024

Sharing State Between Two Components in React JavaScript

React State Management: Sharing State Between Components

In React JavaScript, it is common to have multiple components that need to share state. This allows you to create a more modular and reusable codebase. Sharing state between components can be achieved using various techniques such as props drilling, context API, and state lifting. In this section, we will explore these techniques and provide examples to illustrate how to share state between components in React.

Related Article: 25 Handy Javascript Code Snippets for Everyday Use

Props Drilling

Props drilling is a technique where you pass down props from a parent component to a child component, and then to its child component, and so on. This can become cumbersome and lead to code that is difficult to maintain, especially when dealing with deeply nested components. However, it can be a simple and effective way to share state between components in certain scenarios.

Here’s an example to illustrate props drilling:

// ParentComponent.js
import React from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
  const [count, setCount] = React.useState(0);

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

  return (
    <div>
      <h2>Parent Component</h2>
      <p>Count: {count}</p>
      
    </div>
  );
};

export default ParentComponent;
// ChildComponent.js
import React from 'react';

const ChildComponent = ({ count, incrementCount }) => {
  return (
    <div>
      <h3>Child Component</h3>
      <p>Count: {count}</p>
      <button>Increment</button>
    </div>
  );
};

export default ChildComponent;

In this example, the ParentComponent maintains the count state using the useState hook. The count state is then passed down to the ChildComponent as a prop, along with the incrementCount function. The ChildComponent displays the count and provides a button to increment it. When the button is clicked, the incrementCount function from the ParentComponent is called, updating the count state.

While props drilling can work well for simple applications or shallow component hierarchies, it becomes less manageable as the application grows. In such cases, other techniques like the context API or state lifting may be more suitable.

Context API

The context API is a built-in feature in React that allows you to share state between components without explicitly passing props through every level of the component tree. It provides a way to create global state that can be accessed by any component within the application.

To use the context API, you need to create a context object and wrap the relevant components with a context provider. This provider component makes the context available to its child components, allowing them to access and update the shared state.

Here’s an example to illustrate the use of the context API:

// MyContext.js
import React from 'react';

const MyContext = React.createContext();

export default MyContext;
// ParentComponent.js
import React from 'react';
import ChildComponent from './ChildComponent';
import MyContext from './MyContext';

const ParentComponent = () => {
  const [count, setCount] = React.useState(0);

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

  return (
    
      <div>
        <h2>Parent Component</h2>
        <p>Count: {count}</p>
        
      </div>
    
  );
};

export default ParentComponent;
// ChildComponent.js
import React from 'react';
import MyContext from './MyContext';

const ChildComponent = () => {
  const { count, incrementCount } = React.useContext(MyContext);

  return (
    <div>
      <h3>Child Component</h3>
      <p>Count: {count}</p>
      <button>Increment</button>
    </div>
  );
};

export default ChildComponent;

In this example, we create a MyContext object using the createContext function from React. The ParentComponent wraps its child components with the MyContext.Provider component and passes the count state and incrementCount function as the value prop. The ChildComponent uses the useContext hook to access the shared state and function from the context.

The context API provides a convenient way to share state between components without the need for props drilling. However, it should be used judiciously as it can make the codebase less predictable and harder to understand if not used properly.

React Lifecycle Methods: Understanding Component Lifecycle

In React JavaScript, components go through a lifecycle of events from initialization to destruction. Understanding the component lifecycle is crucial for managing state, performing side effects, and optimizing performance in React applications. In this section, we will explore the different lifecycle methods available in React and their purposes.

Related Article: Accessing Parent State from Child Components in Next.js

Class Component Lifecycle Methods

In class components, the component lifecycle is divided into three main phases: mounting, updating, and unmounting. Each phase has corresponding lifecycle methods that are automatically called by React at specific points in the component’s lifecycle.

Mounting Phase

During the mounting phase, the component is being created and inserted into the DOM. The following lifecycle methods are called in order:

1. constructor(): This is the first method called when a component is created. It is used to initialize state and bind event handlers.

2. static getDerivedStateFromProps(props, state): This method is called right before rendering the component and allows you to update the component’s state based on changes in props. It is rarely used in practice and has been mostly replaced by the componentDidUpdate method.

3. render(): This method is responsible for rendering the component’s UI. It should be a pure function that returns the JSX representation of the component.

4. componentDidMount(): This method is called immediately after the component is mounted and inserted into the DOM. It is commonly used to fetch data from an API, set up event listeners, or start timers.

Here’s an example that demonstrates the mounting phase lifecycle methods:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: 'Hello, World!'
    };
  }

  static getDerivedStateFromProps(props, state) {
    // Update state based on props
    if (props.name !== state.name) {
      return {
        name: props.name
      };
    }
    return null;
  }

  componentDidMount() {
    // Fetch data from an API
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        this.setState({ data });
      });
  }

  render() {
    return (
      <div>
        <h2>{this.state.message}</h2>
        <p>{this.state.data}</p>
      </div>
    );
  }
}

In this example, the constructor method is used to initialize the component’s state and bind event handlers. The getDerivedStateFromProps method is used to update the state based on changes in props. The render method returns the JSX representation of the component. Finally, the componentDidMount method is used to fetch data from an API and update the component’s state.

Updating Phase

During the updating phase, the component receives new props or state and re-renders. The following lifecycle methods are called in order:

1. static getDerivedStateFromProps(props, state): This method is called before rendering when new props are received. It allows you to update the component’s state based on changes in props. However, it is rarely used in practice and has been mostly replaced by the componentDidUpdate method.

2. shouldComponentUpdate(nextProps, nextState): This method is called before re-rendering and allows you to control whether the component should update or not. By default, it returns true, but you can implement custom logic to optimize performance.

3. render(): This method is responsible for rendering the component’s UI. It should be a pure function that returns the JSX representation of the component.

4. getSnapshotBeforeUpdate(prevProps, prevState): This method is called right before the changes from the virtual DOM are reflected in the DOM. It allows you to capture some information from the DOM before it potentially changes.

5. componentDidUpdate(prevProps, prevState, snapshot): This method is called after the component has been updated and the changes have been applied to the DOM. It is commonly used to perform side effects, such as making API requests or updating the DOM directly.

Here’s an example that demonstrates the updating phase lifecycle methods:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  static getDerivedStateFromProps(props, state) {
    // Update state based on props
    if (props.value !== state.value) {
      return {
        value: props.value
      };
    }
    return null;
  }

  shouldComponentUpdate(nextProps, nextState) {
    // Only update if the count is even
    return nextState.count % 2 === 0;
  }

  render() {
    return (
      <div>
        <h2>{this.state.count}</h2>
      </div>
    );
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Capture the scroll position before update
    return window.pageYOffset;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // Restore the scroll position after update
    window.scrollTo(0, snapshot);
  }
}

In this example, the getDerivedStateFromProps method is used to update the component’s state based on changes in props. The shouldComponentUpdate method is used to control whether the component should update or not, based on the count state. The render method returns the JSX representation of the component. The getSnapshotBeforeUpdate method is used to capture the scroll position before the component updates. Finally, the componentDidUpdate method is used to restore the scroll position after the update.

Unmounting Phase

During the unmounting phase, the component is being removed from the DOM. The following lifecycle method is called:

1. componentWillUnmount(): This method is called right before the component is unmounted and removed from the DOM. It is commonly used to clean up resources, such as event listeners or timers.

Here’s an example that demonstrates the unmounting phase lifecycle method:

class MyComponent extends React.Component {
  componentDidMount() {
    this.timerId = setInterval(() => {
      console.log('Tick');
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timerId);
  }

  render() {
    return (
      <div>
        <h2>Component with Timer</h2>
      </div>
    );
  }
}

In this example, the componentDidMount method is used to start a timer that logs ‘Tick’ every second. The componentWillUnmount method is used to clean up the timer before the component is unmounted.

Functional Component Lifecycle Methods

Functional components are a newer feature in React and do not have their own lifecycle methods. However, with the introduction of React hooks, functional components can now have lifecycle-like behavior using the useEffect hook.

The useEffect hook allows you to perform side effects in functional components, such as fetching data from an API, subscribing to events, or updating the DOM. It combines the functionality of multiple lifecycle methods into a single hook.

Here’s an example that demonstrates the use of the useEffect hook:

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

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

  useEffect(() => {
    // Fetch data from an API
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        console.log(data);
      });

    // Set up event listeners
    window.addEventListener('resize', handleResize);

    // Clean up event listeners
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  const handleResize = () => {
    console.log('Resize');
  };

  return (
    <div>
      <h2>{count}</h2>
      <button> setCount(count + 1)}>Increment</button>
    </div>
  );
};

In this example, the useEffect hook is used to fetch data from an API and log it to the console. It also sets up an event listener for the resize event and logs ‘Resize’ whenever the window is resized. The hook returns a cleanup function that removes the event listener when the component is unmounted.

The useEffect hook takes two arguments: a callback function that contains the side effect logic, and an optional array of dependencies. The dependencies array specifies the values that the effect depends on. If any of the dependencies change, the effect will be re-run. If the dependencies array is empty, the effect will only run once, similar to the componentDidMount lifecycle method.

React Hooks: Simplifying State Management and Side Effects

React hooks are a new feature introduced in React 16.8 that allows you to use state and other React features in functional components. They provide a more concise and intuitive way to manage state and perform side effects compared to class components and lifecycle methods.

In this section, we will explore some of the most commonly used React hooks and demonstrate how they simplify state management and side effects in functional components.

Related Article: Advanced DB Queries with Nodejs, Sequelize & Knex.js

useState Hook

The useState hook allows you to add state to functional components. It takes an initial value as an argument and returns an array with two elements: the current state value and a function to update the state.

Here’s an example that demonstrates the use of the useState hook:

import React, { useState } from 'react';

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

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

  return (
    <div>
      <h2>Counter: {count}</h2>
      <button>Increment</button>
    </div>
  );
};

In this example, the useState hook is used to add a count state variable to the component with an initial value of 0. The setCount function returned by the hook is used to update the count state when the button is clicked.

The useState hook simplifies state management by encapsulating the state logic within the component function. It eliminates the need for class components and the confusion caused by this keyword and binding.

useEffect Hook

The useEffect hook allows you to perform side effects in functional components. It takes a callback function as an argument and executes it after the component has rendered. It can also optionally specify dependencies to control when the effect should run.

Here’s an example that demonstrates the use of the useEffect hook:

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

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

  useEffect(() => {
    document.title = `Count: ${count}`;

    return () => {
      document.title = 'React App';
    };
  }, [count]);

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

  return (
    <div>
      <h2>Count: {count}</h2>
      <button>Increment</button>
    </div>
  );
};

In this example, the useEffect hook is used to update the document title with the current count value. The effect is only run when the count state changes, thanks to the [count] dependency array. The effect also returns a cleanup function that restores the document title when the component is unmounted.

The useEffect hook simplifies side effect management by providing a clear and declarative way to specify when and how the effect should run. It replaces the need for multiple lifecycle methods and their corresponding logic.

Custom Hooks

Custom hooks are a way to reuse stateful logic between components. They are regular JavaScript functions that use React hooks internally. Custom hooks can be used to extract stateful logic into reusable functions, making the code more modular and maintainable.

Here’s an example of a custom hook:

import { useState, useEffect } from 'react';

const useFetchData = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const data = await response.json();
        setData(data);
        setLoading(false);
      } catch (error) {
        console.error(error);
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading };
};

In this example, the useFetchData custom hook encapsulates the logic for fetching data from a given URL. It uses the useState and useEffect hooks internally to manage the state and perform the side effect. The hook returns an object with the fetched data and a loading flag to indicate whether the data is being fetched.

Custom hooks enable code reuse and separation of concerns by abstracting away common stateful logic. They can be used across multiple components, making it easier to share and maintain state-related code.

React hooks have revolutionized the way developers write React components by providing a simpler and more intuitive API for managing state and performing side effects. They have gained wide adoption in the React community and are considered a best practice for writing functional components.

Related Article: Advanced Node.js: Event Loop, Async, Buffer, Stream & More

JavaScript Components vs React Components: Key Differences

JavaScript components and React components share some similarities in terms of their purpose and structure, but they also have key differences that set them apart. In this section, we will explore the differences between JavaScript components and React components and discuss the advantages of using React components.

Structure and Syntax

JavaScript components can be implemented using different patterns, such as classes, functions, or object literals. The structure and syntax of JavaScript components can vary depending on the chosen pattern and the developer’s preferences.

React components, on the other hand, have a specific structure and syntax defined by the React library. React components can be implemented as classes or functions, but the recommended approach is to use functional components with hooks. Functional components with hooks offer a more concise and readable syntax compared to class components.

Here’s an example of a JavaScript component implemented as a class:

class MyComponent {
  constructor() {
    // Component initialization
  }

  render() {
    // Component rendering
  }

  componentDidMount() {
    // Component lifecycle method
  }

  // Other component methods
}

And here’s an example of a React component implemented as a functional component with hooks:

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

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

  useEffect(() => {
    // Side effect logic
  }, [count]);

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

As you can see, the React component has a more declarative and concise syntax, especially when using functional components with hooks.

Virtual DOM and Rendering Efficiency

One of the key advantages of React components is the use of a virtual DOM. The virtual DOM is a lightweight copy of the actual DOM that React uses to efficiently update the UI. When a React component’s state or props change, React compares the virtual DOM with the actual DOM and only updates the necessary parts, minimizing the number of DOM manipulations.

JavaScript components, on the other hand, do not have this optimization mechanism built-in. In JavaScript components, developers are responsible for manually updating the DOM when the component’s state or properties change. This can be error-prone and lead to inefficient rendering.

React’s virtual DOM and its diffing algorithm make React components more efficient and performant compared to JavaScript components. The virtual DOM allows React to update the UI in a smart and optimized way, resulting in faster rendering and better user experience.

Related Article: AI Implementations in Node.js with TensorFlow.js and NLP

Component Reusability and Composition

Another advantage of React components is their reusability and composition. React components are designed to be modular and composable, allowing developers to build complex UIs by combining smaller, reusable components.

JavaScript components can also be reusable, but they often lack the composition capabilities provided by React. JavaScript components tend to be more tightly coupled and harder to reuse in different contexts.

React’s component-based architecture promotes reusability and composition, enabling developers to create UI components that are decoupled, self-contained, and easily composable. This leads to cleaner, more maintainable code and promotes code reuse across projects.

Community and Ecosystem

React has a large and active community of developers, which has led to the growth of a rich ecosystem of libraries, tools, and resources. The React ecosystem includes popular libraries like Redux for state management, React Router for routing, and Material-UI for UI components, among others. This vibrant ecosystem makes it easier for developers to build sophisticated applications using React.

JavaScript components, on the other hand, do not have a dedicated community or ecosystem like React. While there are many JavaScript libraries and frameworks available, they are not specifically focused on component-based development like React.

Being part of the React community and ecosystem provides developers with access to a wealth of resources, support, and best practices. It also makes it easier to collaborate with other developers and leverage existing solutions to common problems.

In conclusion, while JavaScript components and React components share some similarities, React components offer distinct advantages in terms of structure, syntax, rendering efficiency, component reusability, and the wider community and ecosystem. React components provide a more modern and efficient way to build UI components, making React the preferred choice for many developers working on complex web applications.

JavaScript State in Components: Managing State in JavaScript

State management is an important aspect of building JavaScript applications. It allows you to store and manage data that can change over time. In this section, we will explore different approaches to managing state in JavaScript components and discuss their advantages and disadvantages.

Related Article: AngularJS: Check if a Field Contains a MatFormFieldControl

Local Component State

The simplest and most common way to manage state in JavaScript components is to use local component state. Local state refers to data that is specific to a particular component and is not shared with other components.

Local state can be managed using the useState hook in modern JavaScript frameworks like React. The useState hook allows you to define and update state within functional components. It returns an array with two elements: the current state value and a function to update the state.

Here’s an example that demonstrates the use of local component state with the useState hook:

import React, { useState } from 'react';

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

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

  return (
    <div>
      <h2>Counter: {count}</h2>
      <button>Increment</button>
    </div>
  );
};

In this example, the useState hook is used to add a count state variable to the component with an initial value of 0. The setCount function returned by the hook is used to update the count state when the button is clicked.

Local component state is suitable for managing data that is specific to a single component and does not need to be shared with other components. It provides a simple and intuitive way to manage state within the component itself.

Global State Management

In some cases, you may need to share state between multiple components or across different parts of your application. Global state management solutions can help you achieve this by providing a centralized store for your application’s state.

There are several libraries and frameworks available for global state management in JavaScript, such as Redux, MobX, and Zustand. These libraries provide a way to define and update global state that can be accessed by any component within the application.

Here’s an example that demonstrates the use of Redux for global state management:

import React from 'react';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';

// Redux actions
const increment = () => ({
  type: 'INCREMENT'
});

// Redux reducer
const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    default:
      return state;
  }
};

// Redux store
const store = createStore(reducer);

const Counter = () => {
  const count = useSelector(state => state);
  const dispatch = useDispatch();

  const handleIncrement = () => {
    dispatch(increment());
  };

  return (
    <div>
      <h2>Counter: {count}</h2>
      <button>Increment</button>
    </div>
  );
};

const App = () => {
  return (
    
      
    
  );
};

In this example, Redux is used to manage the global state of the application. The state is defined and updated using actions and a reducer. The useSelector hook is used to access the global state within the Counter component, and the useDispatch hook is used to dispatch actions to update the state.

Global state management is suitable for managing data that needs to be shared between multiple components or across different parts of your application. It provides a centralized and predictable way to manage state, making it easier to reason about and maintain.

JavaScript Event Handling in Components: Handling User Interactions

Event handling is a fundamental part of building interactive JavaScript applications. It allows you to respond to user interactions, such as button clicks, form submissions, or keyboard inputs. In this section, we will explore different approaches to handling events in JavaScript components and discuss their advantages and disadvantages.

Related Article: Big Data Processing with Node.js and Apache Kafka

Inline Event Handlers

The simplest way to handle events in JavaScript components is to use inline event handlers. Inline event handlers are defined directly in the HTML markup and are executed when the corresponding event occurs.

Here’s an example that demonstrates the use of inline event handlers:

import React from 'react';

const Button = () => {
  const handleClick = () => {
    console.log('Button clicked');
  };

  return (
    <button>Click me</button>
  );
};

In this example, the handleClick function is defined as an inline event handler for the onClick event. When the button is clicked, the function is executed and logs ‘Button clicked’ to the console.

Inline event handlers provide a simple and straightforward way to handle events in JavaScript components. They are easy to understand and require minimal setup. However, they can become cumbersome and hard to maintain when dealing with complex logic or multiple events.

Event Listeners

Another approach to handling events in JavaScript components is to use event listeners. Event listeners are functions that are attached to DOM elements and are executed when the corresponding event occurs.

Here’s an example that demonstrates the use of event listeners:

import React, { useEffect } from 'react';

const Button = () => {
  useEffect(() => {
    const button = document.getElementById('my-button');
    button.addEventListener('click', handleClick);

    return () => {
      button.removeEventListener('click', handleClick);
    };
  }, []);

  const handleClick = () => {
    console.log('Button clicked');
  };

  return (
    <button id="my-button">Click me</button>
  );
};

In this example, an event listener is attached to the button element using the addEventListener method. The handleClick function is executed when the button is clicked and logs ‘Button clicked’ to the console. The event listener is removed when the component is unmounted using the cleanup function returned by the useEffect hook.

Event listeners provide a more flexible and useful way to handle events in JavaScript components compared to inline event handlers. They allow you to attach multiple event handlers to the same element and handle events that are not directly supported by React.

However, event listeners can be harder to reason about and maintain compared to inline event handlers, especially when dealing with complex event logic or multiple events. They also require manual cleanup to prevent memory leaks.

React Synthetic Events

React provides its own event system called synthetic events. Synthetic events are cross-browser wrappers around the native browser events. They have the same interface as native events but with some additional features and improvements.

To handle events in React components using synthetic events, you can use the same inline event handlers or event listeners approach as in regular JavaScript components. React’s event system ensures that the event handlers are executed consistently across different browsers and provides additional features like event pooling and event delegation.

Here’s an example that demonstrates the use of synthetic events:

import React from 'react';

const Button = () => {
  const handleClick = (event) => {
    event.preventDefault();
    console.log('Button clicked');
  };

  return (
    <button>Click me</button>
  );
};

In this example, the handleClick function is defined as an inline event handler for the onClick event. The event object is passed as an argument to the function, allowing you to access additional information about the event. In this case, the preventDefault method is called to prevent the default behavior of the button.

React’s synthetic events provide a consistent and cross-browser way to handle events in JavaScript components. They abstract away the differences between browser implementations and provide additional features for handling events.

Related Article: Conditional Flow in JavaScript: Understand the 'if else' and 'else if' Syntax and More

Additional Resources

How State Works in React
Understanding React Components
React Props Explained

You May Also Like

25 Handy Javascript Code Snippets for Everyday Use

Solve everyday coding problems with our tutorial on 25 practical Javascript code snippets. From adding numbers to converting temperature units, these snippets will help... read more

Advanced DB Queries with Nodejs, Sequelize & Knex.js

Learn how to set up advanced database queries and optimize indexing in MySQL, PostgreSQL, MongoDB, and Elasticsearch using JavaScript and Node.js. Discover query... read more

Advanced Node.js: Event Loop, Async, Buffer, Stream & More

Node.js is a powerful platform for building scalable and web applications. In this article, we will explore advanced features of Node.js such as the Event Loop, Async... read more

AI Implementations in Node.js with TensorFlow.js and NLP

This article provides a detailed look at using TensorFlow.js and open-source libraries to implement AI functionalities in Node.js. The article explores the role of... read more

AngularJS: Check if a Field Contains a MatFormFieldControl

AngularJS developers often encounter the need to ensure that their Mat Form Field contains a MatFormFieldControl. This article provides guidance on how to achieve this... read more

Big Data Processing with Node.js and Apache Kafka

Handling large datasets and processing real-time data are critical challenges in today's data-driven world. In this article, we delve into the power of Node.js and... read more