Search code examples
javascriptreactjsstorefluxible

How can I make my React child components render even if a prop is missing


TLDR; I need to be able to render child components in React even if a property of this.props is missing.

I have an React app built with Yahoo's Flxubile. The app fetches data from a Wordpress site with WP REST API. Sometimes an image might be missing or something else from the API, this causes the client to break. Here's an example:

I have a file called Articles.js which is connected to ArticlesStore which holds my articles. I then render one Article.js for every article I have and pass props like this:

{   this.props.articles.map(function(el, index) {                                           
      return <Article key={index} article={el} />
    })
}

Everything is ok here, but then in my Article.js when I try to access properties that is not set I get the following:

Uncaught TypeError: Cannot read property 'sizes' of undefined

This is the line which causes the error:

<img src={this.props.article.meta_fields.image.sizes.large} />

This happens when a image is missing from an article. I understand the javascript error of course but I want to render the Article.js component event if a image url is missing from the API/Store. I have tried the following solutions, but it causes too much clutter and no control:

  1. Conditional set if we have the prop i.e. {this.props.article.meta_fields.image ? this.props.article.meta_fields.image.sizes.large : "imagemissing.png"}
  2. Setting defaultProps. This does not work because passed properties from parent to child overwrites defaultProps.

Maybe I should try something else than passing props from parent to child? Have a ArticleStore where I can set default values for every article? How would you do it?


Solution

  • If you want to provide a nested structure as a prop (as with article) you'll want to be able to rely on the structure always being pretty much the same. In this case it won't be, sometimes the meta_fields does not have an image-attribute (as your TypeError suggests).

    In your case I would consider pulling out the things you actually need/use in the Article component from the article object and pass those as props.

    Say that your Article only uses title, body, and image. Then just pass those as props.

    <Article title={ article.title } body={ article.body } image={ getImage(article) }/>
    
    function getImage(article) {
        if (article.meta_fields.image
         && article.meta_fields.image.sizes
         && article.meta_fields.image.sizes.large
        ) {
            return article.meta_fields.image.sizes.large;
        }
        return 'default_image.jpg'; // Or whatever you want.
    }
    

    One might think that the extra props constitutes more "clutter" here, but given the choice between clutter and TypeError, I choose clutter.

    And if you don't want to reinvent the wheel. The problem of accessing data in a nested structure like this have been solved before.

    // Lodash
    _.get(article, 'meta_fields.image.sizes.large', 'default_image.jpg')
    // Ramda
    _.pathOr('default_image.jpg', ['meta_fields', 'image', 'sizes', 'large'], article)