Lab 07 - Styling & A/B Testing

CSS Styling

Next.js has support for native .css files that can be applied globally or at the component level.

Global Styling

pages/_app.js is the default app component used by Next.js to initialize each page. It is the starting point of all page components and acts as a blueprint for your pages. Consequently, if you want to apply global styling to your Next.js application, the stylesheets (.css files) must be included here.

It is recommended to place the global .css files in the styles directory.

/* styles/globals.css */
 
body {
  font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', sans-serif;
  padding: 20px 20px 60px;
  margin: 0 auto;
  background: white;
}
// pages/_app.jsx
 
import '../styles.css'
 
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

Component Styling

To add component-level CSS, we must use CSS modules. These are files that use the [name].module.css naming convention, can be imported anywhere in the application and help you scope the stylesheet to a particular component.

/* components/DangerousButton.module.css */
 
.danger {
    color: white;
    background-color: red;
}
.danger:hover {
    color: red;
    background-color: white;
}

For instance, we can use the CSS module to add custom styles to a Material UI button.

// components/DangerousButton.jsx
 
import styles from './DangerousButton.module.css'
import { Button } from "@mui/material";
 
export function DangerousButton(props) {
  return (
    <Button type="button" className={styles.danger} {...props}>
      { props.children }
    </Button>
  )
}

Note that we are using the className property to pass the CSS class to the component.

Also, note that we are passing all props further into the Material UI Button component using object restructuring (…props).

In this case, props.children is a special property that allows us to further pass the content inside the DangerousButton tag into the inner Button tag.

All CSS files included in the _app.jsx component are applied globally, so make sure the global style directives do not clash with the component-level ones.

JS Styling

As an alternative to the classic CSS styling, Next.js offers a variety of solutions to style your React components using JavaScript:

While the majority of these solutions are provided through 3rd party libraries, inline styles and styled jsx are available out of the box.

Inline Styles

To apply inline styling, we must use the style property.

// components/DangerousTypography.jsx
 
import { Typography } from "@mui/material";
 
export function DangerousTypography(props) {
  return (
    <Typography {...props} style={{ color: 'red' }}>
      {props.children}
    </Typography>
  );
}

The value passed to the style property is always an object (notice the double curly braces) that contains style directives, similar to those written into a CSS file.

Styled Components

Styled Components is one of the most popular libraries for adding style to your React components. It can be used globally or at the component level. However, we will only discuss the component-level styling with this library.

Conveniently, Material UI natively supports styled components, so we can easily use this library to overwrite the default styling of the MUI components.

// components/CoolCheckbox.jsx
 
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Checkbox from "@mui/material/Checkbox";
 
const StyledCheckbox = styled(Checkbox)`
  color: green!important;
`;
 
export default function CoolCheckbox({ props }) {
  return  <StyledCheckbox {...props}/>;
}

We use !important to make sure we override any other inherited styling. You can read this article if you want to find out more about CSS specificity and directives precedence.

A/B Testing

A/B testing is an accurate method of testing a particular idea, strategy or feature by performing user bucketing and simultaneously delivering two or more sources of truth to these buckets. Such solutions are usually integrated with analytics tools, so you can later assess which source of truth renders the best results.

Let's take a concrete example. You want to display 2 different versions of a button to your users - a blue one and a green one. You want to distribute this experiment evenly among your users, so you would like half of your users to see the green button and the other half the blue one. Moreover, you want this behaviour to be sticky. That means that if a user sees a green button once, they must keep seeing it green until the experiment is over. In the end, we would like to calculate the conversion rate of that particular button, so we need to track each click and record the version of the button (green or blue) for such click. Then, we must wait a sufficient amount of time so we have statistically relevant data for our conversion rate comparison. The winning button will be kept and the other one discarded.

Fortunately, there are 3rd party solutions that allow us to perform such an experiment easily. One of them is Flagsmith and we chose it for this lab because it offers a free plan.

Feature Flags

Flagsmith allows the creation of feature flags, which are the cornerstones of A/B testing. They act as toggle buttons that turn various features on and off.

feature-flags-ss.jpg

A/B Testing

To use feature flags for A/B testing, however, we need to make them multi-variate. That means that our feature flags support variations, and each variation has a particular value assigned to it. The control value is the default value that gets sent to your application if the user is not yet identified (more on that later). Finally, these values can be weighted so you can decide the percentage of users that should participate in the experiment.

multi-variate-flags.jpg

For a classic A/B test experiment, the natural tendency is to use the control value for bucket A. However, because the control value gets sent to the unidentified users no matter the weighting, the accuracy of the experiment results can be impacted. Consequently, in this case, you should always have two variations besides the control value - one for bucket A and one for bucket B.

Users

Just like Segment, Flagsmith requires users to be identified. That means that an explicit id must be provided for the user that currently uses the application. If we don't explicitly inform Flagsmith about the user's identity, that user will be considered anonymous and a random id will be assigned internally. Even though feature flags continue to work just fine for both identified and unidentified users, A/B testing can only be performed on identified users. That means that if an unidentified user uses our application, they will always see the feature version corresponding to the control value. In addition to the control value, the identified users can also see the weighted variations.

The unidentified users will not appear in the Users table.

Users can also have traits and they are useful when you want to perform user segmentation. For instance, you can choose to perform an A/B test only for males, who have premium subscriptions and are at least 21 years old.

