Communication between sibling components in react

This is one of the most asked interview questions in React interviews.

Inter component communication in react is pretty simple if the communication is from parent to child. it is done using what we call "prop drilling". The communication from child to parent is done using events.

But how do you pass a message to a component that is neither parent nor child ?

There are two ways to do this.

  1. Use something like Redux.

Redux like libraries can be seen as "databases" that all components have access to. You can subscribe to certain nodes in the Redux, and then you can also dispatch events to this store. This can be seen as read and write operations to a database.

Here it does not matter who and why is changing a certain state but the components who are interested in that change can always subscribe.

  1. Context

React realized that Redux like model is very complicated for many projects and increases complexity of react code base. Not to mention when done incorrectly it can also impact performance. React hence introduced a concept call 'Context'.

A context is an object created using 'createContext' hook in react. Once created you can pass it to any component tree by wrapping that component tree inside <Provider> component.

Any component in that component tree can then access that object using "useContext" hook.

A detailed discussion on Context

Generally, it is always better to do prop drilling in react. There is nothing wring with it and if your components are well designed, it is very readable.

But contexts are very useful when you have to communicate from children to parents or siblings. For example a button on your page that toggles the whole site into light or dark mode.

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

// Create the context object
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}
function MyComponent() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div>
      <p>The current theme is: {theme}</p> 
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}

What you notice here is that ThemeContext is an object created and shared with the component tree. The object is fully customizable and there is no restriction on what you can put in the object. The API is entirely upto you. Which also means the design can evolve and change with time with more entropy.

Redux or Context ?

A lot of large companies like to use Redux. One reason here is that Redux is well understood and battle tested library. Context on other hand is relatively new feature and requires co-ordination between multiple teams to make sure you are designing it properly.

In my opinion though, context is a better choice because it is a first class citizen of the react world. Secondly, this sort of context based communication is actually a very commonly used pattern in say Android programming or Flutter like programming and has worked well and scaled well.

Another feature I like about contexts is that unlike Redux, you can really control which component tree has access to what data. In redux too you can achieve this but contexts make it much more simple.

I think contexts are also a more elegant design. What components are really interested in is features and not raw data. It makes sense that a component trying to change your Theme has access to a themeContext object which provides an elegant api to make theme related changes. Unlike say in redux where it "dispatches" and "subscribes" to events that have no semantic meaning.