Continuing our way down the home page, we're going to implement the featured content row that exists under our Hero component as two components. We're going to start by creating a 50/50 row component and then we'll add in our Featured Post component and insert it into the placeholders we define on our 50/50 row component.

50/50 Placeholder Row

In order to build out this section, we should really build a Row component with placeholders for each side that will allow us to possibly extend this in the future and add different types of components to either side.

To do this, we start off by scaffolding our component as usual:

jss scaffold Row5050

Then we can add the structure from the bootstrap example and build out the React component:

src/components/Row5050/index.js

import React from 'react';
import { Placeholder } from '@sitecore-jss/sitecore-jss-react';

const Row5050 = ({rendering}) => (
  <div className="row mb-2">
    <div className="col-md-6">
      <Placeholder name="jss-row-5050-left" rendering={rendering} />
    </div>
    <div className="col-md-6">
      <Placeholder name="jss-row-5050-right" rendering={rendering} />
    </div>
  </div>
);

export default Row5050;

The component definition contains no fields but we need to declare our exposed placeholders on it:

sitecore/definitions/components/Row5050.sitecore.js

// eslint-disable-next-line no-unused-vars
import { CommonFieldTypes, SitecoreIcon, Manifest } from '@sitecore-jss/sitecore-jss-manifest';

/**
 * Adds the Row5050 component to the disconnected manifest.
 * This function is invoked by convention (*.sitecore.js) when 'jss manifest' is run.
 * @param {Manifest} manifest Manifest instance to add components to
 */
export default function(manifest) {
  manifest.addComponent({
    name: 'Row5050',
    placeholders: ['jss-row-5050-left', 'jss-row-5050-right']
  });
}

And then add an instance of this component to our home route data:

data/routes/en.yml

id: home-page
placeholders:
  jss-main:
   - id: header-content
   - componentName: Hero
     fields:
       title: Hero Title
       description: This is some description text
       link:
        href: https://www.sitecore.com/
        text: Visit Sitecore
   - componentName: Row5050

We'll come back and fill in more data here after we have our component built.

Start by scaffolding this component like we would with any component

jss scaffold FeaturedPost

After that is completed, let's define our component's data model:

sitecore/definitions/components/FeaturedPost.sitecore.js

// eslint-disable-next-line no-unused-vars
import { CommonFieldTypes } from '@sitecore-jss/sitecore-jss-manifest';

export default function(manifest) {
  manifest.addComponent({
    name: 'FeaturedPost',
    fields: [
      { name: 'eyebrow', type: CommonFieldTypes.SingleLineText },
      { name: 'title', type: CommonFieldTypes.GeneralLink },
      { name: 'date', type: CommonFieldTypes.Date },
      { name: 'description', type: CommonFieldTypes.SingleLineText },
      { name: 'readMore', type: CommonFieldTypes.GeneralLink },
      { name: 'image', type: CommonFieldTypes.Image },
    ]
  });
}

We'll likely revisit this component in the future. Ideally we'd be pulling some of these fields from the item we're linking to, but for now let's just create a field for everything to make it flexible.

After we have that in place, let's define our React component:

src/components/FeaturedPost/index.js

import React from 'react';
import { Text, Link, DateField, Image } from '@sitecore-jss/sitecore-jss-react';

const FeaturedPost = ({ fields }) => (
  <div className="card flex-md-row mb-4 shadow-sm h-md-250">
    <div className="card-body d-flex flex-column align-items-start">
      <strong className="d-inline-block mb-2 text-primary"><Text field={fields.eyebrow} /></strong>
      <h3 className="mb-0">
        <Link field={fields.title} className="text-dark" />
      </h3>
      <div className="mb-1 text-muted">
        <DateField field={fields.date} 
                   render={(date) => date.toLocaleDateString("en-US", { year: 'numeric', month: 'long', day: 'numeric' })} />
      </div>
      <p className="card-text mb-auto">
        <Text field={fields.description} />
      </p>
      <Link field={fields.readMore} />
    </div>
    <Image className="bd-placeholder-img card-img-right flex-auto d-none d-lg-block" media={fields.image} />
  </div>
);

export default FeaturedPost;

We're using some field types for the first time here. In particular, DateField. Here we're defining our own rendering function and formatting the date for display. Image is also being used here as well but we're using it largely the same way we've used other fields so far.

After this is implemented we can go and define our route data and fill out the placeholders for our row. That file is getting a little long so you can view the full thing here. The interesting snippet is this though:

sitecore/definitions/components/FeaturedPost.sitecore.js

   - componentName: Row5050
     placeholders:
        jss-row-5050-left:
          - componentName: FeaturedPost
            fields:
              eyebrow: World
              title:
                href: https://www.sitecore.com/
                text: Featured Post Title
              date: "2012-05-04T00:00:00Z"
              description: This is some description text.
              readMore:
                href: https://www.sitecore.com/
                text: Read More
              image:
                src: https://placeimg.com/200/250/people
                alt: Placeholder Image

There's a definition for the right side of the 5050 component as well.

Browse the code for this post and previous under the GitHub tag featured-content.

Conclusion

In this post we covered how to create a structure component with some nested Placeholders and we implemented a component that can be inserted into those placeholders.