Creating a Switch: Part III

25m

Intermediate

Welcome to the third and final part of the Designing a Switch in Framer X series, a group of tutorials where we’ll be creating and using a code component in Framer X.

In Part I, we:

  • Set up our Framer X workspace
  • Created a code component for our Switch
  • Defined our component’s state logic
  • Presented our component’s state using animations

In Part II, we:

  • Added props to our Switch, so that we could set its state from outside of the component
  • Added property controls for our Switch, so that we could set our component’s props through Framer X’s user interface

In Part III, we’ll:

  • Give our Switch an event handler prop, so that we can share its state with the rest of our project
  • Use overrides to change a second component when our switchy Switch switches

If you’re new to Framer X (or to React), then this tutorial is a great introduction to working with code. I’d recommend completing Part I and Part II before working through this one.

Getting Started

We’ll be making this project with Framer X. If you don’t have it already, download a copy and enjoy the 14-day trial.

Also, if you’d like to pick up where we left off, download the finished project from Part II of this tutorial.

Sharing state with an event handler

In the last article, we learned about how to get information into our component from the outside world through props. While our switch can now receive information, it still has no way of sharing information with other components. In other words, our switch can be switched, but it can’t switch anything else!

In order to give other parts of our project a way to respond when the user flips our switch, we’ll need to give our component an event handler prop.

Event handlers are callback functions that a component will call when an event occurs. Components will usually call the event handlers with arguments containing information about the event that has occurred. In Framer X, frames and other components use events as a way to respond to user interactions.

Adding our event handler

We’ll call our event handler onValueChange. Let’s start by adding onValueChange to our component’s default props.

  1. With our project open in Framer X, open the code tab by selecting the code icon in the left panel, or by pressing Command + 4.
  2. In the list of files, select our component’s file: Switch.tsx.
  3. In our component’s file, find our component’s defaultProps definition. It’ll be down below our component.
  4. In our component’s defaultProps, add a new property named onValueChange. Set it to a function that accepts a boolean argument called isOn and returns null.
import * as React from "react"
import { Frame } from "framer"
export function Switch(props) {
...
}
Switch.defaultProps = {
isOn: false,
onValueChange: (isOn: boolean) => null
}
COPY

Event handlers are usually named by putting on before the name of the event. For example, a “click” event handler will usually be named onClick, a “crash” event handler will usually be named onCrash, and so on. Beyond making our components easier to use, the names we use have no effect on how the function works.

Calling the event handler

Next, let’s call props.onValueChange each time our switch flips.

  1. Back in our component, find the flipSwitch function.
  2. Above our setState call, insert a line that calls props.onValueChange with the new value of state.isOn.
const flipSwitch = () => {
props.onValueChange(!state.isOn)
setState({
isOn: !state.isOn,
})
}
COPY

Take another look at this function. Notice that we’re writing !state.isOn twice? Repetition is usually a sign that our code is getting messy. Cleaning up messy code (or refactoring) is part of learning to code!

Can you refactor our flipSwitch function so that we only write !state.isOn once?

Testing our event handler

Now that we’ve added our event handler, let’s make sure that it’s doing what we want.

  1. Back in our defaultProps, change the default onValueChange function to log a message in the console as shown below.
  2. Next, open the preview window.
  3. Open the console by selecting Show Console from the preview’s menu or by pressing Command + I.
Switch.defaultProps = {
isOn: false,
onValueChange: (isOn: boolean) => console.log(isOn)
}
COPY

The console is a great way to check whether your project is doing what you expect it to do. You can console.log anything, giving you just enough feedback to solve your bugs.

If all is going well, we’ll see a readout of the argument (!state.isOn) that we’re using when we call props.onValueChange from our flipSwitch function.

We’ll be using the console again in the next steps, so let’s clean it up by removing our call to console.log.

  1. Go back to our component’s defaultProps.
  2. Turn our onValueChange function back to (isOn: boolean) => null.

Finishing touches

Believe it or not, we’re done with our component! Or at least, we won’t be making any more changes to it in this tutorial. If you’d like to keep working on the switch, here are some challenges:

  • Can you create a prop that controls the color of the Switch’s container frame when the switch is on?
  • Can you create a property control for this prop?
  • Can you add a disabled state to the switch?
  • Can you change the size of the component’s frames so they respond to the component’s height and width on the canvas?

While our switch is done, this tutorial isn’t! Before we wrap things up, let’s actually use our switch to control something in our project.

