3

When a stateful variable that's been instantiated using the useState() React Hook in a Context Component I've built is updated (using a setState() method), one of my functional components that consumes this variable doesn't update. Should this variable be consumed directly in the functional component, or passed as a prop from a parent functional component?

Expected Behavior

The user clicks a button, triggering the deletion of one of the notifications they've set. The UI then deletes that notification from the table row displaying in the UI. When there are no notifications, an option to add one is presented to the user. When the user then adds a notification, it appears in the UI as a table row.

Observed Behavior

The user is able to delete notifications (and this reflects in the table instantly), but when all notifications are deleted and the user attempts to add one, it is successfully added to by using a setState() method in the context provider component, but this does not cause the functional component consuming this variable via the useContext() method to update the UI.

Relevant Code Snippets

Context Component Instantiates the alert Variable and Uses Reducer Function to Update It

import React, { createContext, useState } from 'react'
import { sampleUser } from '../SampleData'

export const SettingsContext = createContext();

const SettingsContextProvider = props => {
    
    // Instantiates alerts array (via sample data), and establishes the setAlert update method
    const [alerts, setAlerts] = useState(importedSampleUser.withoutAlert);

    ...
    
    /** Reducer function that handles all notification modifications. 
    * @param {type} string Add, delete, personal, or organizational.
    * @param {obj} obj Object containing the details needed to complete the action on the backend.
    */
    const updateAlerts = (type, obj) => {
      switch (type) {

        // Creates an empty array if notificaitons have all been deleted
        case "add notification":
          if (!alerts.length) {
            setAlerts([]);
          };
          let newAlertArray = alerts;
          newAlertArray.push({
            id: obj.id,
            type: "Birthday",
            group: obj.group,
            hoursPrior: obj.hoursPrior
          });

          // Updates the variable consumed by the UI Component
          setAlerts(newAlertArray);
          break;

        case "delete notification":
          let withoutAlert = alerts;
          withoutAlert = withoutAlert.filter(alert => alert.id !== obj.id);
          setAlerts(withoutAlert);
          break;
        default:
          console.log("Oops! No more alert update types available.");
          return;
      }
    }

UI Component Builds Layout

const PersonalAlerts = () => {

  // Holds basic layout
  return (
    <div>
      <h5>Your Alerts</h5>
      {/* Displays a table with a list of notifications the user has set   */}
      <AlertTable />
    </div>
  );

Child UI Component Creates a Table Based on alert Variable Consumed from Context

const AlertTable = () => {
  // Consumes the alerts state from the Context Component
  const { alerts, updateAlerts } = useContext(SettingsContext);

  // Handles personal alert delete requests.
  const deleteAlert = (e, type, id) => {
    e.preventDefault();
    // Dispatches action to Settings Context
    updateAlerts("delete personal", { id });
  };

  // Builds an element in the table for each alert the user has set.
  let tableElements = alerts.map(alert => {
    ...

    return (
      <tr key={alert.id}>
        {/* Builds alert row for each alert in array received from Context */}
        <td>{alert.type}</td>
      </tr>
    );
  });

  // Builds a table row to display if there are no personal alerts to display.
  const noAlerts = (
    <tr>
      <td onClick={() => triggerAddNotificationUI()}>Add a notificaiton</td>
    </tr>
  );
};

Questions

  1. Why, when the alerts stateful variable is updated in the Context Component, doesn't the UI--specifically, the AlertTable Component--re-render? Should a re-render be triggered by a change in the variable consumed by useContext()?
  2. Is it better practice to consume a Context variable directly in a UI Component (e.g. AlertTable), or to consume it "higher in the Component tree" (e.g. PersonalAlerts), then pass it down as a prop to the child component?

Thanks in advance for your help.

Community
  • 1
  • 1
Davis Jones
  • 1,504
  • 3
  • 17
  • 25
  • in your AlertTable code `updateAlerts` is undefined.. where does that come from? – azium Mar 13 '20 at 18:30
  • 1
    and the answer to number 2 is it makes no difference. – azium Mar 13 '20 at 18:31
  • Good question @azium. I updated the snippet to show that it's being consumed in the AlertTable UI component. – Davis Jones Mar 13 '20 at 18:37
  • @azium - I disagree. Context is to be used sparingly [react even says so](https://reactjs.org/docs/context.html#before-you-use-context). In extremely general terms, if you've got to prop-drill/callback-drill more than 2 levels, then yeah, context is ok. But in general you should let props do all the work. In libraries (things like form libraries) context works fine too because you know how the components will be used. But in general, in dumb UI components you should definitely not use context for the exact same reasons you shouldn't use redux (redux is just a context) in "dumb" components. – Adam Jenkins Mar 13 '20 at 18:41
  • 1
    @DaxJones Also your reducer is wrong `newAlertsArray === alerts` - you're mutating state. Which explains `but this does not cause the functional component consuming this variable via the useContext() method to update the UI.` - that's because `state` hasn't changed, it's the same reference. – Adam Jenkins Mar 13 '20 at 18:43
  • Add on to previous comment, for very experienced react devs who know EXACTLY what they are doing and the reasons behind it, then they can break all the rules that a junior react dev will think are sacred, that's ok, they know (or should) what they are doing and why. Unless you are a reasonably seasoned react dev, stay away from context (in everything except "sandbox/playground" code) at all costs. – Adam Jenkins Mar 13 '20 at 18:44
  • So @Adam are you suggesting that the whole idea of handling these alerts via Context is silly? Are you suggesting that all the logic surrounding these alerts should be done in a single Component, or a couple of proximate Components, rather than via context? Thanks for sharing your thoughts. – Davis Jones Mar 13 '20 at 18:47
  • To question 1 - it depends how many levels down the tree you are and if you can reorganize your components in a way where you don't have to use it. I'm not suggesting it's silly, I'm suggesting that most of the time for most of the people context is unnecessary and when you use it, you should have a very good/specific reason to use it. IMO I don't think you have one here. – Adam Jenkins Mar 13 '20 at 18:49
  • 1
    To question 2 - all the logic doesn't have to be done in a component, per se - it could be done in hook (maybe semantics, a component uses a hook) but at least this way it isolates component logic from hook logic making both smaller and more easily testable. – Adam Jenkins Mar 13 '20 at 18:50
  • One final thing, redux (which is context) might make the most sense for you here from taking another skin at what you’re trying to achieve. It has the added benefit over “plain context” of having dev tools and a pattern (dispatching actions) to follow. – Adam Jenkins Mar 13 '20 at 18:58

0 Answers0