Add search and filter to your app

Add search and filter functionality to an app built with the Miro Web SDK.

The Miro Developer Platform allows developers to create apps and integrations on top of Miro that add extra or custom functionality.

Whether it's something creative, useful, or something that allows you and your team to collaborate more efficiently together, the Miro Developer Platform gives you all the pieces you need to build your next big idea.

Goal

In this guide, we’ll be exploring how to implement asset search in your Web SDK-based app.
The sample app we're going to create together builds on the finished Add drag and drop to your app tutorial.

Download the complete code sample for this guide from our app-examples repository.

Here's what we’ll build:

finished search app

Add a search input

Path: src/components/Input.tsx

To search through our assets, we need an input that can do a few things:

  • Accept user input.
  • Pass the input value to our image list, so we can filter it.
  • Clear the input value upon clicking.
    (We’ll add a ✖ close icon to perform this action.)

All the styles for our component are already included in styles.css.

Track internal state

const [internalInputValue, setInternalInputValue] = useState("");

This line of the Input.tsx file does the following:

  • It creates a variable, called internalInputValue, to hold the state of the input.
  • It creates a function, called setInternalInputValue, to update the value of this state.

We’ll call the second function every time the input changes, and we'll update the first variable with the current input value.

We need to keep track of this value, so we can clear the value if users click the ✖ close icon.
We’ll explore this step in the next section.

Clear internal state

Now that we are keeping track of the current value of the input, we’re also able to clear it if a user initiates the action.

We created a separate component called X for the ✖ close icon.

const X = ({ onClick }: { onClick: () => void }) => {
  return (
    <div id={"input-x-container"} onClick={onClick}>
      <svg
        width="12"
        height="12"
        viewBox="0 0 12 12"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          fill-rule="evenodd"
          clip-rule="evenodd"
          d="M11.1382 1.8047C11.3985 1.54435 11.3985 1.12224 11.1382 0.861888C10.8778 0.601539 10.4557 0.601539 10.1953 0.861888L6.00008 5.05715L1.80482 0.861888C1.54447 0.601539 1.12236 0.601539 0.862011 0.861888C0.601661 1.12224 0.601661 1.54435 0.862011 1.8047L5.05727 5.99996L0.862011 10.1952C0.601661 10.4556 0.601661 10.8777 0.86201 11.138C1.12236 11.3984 1.54447 11.3984 1.80482 11.138L6.00008 6.94277L10.1953 11.138C10.4557 11.3984 10.8778 11.3984 11.1382 11.138C11.3985 10.8777 11.3985 10.4556 11.1382 10.1952L6.94289 5.99996L11.1382 1.8047Z"
          fill="#5F5C80"
        />
      </svg>
    </div>
  );
};

We’re giving X a property called onClick. This property fires when users click the ✖ close icon.

After adding the X component to the Input component above, we can pass the onClick property a function that will clear the current state of the input. The function name is handleClearInput.
In this function, we call the function to set the internal input value, and we clear the state by setting its value to “”.

We’re only showing the X component if the value is not empty, so we will only see it after the user starts to type.

{internalInputValue !== "" && <X onClick={handleClearInput} />}

The handleClearInput function contains a call to handleInputChange. This is a render prop; we’ll discuss it in the next section.

Pass the input value to a parent component through render props

Because we’re keeping track of the input value only internally, we also need a way to access this value from a parent component; in our case, the main app.

In React, this pattern is known as using render props.
This pattern is the same as creating a prop for our component; but in this case, our prop is a function.

At the beginning of the input.tsx file, we define the handleInputChange render prop, so we can use it further down in our component.

const Input = ({
  handleInputChange,
}: {
  handleInputChange: (e: string) => void;
}) => {
...

We call this function every time the input changes, and when the input value is cleared. This passes the current value of our input to the parent when we call this render prop.

Import input into app and track value in app state

Path: src/App.tsx

Now that we have a working Input, we can use it inside our main app.

We import our new component in App.tsx, and then we render it at the top of our app container.

...
  return (
    <div className="main">
      <Input handleInputChange={(value) => setInputValue(value)} />
...

We're also calling our handleInputChange prop, and setting the state of our app with the value returned from the Input.

We're keeping track of this value on in a variable called inputValue.

const [inputValue, setInputValue] = useState("");

We now have a component that collects user input, and a variable that contains its current value!

Add names and tags to images

Our images currently don’t have any information attached to them, other than the URL we’re using to insert the image.

To filter our images by the keyword we type into the input, we’ll need to add some extra properties to the image data by converting the array of image strings into an array of image objects.

For this example, we’ve turned the strings into objects, and we've updated the object's keys to include url, name, and tags.

  • The url key remains the same as the original URL string that the image had.
  • The name key can be something descriptive about the image being rendered.
  • The tags key can be an array containing multiple strings; each string should be descriptive and/or related to the image.
  const images = [
    {
      url: "https://static-website.miro.com/static/images/page/mr-features-1/tour-m-projects.svg",
      name: "projects",
      tags: ["folders", "collaboration"],
    },
    {
      url: "https://static-website.miro.com/static/images/page/mr-features-1/tour-m-account.svg",
      name: "account",
      tags: ["user", "profile"],
    },
    {
      url: "https://static-website.miro.com/static/images/page/mr-features-1/tour-m-product.svg",
      name: "product",
      tags: ["tool", "collaboration"],
    },
    {
      url: "https://static-website.miro.com/static/images/page/mr-features-1/tour-m-ux-research.svg",
      name: "ux research",
      tags: ["design", "information"],
    },
    {
      url: "https://static-website.miro.com/static/images/page/mr-features-1/tour-m-learn.svg",
      name: "learn",
      tags: ["education", "tutorials"],
    },
  ];

After updating the image array, we also need to make sure we update the mapped image in our return body.
To do so, we pass the url to the img tag src, so our app can render correctly.

Add a filter

Now that we have an input to collect our search term and an array of images with metadata, all we need to do is add a filter that returns the image we’re looking for.

Luckily, JavaScript includes a helpful method we can use to filter items from an array.

In App.tsx, we’re setting up our filter like this:

        .filter((o) => {
          return (
            o["name"].toLowerCase().includes(inputValue.toLowerCase()) ||
            o["tags"].some((value) => {
              return value.includes(inputValue.toLowerCase());
            })
          );
        })

The filter contains 2 parts: one that filters the list of images by name, and the second that filters it by tags.

We can continue this pattern if we have more fields that we want to filter our images with; for example, we could add a new property to the image data called size, or author.

Let's wrap up

Implementing a search functionality to your app can be as complex as you need it to be, but this should serve as a simple example on implementing it in the context of images that might have multiple properties you would like to filter by.


What's next

To discover and to get acquainted with more Web SDK features, check the other tutorials.