Overrides

In Framer X, overrides are a way to connect parts of our designs.

On the most basic level, overrides allow us to merge new props into the existing props of our design’s components. They give us a way to change (or override) properties that we’ve set on the canvas, like a frame’s height and width, but they also let us set props that can’t be set from the canvas, such as onTap. Overrides only take effect in the preview window.

In order to really make overrides useful, we need to use them together with the data object. The data object is similar to our component’s state but designed to work with overrides: whenever we change its values, every override will run again, each merging new props to their connected components.

Don’t worry if you don’t get it yet! As usual, we’ll learn by doing in Framer X.

Creating an overrides file

Let’s create a new file where we’ll write our overrides.

  1. Return to the canvas.
  2. Click on our Switch instance to select it.
  3. On the properties panel, click the section labeled Override.
  4. In the Files dropdown, select New File.
  5. Now click the Edit Code button.

This will open up a new overrides file named App.tsx.

Like our component’s file (Switch.tsx), an overrides file is a .tsx file that exports different functions to the rest of our project.

Creating our data state

Let’s start by creating a Data object to hold our prototype’s state. In this example, we’ll be using our switch to control our design’s “theme,” which can be either day or night.

  1. At the top of our App.tsx file, add Data to the list of imports from the "framer" library.
  2. Delete the rest of the code in the file.
  3. Create a new Data object as shown below.
import { Override, Data } from "framer"
const data = Data({
theme: "day"
})
COPY

Overriding our device frame

Next, let’s create an override for our device frame. Our device frame is the very first frame we made in this tutorial: it sits on our canvas and contains our Switch instance.

  1. In our overrides file, delete the default override.
  2. In its place, create a new override named DeviceFrame as shown below.

In order for Framer X to recognize our overrides, we need to give the function the override type and export it from the file.

import { Override, Data } from "framer"
const data = Data({
theme: "day"
})
export function DeviceFrame(props): Override {
return {
backgroundColor: "#222222"
}
}
COPY

This is the most basic override: a function that gets called with the props of its connected frame, and that returns an object of new props. When we preview a component that is connected to an override, Framer X will call the override function, get back the new props, and then merge those props into the connected component’s original props. Framer X will then pass the result back to the component as props.

To see it working, let’s connect the override to our device frame.

  1. Back on the canvas, select the device frame.
  2. On the properties panel, select our new override from the override dropdown.
  3. Open the preview window to see the results.

Interesting! Our device frame’s background is white on the canvas but—thanks to our override—an elegant black in our preview window.

As nice as this is, it doesn’t match our theme! According to our data state, our theme is currently "day". We only want our device frame to be black when our theme is “night”.

Making our override conditional

Let’s go back and change our override so that the background prop it returns is either black or white, depending on the value of data.theme.

  1. Go back to the code tab.
  2. If it isn’t already selected, select our overrides file (App.tsx) in the list of our project’s code files.
  3. In our DeviceFrame override, remove the background property from the override’s set of returned props.
  4. In its place, add a set of variants for day and night as shown below.
  5. Add an initial and animate property, and point both properties to the value of data.theme.
import { Override, Data } from "framer"
const data = Data({
theme: "day",
})
export function DeviceFrame(props): Override {
return {
variants: {
day: {
backgroundColor: "#FFFFFF"
},
night: {
backgroundColor: "#222222"
}
},
initial: data.theme,
animate: data.theme,
}
}
COPY

This code should look familiar: it’s almost identical to the code we used to define our component’s variants in Part I of this tutorial.

Open the preview window again and check out the results. If your code looks like mine, then the device frame should be back to having a white background.

To see if it’s all working, try changing the value of data.theme from "day" to "night". Did your device also change its background color from white to black?

Overriding our Switch

Now that our device frame’s background color is connected to our data state, let’s move on to our Switch instance. We want our switch to be on when theme.mode is "night" and off when theme.mode is “day”.

You’ve already learned enough to write this one yourself. I’ll still include the steps below, but give it a shot on your own first. Good luck!

  1. In our overrides file, create a new override and name it ThemeSwitch. If that name doesn’t appear, feel free to use another—the names of our overrides don’t matter, so long as they’re unique.
  2. In the props that our override returns, set the isOn prop with an expression that will evaluate to true if data.theme is "night" , or else to false.
  3. Back on the canvas, connect our Switch instance to the new override.
export function ThemeSwitch(props): Override {
return {
isOn: data.theme === "night",
}
}
COPY