If you click on a user from the Users table, you can inspect its traits, manually delete them or add new ones, toggle features for that particular user or even change the values of those features. That can be really useful if you have a development account that you use to test different functionalities.

flagsmith-user-details.jpg

Next.js Integration

Flagsmith has native support for Next.js, as it can run both on the client and server sides.

Initialization

The initialization code must be written in the pages/_app.jsx component on both the client-side and server-side, so the flags can be then accessed by all pages.

// pages/_app.jsx
 
import * as React from 'react';
import Head from 'next/head';
import Layout from '../layouts/layout';
import '../styles/globals.css';
import flagsmith from "flagsmith/isomorphic";
import { FlagsmithProvider } from "flagsmith/react";
 
const FLAGSMITH_OPTIONS = {
  environmentID: 'YOUR_ENVIRONMENT_ID_HERE',
  enableAnalytics: true,
  preventFetch: true
}
 
MyApp.getInitialProps = async () => {
  await flagsmith.init(FLAGSMITH_OPTIONS);
  return { flagsmithState: flagsmith.getState() }
}
 
export default function MyApp({ Component, pageProps, flagsmithState }) {
  return (
      <FlagsmithProvider serverState={flagsmithState} flagsmith={flagsmith} options={FLAGSMITH_OPTIONS}>
        <Head>
          <title>Lab 7</title>
          <meta name="description" content="This is the styling and A/B testing lab."/>
          <meta name="viewport" content="initial-scale=1, width=device-width"/>
        </Head>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </FlagsmithProvider>
  );
}

You can get your environment id from the Settings tab of your environment. We recommend using the Development environment that Flagsmith provides you with out of the box.

Feature Flags

Feature flags can then be accessed right from the React components.

// pages/index.jsx
 
import React from 'react';
import { Container, Typography } from "@mui/material";
import { useFlags } from "flagsmith/react";
 
export default function Home() {
  const flags = useFlags(['google_signup', 'payment_message']);
  const isGoogleSignupEnabled = flags['google_signup']?.enabled;
  const paymentMessageSize = flags['payment_message']?.value; // 'big', 'huge' or 'control'
 
  return (
    <Container maxWidth="sm">
      <Typography variant="p" component="p">
        Is Google signup enabled: {isGoogleSignupEnabled}
      </Typography>
      <Typography variant="p" component="p">
        Payment message size: {paymentMessageSize}
      </Typography>
    </Container>
  );
}

Even though Flagsmith allows you to fetch the flags on the backend (in getStaticProps and getServerSideProps functions), you might not be able to rely on the values of these flags because users usually get identified on the client side. Of course, this can be solved with cookies, but that exceeds the scope of this lab.

Users

Because we don't have a login system integrated into our app, we will identify a fictional user right after the app initialization code.

If we want to test the behaviour of our app for other users, we will simply change the user id (traits can stay the same) in the code and refresh the page.

// pages/_app.jsx
 
MyApp.getInitialProps = async () => {
  await flagsmith.init(FLAGSMITH_OPTIONS);
  await flagsmith.identify(
    'user_90619',
    {
      fullName: 'Marie Curie',
      email: 'marie.curie@gmail.com',
      age: 23,
      gender: 'female',
      isPremiumUser: false
    }
  );
  return { flagsmithState: flagsmith.getState() }
}

In all the other components that call the useFlags function, Flagsmith will return the flags assigned to the user identified using the code above. The values of these flags will depend on which bucket (A or B) the user belongs to. The bucketing is performed by Flagsmith automatically.

Tasks

Again, we will use Material UI as a component library for our project. All the necessary dependencies have already been included, so you can make use of the readily available components. You can check out the official docs to understand how can you include those components in your code. Some tasks will ask you to style these components to add an extra layer of customization.

The app uses the Cat Facts API and the GET requests are performed using Axios.

The necessary dependencies for the Styled Components and Flagsmith are also included in the project.

This time, we are using yarn as a package manager. So make sure you install it before tackling the tasks. If you want to know more about it, you can read a comparison between yarn and npm here.

  1. Download the project archive for this lab and run yarn install and npm run dev.
  2. Create a blue button using Styled Components in components/styled/BlueButton.jsx.
  3. Create a green button using CSS modules in components/vanilla/GreenButton.jsx. Add the corresponding CSS module in the same directory.
  4. Make the welcome message of the pages/index.jsx page component bold using inline styling.
  5. Integrate the global styling written in the styles/globals.css file into the app and remove the horizontal and top padding.
  6. Create a free Flagsmith account, set up a fictive organisation and a project called Lab 7.
  7. Create a feature in the Development environment called button_color. Create two variations. Give a default value to the control state and suggestive values to the variations (these values should describe the button colours). The weighting should be 50-50 (the control value should have weight 0).
  8. Write your environment id and place the user identification code in pages/_app.jsx.
  9. Conditionally display the See Tasks button in pages/index.jsx. If the button_color feature flag is disabled, it should be a DangerousButton. Conversely, the button should be a GreenButton or BlueButton depending on the feature flag value.
  10. Observe how the button changes when you start identifying new users. Check the Users table in the Flagsmith dashboard as well.

You should lookout for the TODOs in the code.

Feedback

Please take a minute to fill in the feedback form for this lab.

se/labs/07.txt · Last modified: 2023/10/10 01:14 by emilian.radoi
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0