Apps can attach metadata to app elements and then access that data when rendering the element. This data is known as app element data.
The primary benefit of app element data is that users can edit elements after creating them.
For example, imagine an app that outputs gradients. If an app outputs the gradients as native image elements, Canva can't "remember" the color stops used to create them. Once the gradients are added to the design, the metadata is lost. As a result, users can only create new gradients — they can't modify existing ones.
If, on the other hand, an app outputs the gradients as image elements inside an app element, the app can save the color stops on the element itself. Then, if a user selects the element after creating it, the app can load this information and allow the user to modify it. As a result, the element becomes re-editable.
This guide explains how to use app element data.
Step 1: Define the data structure
Use TypeScript to create a type that defines the structure of the app element data:
type AppElementData = {color1: string;color2: string;};
Then pass this type into the getDesignInteraction
function as a type parameter:
const designInteraction = getDesignInteraction<AppElementData>();
This ensures that the Design Interaction APIs are type-safe.
Step 2: Call the setAppElementData
method
At some point in the lifecycle of the app, call the setAppElementData
method:
designInteraction.setAppElementData({color1: "#ffffff",color2: "#ff0099",});
This method accepts an object of key-value pairs. That object is then attached to the app element. It should conform to the structure defined in the previous step.
Step 3: Use the app element data
When an app calls the setAppElementData
method, Canva runs the app element renderer callback. This callback receives the app element data as its only argument:
designInteraction.registerRenderAppElement((data) => {console.log(data.color1); // => "#ffffff"console.log(data.color2); // => "#ff0099"});
The callback is expected use the data to render the element. For example, in the following snippet, the colors are used to create a gradient as an image element:
designInteraction.registerRenderAppElement((data) => {const dataUrl = createGradient(data.color1, data.color2);return [{type: "IMAGE",dataUrl,width: 640,height: 360,},];});function createGradient(color1: string, color2: string): string {const canvas = document.createElement("canvas");canvas.width = 640;canvas.height = 360;const ctx = canvas.getContext("2d");if (!ctx) {throw new Error("Can't get CanvasRenderingContext2D");}const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);gradient.addColorStop(0, color1);gradient.addColorStop(1, color2);ctx.fillStyle = gradient;ctx.fillRect(0, 0, canvas.width, canvas.height);return canvas.toDataURL();}
Step 3: Handle app element changes
Apps can register a callback with the onAppElementChange
method:
designInteraction.onAppElementChange((element) => {console.log(element.data); // => { color1: "#ffffff", color2: "#ff0099" }});
This callback runs when:
- An app calls the
setAppElementData
method - A user selects an app element
- A user deselects an app element
A use-case for this callback is to update the state of the app's user interface.
For example, if a user opens an app element in an app, the app can use this callback to update the app's form fields with the values from the app element data. If an app doesn't do this, the app element data and the state of the user interface will fall out of sync.
The onAppElementChange
callback receives an element
parameter. If an app element is selected, this parameter has a data
property that contains the app element data:
designInteraction.onAppElementChange((element) => {if (element) {console.log("The app element is selected");console.log(element.data); // => { ... }}});
Otherwise, the parameter is undefined
:
designInteraction.onAppElementChange((element) => {if (element) {console.log("The app element is selected");console.log(element.data); // => { ... }} else {console.log("The app element is not selected");console.log(element); // => undefined}});
Example
import React from "react";import { getDesignInteraction } from "@canva/design-interaction";type AppElementData = {color1: string;color2: string;};const initialState: AppElementData = {color1: "#ffffff",color2: "#ff0099",};export const App = () => {const designInteraction = getDesignInteraction<AppElementData>();const [state, setState] = React.useState<AppElementData>(initialState);React.useEffect(() => {designInteraction.registerRenderAppElement((data) => {const dataUrl = createGradient(data.color1, data.color2);return [{type: "IMAGE",dataUrl,width: 640,height: 360,},];});designInteraction.onAppElementChange((element) => {if (element) {setState({color1: element.data.color1,color2: element.data.color2,});} else {setState(initialState);}});}, [designInteraction]);const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {setState((prevState) => {return {...prevState,[event.target.name]: event.target.value,};});};const handleClick = () => {designInteraction.setAppElementData({color1: state.color1,color2: state.color2,});};return (<div><div><inputtype="text"name="color1"placeholder="Color #1"value={state.color1}onChange={handleChange}/></div><div><inputtype="text"name="color2"placeholder="Color #2"value={state.color2}onChange={handleChange}/></div><div><button onClick={handleClick}>Add element to design</button></div></div>);};function createGradient(color1: string, color2: string): string {const canvas = document.createElement("canvas");canvas.width = 640;canvas.height = 360;const ctx = canvas.getContext("2d");if (!ctx) {throw new Error("Can't get CanvasRenderingContext2D");}const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);gradient.addColorStop(0, color1);gradient.addColorStop(1, color2);ctx.fillStyle = gradient;ctx.fillRect(0, 0, canvas.width, canvas.height);return canvas.toDataURL();}