Rethinking #Sitecore Content Hub: Dissecting an External React Component

In my previous blog I talked about how to get started with external components. One of my goals when utilizing them was to be able to take data from an asset and use that in a component. The possibilities you can do are endless. I am going to dive into an example of a working component that will receive a value from CH and display it.

Let’s start with the following. In this example we will see files with the .tsx extension.They are typescript files that contain JSX syntax.

Starting with the clientExample.tsx you will see the following code. See comments in the code for a breakdown of what the syntax does.

//Helper Libraries
import { entityLoadConfigurationFromOptions } from "@/lib/data/dataHelpers";
import type { IModuleProps } from "@/lib/types";
import { useEffect, useMemo, useState } from "react";

//Sets the property and value that will be received. In this case you can send any property value as long as it is a string.
export type ClientExampleConfig = {
  propertyName: string;
};

export default ({ createClient, config, options }: IModuleProps<ClientExampleConfig>) => {
  const [propertyValue, setPropertyValue] = useState<string>();

//The createClient is provided by the Content Hub to communicate with the component.
  const client = useMemo(createClient, [createClient]);

//This code will only load the configured property. Important not to load everything for better performance.
  const loadConfiguration = useMemo(() => entityLoadConfigurationFromOptions({ properties: [config.propertyName] }), [config.propertyName]);

//Fetches the entity. When the entity changes the model loads. Gets the property value and stores it in state.
  useEffect(() => {
    (async () => {
      if (!client || !loadConfiguration || !options.entityId) setPropertyValue(undefined);
      else {
        const entity = await client.entities.getAsync(options.entityId, loadConfiguration);
        setPropertyValue(entity?.getPropertyValue(config.propertyName) as string);
      }
    })();
  }, [client, options.entityId]);

//This displays the property value. This part should look somewhat familiar as it uses HTML.
  return (
    <dl>
      <dt>{config.propertyName}</dt>
      <dd>{propertyValue ? propertyValue : <em>Not set</em>}</dd>
    </dl>
  );
};

Tying this together with the index.tsx you will see the following code. See comments in the code for a breakdown of what the syntax does.

//Uses a helper library to create
import createModule from "@/lib/createModule"; 
//Imports the React component and adds the configuration type it expects.
import ClientExample, { ClientExampleConfig } from "./clientExample"; 

//These lines bring it all together to create the module.
export default createModule<ClientExampleConfig>(
props => <ClientExample {...props} />
 );

As I would suggest to everyone Storybook is a powerful tool that I highly recommend to test components. Let’s take a look at clientExample.stories.tsx.

//Defines component story and variations.
import type { Meta, StoryObj } from "@storybook/react";
//Defines the configuration.
import ClientExample, { ClientExampleConfig } from "./clientExample";
//Helpers that simulate Content Hub behavior.
import { CreateMockEntity, CreateMockModuleProps, CreateMockQueryResponse, MockApiHandler, MockBaseUrl, MockEntityId, MockRoutes } from "@/lib/mockData";

//MSW = Mock Service Worker
//Intercepts HTTP calls in the browser and replace it with a mocked responses.
import { http, HttpResponse } from "msw";

//These can be set to whatever values you want to test.
const mockedPropertyName = "title";
const mockedPropertyValue = "Hello World!";

//Registers the story and supplies the default props.
const meta = {
  title: "Modules/Client Example",
  component: ClientExample,
  args: {
    ...CreateMockModuleProps<ClientExampleConfig>({ propertyName: mockedPropertyName }),
    options: {
      entityId: MockEntityId,
    },
  },
  tags: ["autodocs"],
} satisfies Meta<typeof ClientExample>;

export default meta;
type Story = StoryObj<typeof meta>;

//Defines the story
export const Default: Story = {
  parameters: {
    msw: {
      handlers: [
        MockApiHandler,
        http.post(MockRoutes.entities_by_query.href, () => {
          const entity = CreateMockEntity({ [mockedPropertyName]: mockedPropertyValue });
          return HttpResponse.json(CreateMockQueryResponse([entity]));
        }),
      ],
    },
  },
  args: {},
};

As stated in my previous blog the title and any asset property can be passed into a React component as you see below.

For this however we will run a quick test in Storybook from the command line in the project (npm run storybook;). You can see the results below:

Wrapping Up

I always like to give people the basics when it comes to getting started. I hope this answers some of your questions on the structure of a React component and how it is used with Sitecore’s Content Hub. Let me know if you have any questions. You can also refer back to my previous blog. Now boldly go and create some new external React components!

Sitecore Content Hub React External Components

Streamlining #Sitecore Content Hub Searches with AI Visual Searching

A while back I got to preview the concept of the visual search feature in content hub. Since then I have been anticipating its release. It is officially here and I wanted to put it to the test.

To access this feature, navigate to the Assets.

Click on the AI button and you should see the following. You will see a few things show up. You can use the first icon to search by image. The second icon lets you search by color. You can also type in keywords.

Click on the first icon to search by an image. You will see the following file upload screen. You then can select a local image to search by. In this case I will select a runner in a city.

After uploading the image, a search for images will show related images.

Next using the search by color option you will see the following. Choose a color.

Once the color is chosen you will see images related to that color.

Once you have results you can even break things down further by adding search text.

What if this is a common search that a content author does? Well you can save this search and use it again if needed. Simply click on the Save button.

The next screen will appear. You can then name your search and share with other content authors.

When you go back to assets you will then have the search under the saved searches.

What if you just wanted to search by keywords? Well you can do that to. Simply type in your search keys and click on the arrow. The keyword(s) will be used to visually search images.

If you need to include existing assets for visual search you can do so in the AI settings.

So using this is something I can see streamlining the searching process and making it much faster in retrieving assets. In my opinion this is one of the best search features I have seen. It stands out not only in the content hub, but everywhere else.

Sitecore Symposium Day 4 – Sitecore Stream, Breakout Sessions and Katie Ledecky. #SitecoreSYM

Let me first say how excited I am about all the new stuff coming soon. Today was a great day.

Sitecore stream is really going to revolutionize the way things are done. Stretching AI across all Sitecore products is going to be amazing. Find out more here.

Breakout Sessions were Fantastic as always. Here are some of the highlights of the ones I attended.

Continuous improvement with Content Hub

Great session were I learned how a real world Content Hub implementation was done. Doing things right in the DAM from the start is essential. This approach will help you down the road. Especially with other parts of the Content Hub.

Content Hub Roadmap

I missed pat of this, but was able to capture a slide.

Developer Experience in XM Cloud

This one was exciting to hear. Lots of great features coming to XM Cloud development. Liz Nelson did a great job explaining it. The new offerings will help developers tremendously.

Last, but not least a fireside chat with Katy Ledecky. I found out that part of the medal is made with the Eiffel Tower.

Almost forgot I ran too.