Now open the preview window again and let’s see what’s changed.

Changing our data state

We’re almost done! All that’s left is to make flipping our Switch between on and off also flip our theme between day and night. To get this done, we’ll use our component’s onValueChange event handler.

  1. In our ThemeSwitch override, add the onValueChange property to the props that this override returns.
  2. Set this property to a callback function that accepts isOn as an argument.
  3. Inside this function, use the value of the isOn argument to set data.theme to either "day" or “night”.
export function ThemeSwitch(props): Override {
return {
isOn: data.theme === "night",
onValueChange: isOn => {
data.theme = isOn ? "night" : "day"
},
}
}
COPY

And when we preview…

Our little Switch is finally doing some work!

Our finished component

Let’s take a final look at our component.

import * as React from "react"
import { Frame, addPropertyControls, ControlType } from "framer"
export function Switch(props) {
const [state, setState] = React.useState({
isOn: props.isOn,
})
React.useEffect(() => {
if (state.isOn !== props.isOn) {
setState({
isOn: props.isOn,
})
}
}, [props.isOn])
const flipSwitch = () => {
props.onValueChange(!state.isOn)
setState({
isOn: !state.isOn,
})
}
return (
<Frame
height={50}
width={80}
center
borderRadius={25}
onTap={flipSwitch}
variants={{
off: { background: "#BBBBBB" },
on: { background: "#0070DF" },
}}
initial={state.isOn ? "on" : "off"}
animate={state.isOn ? "on" : "off"}
transition={{
type: "tween",
duration: 0.2,
}}
>
<Frame
size={46}
top={2}
left={2}
borderRadius={"100%"}
background="#FFFFFF"
variants={{
off: { x: 0 },
on: { x: 30 },
}}
transition={{
type: "tween",
duration: 0.2,
}}
/>
</Frame>
)
}
Switch.defaultProps = {
isOn: false,
onValueChange: () => null,
}
addPropertyControls(Switch, {
isOn: {
type: ControlType.Boolean,
title: "On",
defaultValue: false,
},
})
COPY

Just like before, we’ll walk through each part that we’ve added. Anything not covered below is code that we’ve already covered in Part I or Part II.

Line 18: In our flipSwitch function, we’ve added a line that calls props.onValueChange. We’re calling this function with the value of state.isOn.

Line 63: We’ve also added onValueChange to our component’s defaultProps. The default value is a function that does nothing.

Our finished overrides

Let’s also walk through our overrides file.

import { Override, Data } from "framer"
const data = Data({
theme: "day",
})
export function DeviceFrame(props): Override {
return {
variants: {
day: {
backgroundColor: "#FFFFFF",
},
night: {
backgroundColor: "#222222",
},
},
initial: data.theme,
animate: data.theme,
}
}
export function ThemeSwitch(props): Override {
return {
isOn: data.theme === "night",
onValueChange: isOn => {
data.theme = isOn ? "night" : "day"
},
}
}
COPY

Line 1: On the first line of our overrides file, we’ve imported the override type alias and the Data function from the "framer" library.

Lines 3 - 5: Here we’re creating a data object and assigning it to the variable data. Our data object is keeping track of a single property theme that has the initial value "day".

Lines 7 - 20: This is our override for our device frame. It returns a set of variants, day and night, and uses data.theme to determine both which variant to use when the connected component first mounts (its initial variant) and which variant the component should animate to when the component re-renders.

Lines 22 - 29: Finally, we’ve created an override for our Switch. It overrides the connected component’s isOn with a value that is conditional on data.theme, returning true when data.theme is “night” and false when data.theme is “day”. It also overrides the onValueChange prop with a callback function that sets the value of data.theme to “night” if the switch was turned on or “day” if it was turned off.

To review

As a final note, let’s review how our prototype works:

  • Flipping our switch will change the value of data.theme.
  • Because data is a special data object, this change will cause each of our overrides to run again.
  • When they run, any expressions that point to data.theme will be solved with that property’s current value, returning different props depending on whether that value is “day” or “night”.
Download the finished project ›

Congratulations, you’ve made it to the end! Whether you’re new to code or just new to Framer X, I hope you’ve enjoyed taking some first steps toward designing interactive components and prototypes.

If you have questions, reach out to me on Twitter. We can also connect in Framer’s Slack and Spectrum communities.

Did you miss Part I and Part II? Check out the other tutorials in this series: