Jake
Jake

Reputation: 4255

Why create contexts instead of just exporting objects?

So I want to avoid deep nesting of props and I was starting to use React context to do this, but then it occurred to me "why don't I just export objects instead?"

For example, instead of writing:

const handleClick: = event => {
  event.preventDefault();
  doSomething();
};
const calcPrice = (quantity) = {
  return quantity * 100
};
export const ComponentContext = createContext({});

export const ParentComponent = () => {
  return (
    <ComponentContext.Provider value={{ handleClick, calcPrice }}>
      <ChildComponent quantity={12} />
    </ComponentContext.Provider>

}

And import it as:

export const ChildComponent = (quantity) = {
  const { handleClick, calcPrice } = useContext(ComponentContext);
  const totalPrice = calcPrice(quantity);
  return <button onClick={handleClick}>Total is ${totalPrice}</button>
}

I could instead simply write it as:

const handleClick: = event => {
  event.preventDefault();
  doSomething();
};
const calcPrice = (quantity) = {
  return quantity * 100
};
export const componentProps = { handleClick, calcPrice };

export const ParentComponent = () => {
  return <ChildComponent quantity={12} />
}

And import it as:

const { handleSignUpClick, calcPrice } = componentProps;
export const ChildComponent = (quantity) = {
  const totalPrice = calcPrice(quantity);
  return <button onClick={handleClick}>Total is ${totalPrice}</button>
}

What's the advantage of doing it with context instead of with functions?

Upvotes: 2

Views: 1017

Answers (1)

cbdeveloper
cbdeveloper

Reputation: 31465

It seems that in your example you're just exporting some helper functions. And in that use case, there might not be any difference between exporting an object (with those functions) and using the useContext() hook.

https://reactjs.org/docs/hooks-reference.html#usecontext

But, from React DOCs (link above), we get that:

A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

How would you achieve that (re-render of the consumers) using an exported object?

From the parent perspective, all that could trigger a re-render of a child component is when you render it with a different props object. And how would something that you exported that lives outside of the component function (since you can't export local function variables and 'methods') be able to change the props object that is created inside the component function scope?

TLDR:

The basic difference is that you can't cause a re-render of consumer children with an exported object. At least not without falling into a complete React anti-pattern.


Imagine that you have a ParentComponent that render two expensive child component, that you want to optmize for. And for that you will use React.memo() so you only re-render those child components if their props change.

The one that uses context will re-render, because the context property has changed, but the one that uses the exported variable will not re-render because all that has changed lives outside of React.

SandBox example: https://vq30v.codesandbox.io/

ParentComponent.js

import React, { useState } from "react";
import SomeContext from "./SomeContext";
import ExpensiveChildComponent from "./ExpensiveChildComponent";
import ExpensiveChildComponentExport from "./ExpensiveChildComponentExport";

let count = null; // VARIABLE THAT WILL BE EXPORTED
console.log("Outside ParentComponent...");

function ParentComponent() {
  const [myState, setMyState] = useState(0);

  console.log("Rendering Parent Component...");
  count = myState; // UPDATING THE EXPORTED VARIABLE

  function handleClick() {
    setMyState(prevState => prevState + 1);
  }

  // SETTING UP CONTEXT PROVIDER
  return (
    <div>
      <SomeContext.Provider value={myState}>
        <button onClick={handleClick}>Count</button>
        <h3>Uses Context</h3>
        <ExpensiveChildComponent />
        <h3>Uses Exported Object</h3>
        <ExpensiveChildComponentExport />
      </SomeContext.Provider>
    </div>
  );
}

console.log("After ParentComponent declaration...");

export { ParentComponent, count }; // EXPORTING COMPONENT AND VARIABLE

ExpensiveChildComponent.js (uses context)

import React, { useContext } from "react";
import SomeContext from "./SomeContext";

// REACT MEMO WILL ONLY UPDATE IF PROPS OR CONTEXT HAS CHANGED
const ExpensiveChildComponent = React.memo(function ExpensiveChildComponent() {
  console.log("Rendering ExpensiveChildComponent...");
  const context = useContext(SomeContext);
  return <div>{context}</div>;
});

export default ExpensiveChildComponent;

ExpensiveChildComponentExport.js (uses an exported property)

import React from "react";
import { count } from "./ParentComponent"; // IMPORTING THE EXPORTED VARIABLE

console.log("Outside ExpensiveChildComponentExport...");

// REACT MEMO WILL ONLY UPDATE IF PROPS OR CONTEXT HAS CHANGED (AND BOTH ARE NOT BEING USED)
const ExpensiveChildComponentExport = React.memo(
  function ChildComponentExport() {
    console.log("Rendering ExpensiveChildComponentExport...");
    return (
      <React.Fragment>
        <div>{count}</div>
      </React.Fragment>
    );
  }
);

export default ExpensiveChildComponentExport;

RESULT:

enter image description here

NOTE:

If you remove React.memo from the ExpensiveChildComponentExport it will re-render, because React creates a new props object on each render (it will be an empty object, but it will be a different one on every time). That's why I added React.memo(), because it will perform a shallow compare on the props object. So I can illustrate the behavior that useContext has and an mere exported object doesn't.

Upvotes: 1

Related Questions