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
- Integrate Javascript SDK
- Identify user on login and reset on logout to ensure that preference changes are tagged to the correct user
- Configure notification categories on SuprSend dashboard
- Understanding of the preference data structure and methods
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
- 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. - 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:
- Category Preference - User can set preferences at overall category level or select specific channels in a category from this section.
- Channel Preferences - User can set preferences at overall channel level from this section.
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:
- Loop through the state preferenceData.sections for showing sections, show sub-categories inside each section, and show subcategory's channels inside each sub-category
- 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
- 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 theopt-out
state.
- 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 theopt-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:
- 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.
- Add a radio button next, against channel level options for switching from
all
torequired
preference in channel. - 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,
}}
/>
);
}
Updated 8 months ago