Skip to content

Instantly share code, notes, and snippets.

@stubbornella
Last active August 21, 2017 20:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stubbornella/dbcaa085c7db3b1ad1bc to your computer and use it in GitHub Desktop.
Save stubbornella/dbcaa085c7db3b1ad1bc to your computer and use it in GitHub Desktop.

How do you make a Media component in React?

var Media = React.createClass({

});

We then need to export our component so it can be used outside this file.

module.exports = {
  Media: Media
};

Every component needs a render function This tells the browser what HTML to render.

// just rendering a paragraph for now // this.props.children tells react where to put any child nodes.

var Media = React.createClass({
  render: function () {
    return (
      <p>
        {this.props.children}
      </p>
    );
  }
});

Let's try it. Sweet, this is gonna be rad!

<Media>
    My very first React component!
</Media>

When React renders that component we get this html

<p>My very first React component!</p>

Not very useful, right? Well, you'd never use react to render a simple paragraph because it already understands <p> tags, you can just use them!

What if we want to start building an actual media block? Let's add a left image.

What did we try first?

Tons of attributes

<Media leftImageSource='http://placehold.it/50x50'>
    Media block content
</Media>

How does it work? Well, remember the render function in our component? It snags the leftImageSource attribute and sets it as our image source. (for now we'll focus on api stuff)

So, we thought we were pretty clever, then we realized sometimes the image was a link, so we added an href.

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'>
    Media block content
</Media>
<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'>
    Media block content
</Media>

Oh, and we need to handle the images vertical alignment

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'
  leftImageAlignment='middle'>
    Media block content
</Media>

Oh shoot, accessibility, we need an alt atribute.

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'
  leftImageAlignment='middle'
  alt="profile photo">
    Media block content
</Media>

Oh, and performance! We need a height and width!

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'
  leftImageAlignment='middle'
  leftImageAlt="profile photo"
  leftImageHeight="50px"
  leftImageWidth="50px">
    Media block content
</Media>

The space between the image and the content can vary, so we'll need leftImageSpacing.

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'
  leftImageAlignment='middle'
  leftImageAlt="profile photo"
  leftImageHeight="50px"
  leftImageWidth="50px"
  leftImageSpacing="medium">
    Media block content
</Media>

Oh, and we'd better add all the properties for the media block itself Including stacksize (breakpoint) and bodyAlignment (vertical alignment of body content)

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'
  leftImageAlignment='middle'
  leftImageAlt="profile photo"
  leftImageHeight="50px"
  leftImageWidth="50px"
  leftImageSpacing="medium"
  bodyAlignment='middle'
  stackSize='medium'>
    Media block content
</Media>

This is getting out of hand! But we forgot something, the media block can also have a right image, so we'll need to duplicate all that.

<Media
  leftImageSource='http://placehold.it/50x50'
  leftImageHref='http://www.google.com'
  leftImageAlignment='middle'
  leftImageAlt="profile photo"
  leftImageHeight="50px"
  leftImageWidth="50px"
  rightImageSource='http://placehold.it/50x50'
  rightImageHref='http://www.google.com'
  rightImageAlignment='middle'
  rightImageAlt="profile photo"
  rightImageHeight="50px"
  rightImageWidth="50px"
  rightImageSpacing="medium"
  bodyAlignment='middle'
  stackSize='medium'>
    Media block content
</Media>

This is out of control, we are going the wrong way!

What is working here?

  • It's very explicit, we know what each thing does

What isn't?

  • We're basically recreating html in React, yuck! (we shouldn't make a new different alt attribute!)
  • We have image properties and media properties all mixed up

So what did we think of next?

JSON

var images = [
  {
  "src": "http://placehold.it/50x50",
  "href": "http://www.google.com",
  "alignment": "middle",
  "alt": "profile photo",
  "height": "50px",
  "width": "50px"
  },
  {
  "src": "http://placehold.it/50x50",
  "href": "http://www.google.com",
  "alignment": "middle",
  "alt": "profile photo",
  "height": "50px",
  "width": "50px"
  }
];

Which we can put into our Media component like so:

<Media
  images={images}
  bodyAlignment='middle'
  stackSize='medium'>
    Media block content
</Media>

We never actually built this, because we weren't satisfied with it, but let's talk about what works and doesn't

What works?

  • cleaner separation of concerns (media takes care of media stuff, rather than the properties of it's children)

What doesn't work?

  • It isn't that different than what we had before
  • The abstraction of passing in JSON means all the code isn't in the same place
  • It's a little weird to have JSON in the middle of what looks like markup (IMO)
  • We're really still reinventing html atributes that react would give us for free on an <img> element.

So, what did we try next?

Parsing Children

We decided to try including the images as children.

<Media>
  <img src='http://placehold.it/50x50' href='http://www.google.com' alignment='middle' alt="profile photo" height="50px" width="50px" >
  <p>My media content</p>
  <img src='http://placehold.it/50x50' href='http://www.google.com' alignment='middle' alt="profile photo" height="50px" width="50px" >
</Media>

This looked better, everything is normal html! But, it has a few drawbacks.

What works?

  • Normal HTML
  • Facebook does it this way

What doesn't work?

  • The images and body content need to be in a very particular order, it seems weird to expose that to the user
  • Violates the "build components you can use without understanding CSS"
  • We could loop over children and reorder them, but how do we tell the difference between content images and media images?
  • We were still discovering React, and didn't know how to loop over children yet
  • React provides handy error messages and property validations. We would lose out on that if we made the images children
  • Facebook's images aren't optional, so it's a different case

So what did we try next?

React built in <img> Component

In react, people joke that everything is a component.

It's not a joke, everything is.

First, we make our image.

var leftImage = <img src='http://placehold.it/50x50' href='http://www.google.com' alignment='middle' alt="profile photo" height="50px" width="50px">;
var rightImage = <img src='http://placehold.it/50x50' href='http://www.google.com' alignment='middle' alt="profile photo" height="50px" width="50px">

Next, we make our media object.

<Media
  leftImage={leftImage}
  rightImage={rightImage}
  bodyAlignment='middle'
  stackSize='medium'>
    Media block content
</Media>

You'll notice this looks similar to the JSON example.

You can even write it like this if your really want to.

<Media
  leftImage={<img src='http://placehold.it/50x50' href='http://www.google.com' alignment='middle' alt="profile photo" height="50px" width="50px">}
  bodyAlignment='middle'
  stackSize='medium'>
    Media block content
</Media>

What works?

  • React passess default html attributes in to the resulting img tag, so we don't have to do anything special with height, width, src, and alt.
  • Aria values would also be passed through
  • We separate concerns and the image takes care of it's own properties
  • No need to parse the content

What doesn't work?

  • HTML inside an attribute (in the latter example) is a bit odd, though it does have advantages.
  • Remember how I said default HTML attributes are passed through? The href will also be passed through. So our image will have an href attribute. I like clean html, and that feels weird to me!
<div class="media">
  <a href="styleguide.pivotal.io">
    <img href="styleguide.pivotal.io" />
      ...

We considered going back to properties.

<Media leftImageHref="styleguide.pivotal.io">

But ultimately decided we should make our own <img> wrapper so we'd have more control. We thought this would probably crop up again.

So, what did we try next?

Custom Image component

Our component was born.

It outputs a simple <img> tag, but won't pass through attributes that don't make sense like href.

We'll need to create an image component, just like we did the Media component.

var Image = React.createClass({
});

And export it the same way we did the Media component

module.exports = {Image};

And let's make it get it's properties and render an image

var Image = React.createClass({
  render() {
    var {href, src, children, className, ...other} = this.props;

    var image = <img {...other} src={src} className={classes}>{children}</img>;
    return href ? <a {...{href}}>{image}</a> : image;
  }
});

Now, our images can be responsive, meaning they fit their container, whatever size it flexes to be, so let's handle that property.

var Image = React.createClass({
  render() {
    var {responsive, href, src, children, className, ...other} = this.props;

    var image = <img {...other} src={src} className={classes}>{children}</img>;
    return href ? <a {...{href}}>{image}</a> : image;
  }
});

And we'll need to apply the img-responsive class if we find that property has been set to true.

var Image = React.createClass({
  render() {
    var {responsive, href, src, children, className, ...other} = this.props;
    var classes = classnames({'img-responsive': responsive}, className);

    var image = <img {...other} src={src} className={classes}>{children}</img>;
    return href ? <a {...{href}}>{image}</a> : image;
  }
});

Finally, one of the advantages we talked about for using properties over children is the validation and error handling we can do as a result.

var Image = React.createClass({
  propTypes: {
    responsive: types.bool,
    href: types.string,
    src: types.string.isRequired
  },

  render() {
    var {responsive, href, src, children, className, ...other} = this.props;
    var classes = classnames({'img-responsive': responsive}, className);

    var image = <img {...other} src={src} className={classes}>{children}</img>;
    return href ? <a {...{href}}>{image}</a> : image;
  }
});

Here we're saying

  • responsive has to be true or false
  • href is a string
  • src is a string and is required

@@@ show example of an error message if src is missing @@@

This is really powerful, because now, developers who use this component won't have to wonder why it isn't working, we can give them nice error messages on the console in their browser of choice.

At this point, it was really starting to work well for us, but this is when we had the "ah-ha" moment.

Users are still needing to specify too many things to get this component to work, they might as well just write html!

So, what did we do next?

Elements

We could simplify our interface further!

Our designers only ever used two kinds of alignment:

  1. Traditional media with everything top aligned @@@ photos of these components @@@
  2. "Flag" component a la Harry Roberts

So we thought, let's make those two use cases dead simple.

  1. We changed our default media componetn to default to top alignment if nothing else was specified.
  2. We created a Flag component
<Flag leftImage={leftImage} rightImage={rightImage}>
  Flag content (often just a link)
</Flag>

What works?

  • With Flag and Media, we no longer need to specify alignment unless we want something weird.

What doesn't work?

  • Engineers don't always know what the flag object is. Documentation and designer's teaching helps.

Conclusion

Are any of these wrong?

No, absolutely not.

Facebook uses the child nodes method. We use the final version. Both work well.

For our charts and panels, we loop over children.

Our buttons use more elements and have almost no props.

For tables, we use JSON to give them maximum configurability.

Each component is different.

It's a design decision.

Other devs, who will build more complex views out of your simple components are your users.

You have to user test. (What seems logical to you when you are neck deep in it all will not be logical to anyone else.)

Keep looping back. Change the interface until it works well.

Use all the tools in your toolbox:

  1. Elements
  2. Simple attributes
  3. Built in React elements
  4. Custom Elements
  5. JSON
  6. Children

Last week we got feedback from our New York team that our components are a bit too strict.

They ended up having to use plain html more than they wanted to because we didn't allow them to pass through class names the way the wanted to.

So now we have something else to make better!

We started thinking, and collaborating with some smart folks on a gist. And decided to try passing in an <img> instead of JSON.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment