React

This section describes how to integrate user preferences functionality in React Applications.

This document will cover the methods to integrate User Preferences in your React Applications. Your users will be able to specify their notification preferences using this page. With SuprSend, user can set preferences at 3 levels - communication channel, notification category and selected channels inside a category. We'll cover methods to read and update data at all 3 levels.

There's an example code to add our pre-defined UI at the end of this documentation. This is how a typical preference page will look like


All the data points and functions are same as Javascript application

Pre-requisites


Integration

All preference methods and properties are available under suprsend.user.preferences instance. Here's a reference of all the properties and methods available in this instance. Please read to javascript documentation to understand all these methods and properties in detail


Read Preferences data

There will be a Main Preference component and inside it a React state which contains preference data. Whenever there's an update we will update the latest preference data in the same state for the component to rerender.

import suprsend, {
  ChannelLevelPreferenceOptions,
  PreferenceOptions,
} from "@suprsend/web-sdk";

// this is main component
export default function Preference(){
  const [preferenceData, setPreferenceData] = useState();
  
  ...
  ...
}

Step 1. Get Preference data

After adding the component, call suprsend.user.preferences.get_preferences() method to get the preferences data for the already identified user. This method is used to get full user preferences data from the SuprSend. This method should be called first before any update methods. Calling this method will make an API call and returns a preference response, which you can store in your react state if there is no error.


Step 2. Configure Event Listeners

After calling get_preferences, call these 2 event listeners

  1. preferences_updated - This event is fired when you get a successful response after calling get_preferences method. In the callback, you will get the latest preference data as a parameter. You can use this data to update the latest preference data in the existing React state for the component to render.
  2. preferences_error - When there are validation errors or API errors SDK will fire preferences_error event. In the callback, you will then get error-related information as a parameter.
function Preferences() {
  const [preferenceData, setPreferenceData] = useState();

  useEffect(() => {
      suprsend.user.preferences.get_preferences().then((resp) => {
        if (resp.error) {
          console.log(resp.message);
        } else {
          setPreferenceData(resp);
        }
      });

      // listen for update in preferences data
      suprsend.emitter.on("preferences_updated", (preferenceData) => {
        setPreferenceData({ ...preferenceData });
      });

      // listen for errors
      suprsend.emitter.on("preferences_error", (error) => {
        console.log("ERROR:", error);
      });
    }, []);
}

Show Preference data on the UI

Preferences data is of 2 types:

  1. Category Preference - User can set preferences at overall category level or select specific channels in a category from this section.
Category Preference

Category Preference


  1. Channel Preferences - User can set preferences at overall channel level from this section.
Channel Preference

Channel Preference


Code block to Render Header and Blank state

Our Main component Preferences has 2 child components one for each type of preference.

export default function Preferences() {
  const [preferenceData, setPreferenceData] = useState();

  useEffect(() => {
     ...
  }, []);

  if (!preferenceData) return <p>Loading...</p>;
  return (
    <div style={{ margin: 24 }}>
      <h3 style={{ marginBottom: 24 }}>Notification Preferences</h3>
      <NotificationCategoryPreferences
        preferenceData={preferenceData}
        setPreferenceData={setPreferenceData}
      />
      <ChannelLevelPreferences
        preferenceData={preferenceData}
        setPreferenceData={setPreferenceData}
      />
    </div>
  );
}

Show Category level Preference section on the UI

In category-level preferences, you'll have to fetch the data from 3 parts:

  • Section - to show sections like "Product Updates" in below example
  • Category - to show categories and their overall status like "Refunds" in below example
  • CategoryChannel - to show communication channels inside the category and their status


Below are the steps to render category preference UI:

  1. Loop through the state preferenceData.sections for showing sections, show sub-categories inside each section, and show subcategory's channels inside each sub-category

  2. Add a switch button next to each sub-category for opting in and out of the category. Add checkbox components in sub-category channels for opting in and out of category-channel. You can use any third-party npm package to import these components or design your own component

  3. To update category preference on the click of the switch button, call update_category_preference method and if no error is received in response, update the latest data in the state. For preference state opt-in set the switch state as on and off for the opt-out state.


  1. To update category-channel preference on the click of checkbox next to each channel, call the update_channel_preference_in_category method. Update the latest data in the state if no error is received in response. For preference state opt-in set the checkbox state as checked and unchecked for the opt-out state.


Code block to Render Category section

import suprsend, { PreferenceOptions } from "@suprsend/web-sdk";

const handleCategoryPreferenceChange = (
  data,
  subcategory,
  setPreferenceData
) => {
  const resp = suprsend.user.preferences.update_category_preference(
    subcategory.category,
    data ? PreferenceOptions.OPT_IN : PreferenceOptions.OPT_OUT
  );
  if (resp.error) {
    console.log(resp.message);
  } else {
    setPreferenceData({ ...resp });
  }
};

const handleChannelPreferenceInCategoryChange = (
  channel,
  subcategory,
  setPreferenceData
) => {
  if (!channel.is_editable) return;

  const resp = suprsend.user.preferences.update_channel_preference_in_category(
    channel.channel,
    channel.preference === PreferenceOptions.OPT_IN
      ? PreferenceOptions.OPT_OUT
      : PreferenceOptions.OPT_IN,
    subcategory.category
  );
  if (resp.error) {
    console.log(resp.message);
  } else {
    setPreferenceData({ ...resp });
  }
};


function NotificationCategoryPreferences({
  preferenceData,
  setPreferenceData,
}) {
  if (!preferenceData.sections) {
    return null;
  }
  return preferenceData.sections?.map((section, index) => {
    return (
      <div style={{ marginBottom: 24 }} key={index}>
        {section?.name && (
          <div
            style={{
              backgroundColor: "#FAFBFB",
              paddingTop: 12,
              paddingBottom: 12,
              marginBottom: 18,
            }}
          >
            <p
              style={{
                fontSize: 18,
                fontWeight: 500,
                color: "#3D3D3D",
              }}
            >
              {section.name}
            </p>
            <p style={{ color: "#6C727F" }}>{section.description}</p>
          </div>
        )}

        {section?.subcategories?.map((subcategory, index) => {
          return (
            <div
              key={index}
              style={{
                borderBottom: "1px solid #D9D9D9",
                paddingBottom: 12,
                marginTop: 18,
              }}
            >
              <div
                style={{
                  display: "flex",
                  justifyContent: "space-between",
                  alignItems: "center",
                }}
              >
                <div>
                  <p
                    style={{
                      fontSize: 16,
                      fontWeight: 600,
                      color: "#3D3D3D",
                    }}
                  >
                    {subcategory.name}
                  </p>
                  <p style={{ color: "#6C727F", fontSize: 14 }}>
                    {subcategory.description}
                  </p>
                </div>
                <Switch
                  disabled={!subcategory.is_editable}
                  onChange={(data) => {
                    handleCategoryPreferenceChange(
                      data,
                      subcategory,
                      setPreferenceData
                    );
                  }}
                  uncheckedIcon={false}
                  checkedIcon={false}
                  height={20}
                  width={40}
                  onColor="#2563EB"
                  checked={subcategory.preference === PreferenceOptions.OPT_IN}
                />
              </div>

              <div style={{ display: "flex", gap: 10, marginTop: 12 }}>
                {subcategory?.channels.map((channel, index) => {
                  return (
                    <Checkbox
                      key={index}
                      value={channel.preference}
                      title={channel.channel}
                      disabled={!channel.is_editable}
                      onClick={() => {
                        handleChannelPreferenceInCategoryChange(
                          channel,
                          subcategory,
                          setPreferenceData
                        );
                      }}
                    />
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
    );
  });
}

Show Channel level Preference section on the UI

Below are the steps to render channel preference UI:

  1. Loop through the state in preferenceData.channel_preferences for showing channels and for every channel item we will show an option to select preference using radio buttons.

  2. Add a radio button next, against channel level options for switching from all to required preference in channel.

  3. To update channel preference on the click of the radio button, call update_overall_channel_preference method, and if no error is received in response, update the latest data in the state.

Code block to Render Channel section

import suprsend, { ChannelLevelPreferenceOptions } from "@suprsend/web-sdk";

function ChannelLevelPreferences({ preferenceData, setPreferenceData }) {
  return (
    <div>
      <div
        style={{
          backgroundColor: "#FAFBFB",
          paddingTop: 12,
          paddingBottom: 12,
          marginBottom: 18,
        }}
      >
        <p
          style={{
            fontSize: 18,
            fontWeight: 500,
            color: "#3D3D3D",
          }}
        >
          What notifications to allow for channel?
        </p>
      </div>
      <div>
        {preferenceData.channel_preferences ? (
          <div>
            {preferenceData.channel_preferences?.map((channel, index) => {
              return (
                <ChannelLevelPreferernceItem
                  key={index}
                  channel={channel}
                  setPreferenceData={setPreferenceData}
                />
              );
            })}
          </div>
        ) : (
          <p>No Data</p>
        )}
      </div>
    </div>
  );
}

const handleOverallChannelPreferenceChange = (
  channel,
  status,
  setPreferenceData
) => {
  const resp = suprsend.user.preferences.update_overall_channel_preference(
    channel.channel,
    status
  );
  if (resp.error) {
    console.log(resp.message);
  } else {
    setPreferenceData({ ...resp });
  }
};


function ChannelLevelPreferernceItem({ channel, setPreferenceData }) {
  const [isActive, setIsActive] = useState(false);

  return (
    <div
      style={{
        border: "1px solid #D9D9D9",
        borderRadius: 5,
        padding: "12px 24px",
        marginBottom: 24,
      }}
    >
      <div
        style={{
          cursor: "pointer",
        }}
        onClick={() => setIsActive(!isActive)}
      >
        <p
          style={{
            fontSize: 18,
            fontWeight: 500,
            color: "#3D3D3D",
          }}
        >
          {channel.channel}
        </p>
        <p style={{ color: "#6C727F", fontSize: 14 }}>
          {channel.is_restricted
            ? "Allow required notifications only"
            : "Allow all notifications"}
        </p>
      </div>
      {isActive && (
        <div style={{ marginTop: 12, marginLeft: 24 }}>
          <p
            style={{
              color: "#3D3D3D",
              fontSize: 16,
              fontWeight: 500,
              marginTop: 12,
              borderBottom: "1px solid #E8E8E8",
            }}
          >
            {channel.channel} Preferences
          </p>
          <div style={{ marginTop: 12 }}>
            <div style={{ marginBottom: 8 }}>
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                }}
              >
                <div>
                  <input
                    type="radio"
                    name={`all- ${channel.channel}`}
                    value={true}
                    id={`all- ${channel.channel}`}
                    checked={!channel.is_restricted}
                    onChange={() => {
                      handleOverallChannelPreferenceChange(
                        channel,
                        ChannelLevelPreferenceOptions.ALL,
                        setPreferenceData
                      );
                    }}
                  />
                </div>
                <label
                  htmlFor={`all- ${channel.channel}`}
                  style={{ marginLeft: 12 }}
                >
                  All
                </label>
              </div>
              <p style={{ color: "#6C727F", fontSize: 14, marginLeft: 22 }}>
                Allow All Notifications, except the ones that I have turned off
              </p>
            </div>
            <div>
              <div style={{ display: "flex", alignItems: "center" }}>
                <div>
                  <input
                    type="radio"
                    name={`required- ${channel.channel}`}
                    value={true}
                    id={`required- ${channel.channel}`}
                    checked={channel.is_restricted}
                    onChange={() => {
                      handleOverallChannelPreferenceChange(
                        channel,
                        ChannelLevelPreferenceOptions.REQUIRED,
                        setPreferenceData
                      );
                    }}
                  />
                </div>
                <label
                  htmlFor={`required- ${channel.channel}`}
                  style={{ marginLeft: 12 }}
                >
                  Required
                </label>
              </div>
              <p style={{ color: "#6C727F", fontSize: 14, marginLeft: 22 }}>
                Allow only important notifications related to account and
                security settings
              </p>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

Example Code to Render Full Preference Page

import { useState, useEffect } from "react";
import Switch from "react-switch";
import suprsend, {
  ChannelLevelPreferenceOptions,
  PreferenceOptions,
} from "@suprsend/web-sdk";

// -------------- Category Level Preferences -------------- //

const handleCategoryPreferenceChange = (
  data,
  subcategory,
  setPreferenceData
) => {
  const resp = suprsend.user.preferences.update_category_preference(
    subcategory.category,
    data ? PreferenceOptions.OPT_IN : PreferenceOptions.OPT_OUT
  );
  if (resp.error) {
    console.log(resp.message);
  } else {
    setPreferenceData({ ...resp });
  }
};

const handleChannelPreferenceInCategoryChange = (
  channel,
  subcategory,
  setPreferenceData
) => {
  if (!channel.is_editable) return;

  const resp = suprsend.user.preferences.update_channel_preference_in_category(
    channel.channel,
    channel.preference === PreferenceOptions.OPT_IN
      ? PreferenceOptions.OPT_OUT
      : PreferenceOptions.OPT_IN,
    subcategory.category
  );
  if (resp.error) {
    console.log(resp.message);
  } else {
    setPreferenceData({ ...resp });
  }
};

function NotificationCategoryPreferences({
  preferenceData,
  setPreferenceData,
}) {
  if (!preferenceData.sections) {
    return null;
  }
  return preferenceData.sections?.map((section, index) => {
    return (
      <div style={{ marginBottom: 24 }} key={index}>
        {section?.name && (
          <div
            style={{
              backgroundColor: "#FAFBFB",
              paddingTop: 12,
              paddingBottom: 12,
              marginBottom: 18,
            }}
          >
            <p
              style={{
                fontSize: 18,
                fontWeight: 500,
                color: "#3D3D3D",
              }}
            >
              {section.name}
            </p>
            <p style={{ color: "#6C727F" }}>{section.description}</p>
          </div>
        )}

        {section?.subcategories?.map((subcategory, index) => {
          return (
            <div
              key={index}
              style={{
                borderBottom: "1px solid #D9D9D9",
                paddingBottom: 12,
                marginTop: 18,
              }}
            >
              <div
                style={{
                  display: "flex",
                  justifyContent: "space-between",
                  alignItems: "center",
                }}
              >
                <div>
                  <p
                    style={{
                      fontSize: 16,
                      fontWeight: 600,
                      color: "#3D3D3D",
                    }}
                  >
                    {subcategory.name}
                  </p>
                  <p style={{ color: "#6C727F", fontSize: 14 }}>
                    {subcategory.description}
                  </p>
                </div>
                <Switch
                  disabled={!subcategory.is_editable}
                  onChange={(data) => {
                    handleCategoryPreferenceChange(
                      data,
                      subcategory,
                      setPreferenceData
                    );
                  }}
                  uncheckedIcon={false}
                  checkedIcon={false}
                  height={20}
                  width={40}
                  onColor="#2563EB"
                  checked={subcategory.preference === PreferenceOptions.OPT_IN}
                />
              </div>

              <div style={{ display: "flex", gap: 10, marginTop: 12 }}>
                {subcategory?.channels.map((channel, index) => {
                  return (
                    <Checkbox
                      key={index}
                      value={channel.preference}
                      title={channel.channel}
                      disabled={!channel.is_editable}
                      onClick={() => {
                        handleChannelPreferenceInCategoryChange(
                          channel,
                          subcategory,
                          setPreferenceData
                        );
                      }}
                    />
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
    );
  });
}

// -------------- Channel Level Preferences -------------- //

const handleOverallChannelPreferenceChange = (
  channel,
  status,
  setPreferenceData
) => {
  const resp = suprsend.user.preferences.update_overall_channel_preference(
    channel.channel,
    status
  );
  if (resp.error) {
    console.log(resp.message);
  } else {
    setPreferenceData({ ...resp });
  }
};

function ChannelLevelPreferernceItem({ channel, setPreferenceData }) {
  const [isActive, setIsActive] = useState(false);

  return (
    <div
      style={{
        border: "1px solid #D9D9D9",
        borderRadius: 5,
        padding: "12px 24px",
        marginBottom: 24,
      }}
    >
      <div
        style={{
          cursor: "pointer",
        }}
        onClick={() => setIsActive(!isActive)}
      >
        <p
          style={{
            fontSize: 18,
            fontWeight: 500,
            color: "#3D3D3D",
          }}
        >
          {channel.channel}
        </p>
        <p style={{ color: "#6C727F", fontSize: 14 }}>
          {channel.is_restricted
            ? "Allow required notifications only"
            : "Allow all notifications"}
        </p>
      </div>
      {isActive && (
        <div style={{ marginTop: 12, marginLeft: 24 }}>
          <p
            style={{
              color: "#3D3D3D",
              fontSize: 16,
              fontWeight: 500,
              marginTop: 12,
              borderBottom: "1px solid #E8E8E8",
            }}
          >
            {channel.channel} Preferences
          </p>
          <div style={{ marginTop: 12 }}>
            <div style={{ marginBottom: 8 }}>
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                }}
              >
                <div>
                  <input
                    type="radio"
                    name={`all- ${channel.channel}`}
                    value={true}
                    id={`all- ${channel.channel}`}
                    checked={!channel.is_restricted}
                    onChange={() => {
                      handleOverallChannelPreferenceChange(
                        channel,
                        ChannelLevelPreferenceOptions.ALL,
                        setPreferenceData
                      );
                    }}
                  />
                </div>
                <label
                  htmlFor={`all- ${channel.channel}`}
                  style={{ marginLeft: 12 }}
                >
                  All
                </label>
              </div>
              <p style={{ color: "#6C727F", fontSize: 14, marginLeft: 22 }}>
                Allow All Notifications, except the ones that I have turned off
              </p>
            </div>
            <div>
              <div style={{ display: "flex", alignItems: "center" }}>
                <div>
                  <input
                    type="radio"
                    name={`required- ${channel.channel}`}
                    value={true}
                    id={`required- ${channel.channel}`}
                    checked={channel.is_restricted}
                    onChange={() => {
                      handleOverallChannelPreferenceChange(
                        channel,
                        ChannelLevelPreferenceOptions.REQUIRED,
                        setPreferenceData
                      );
                    }}
                  />
                </div>
                <label
                  htmlFor={`required- ${channel.channel}`}
                  style={{ marginLeft: 12 }}
                >
                  Required
                </label>
              </div>
              <p style={{ color: "#6C727F", fontSize: 14, marginLeft: 22 }}>
                Allow only important notifications related to account and
                security settings
              </p>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

function ChannelLevelPreferences({ preferenceData, setPreferenceData }) {
  return (
    <div>
      <div
        style={{
          backgroundColor: "#FAFBFB",
          paddingTop: 12,
          paddingBottom: 12,
          marginBottom: 18,
        }}
      >
        <p
          style={{
            fontSize: 18,
            fontWeight: 500,
            color: "#3D3D3D",
          }}
        >
          What notifications to allow for channel?
        </p>
      </div>
      <div>
        {preferenceData.channel_preferences ? (
          <div>
            {preferenceData.channel_preferences?.map((channel, index) => {
              return (
                <ChannelLevelPreferernceItem
                  key={index}
                  channel={channel}
                  setPreferenceData={setPreferenceData}
                />
              );
            })}
          </div>
        ) : (
          <p>No Data</p>
        )}
      </div>
    </div>
  );
}

// -------------- Main component -------------- //

export default function Preferences() {
  const [preferenceData, setPreferenceData] = useState();

  useEffect(() => {
    // call suprsend.identify method before calling below method
    suprsend.user.preferences.get_preferences().then((resp) => {
      if (resp.error) {
        console.log(resp.message);
      } else {
        setPreferenceData(resp);
      }
    });

    // listen for update in preferences data
    suprsend.emitter.on("preferences_updated", (preferenceData) => {
      setPreferenceData({ ...preferenceData });
    });

    // listen for errors
    suprsend.emitter.on("preferences_error", (error) => {
      console.log("ERROR:", error);
    });
  }, []);

  if (!preferenceData) return <p>Loading...</p>;
  return (
    <div style={{ margin: 24 }}>
      <h3 style={{ marginBottom: 24 }}>Notification Preferences</h3>
      <NotificationCategoryPreferences
        preferenceData={preferenceData}
        setPreferenceData={setPreferenceData}
      />
      <ChannelLevelPreferences
        preferenceData={preferenceData}
        setPreferenceData={setPreferenceData}
      />
    </div>
  );
}

// -------------- Custom Checkbox Component -------------- //
function Checkbox({ title, value, onClick, disabled }) {
  const selected = value === PreferenceOptions.OPT_IN;

  return (
    <div
      style={{
        border: "0.5px solid #B5B5B5",
        display: "inline-flex",
        padding: "0px 20px 0px 4px",
        borderRadius: 30,
        cursor: disabled ? "not-allowed" : "pointer",
      }}
      onClick={onClick}
    >
      <Circle selected={selected} disabled={disabled} />
      <p
        style={{
          marginLeft: 8,
          color: "#6C727F",
          marginTop: 1,
          fontWeight: 500,
          paddingBottom: 4,
        }}
      >
        {title}
      </p>
    </div>
  );
}

function Circle({ selected, disabled }) {
  const bgColor = selected
    ? disabled
      ? "#BDCFF8"
      : "#2463EB"
    : disabled
    ? "#D0CFCF"
    : "#FFF";

  return (
    <div
      style={{
        height: 20,
        width: 20,
        borderRadius: 100,
        border: "0.5px solid #A09F9F",
        backgroundColor: bgColor,
        marginTop: 3.6,
      }}
    />
  );
}