Rendering as NULL in XHP
There are some at Facebook who have argued against returning null when rendering an XHP element. In fact, it’s actually against Facebook’s standards now. Personally, I prefer to structure my code to return null in XHP, but there definitely are pros and cons. I’ll cover the situations in which you might want to return null and what the alternatives could be, and then you can decide for yourself.
First, let me clarify that when I say returning null, I actually mean returning an empty
class :post:edit-link extends :x:element { attribute Post post @require; protected function render() { $post = $this->getAttribute(‘‘post’’); if ($post->author != get_loggedin_user() && !user_is_admin()) { return
So in this scenario, we have
class :post:edit-link extends :x:element { attribute Post post @require; public static function generateInstance(Post $post) { if ($post->author == get_loggedin_user() | user_is_admin()) { return <post:edit-link post={$post} />; } return null; } protected function render() { $post = $this->getAttribute(‘‘post’’); return <a href={“/post/{$post->id}/edit”}>Edit</a>; }}class :blog:post extends :x:element { attribute Post post @required; protected function render() { $post = $this->getAttribute(‘‘post’’); $editLink = :post:edit-link::generateInstance($post); if ($editLink) { $editLink = |
So now the code is a little less clean, though not by much, but we’ve introduced an important hole into the system. If anyone forgets to instantiate
class :blog:post extends :x:element { attribute Post post @required; protected function render() { $post = $this->getAttribute(‘‘post’’); return <div class="post"> <div class="title">{$post->title}</div> {$post->content} <div class="links"> <post:permalink post={$post} /> · <post:comment-link post={$post} /> · <post:edit-link post={$post} /> </div> </div>; }} // CSS .middot + .middot,.middot:first-child,.middot:last-child { display: none;}
By wrapping the middots in an element, we can target them with CSS and hide any middots that don’t have elements between them, or are trailing at the beginning or end. Unfortunately if you need to support IE6 or basic mobile phones this isn’t going to work. Or maybe you just aren’t a fan of the extra DOM nodes. That’s fine, there’s another way we can handle this. In my post The Difference Between :x:element and :x:primitive I talked about the different rendering processes for each type of XHP class. :x:primitives render after their :x:element children. This means that you actually can write an XHP element that will wait for its children to render before rendering itself, and we can use that to fix our · problem.
class :middot-spacer extends :x:primitve { protected function stringify() { $children = $this->getChildren(); if (!$children) { return ‘’’’; } $split =
This behaves the same way in terms of rendering order as if
class :post:edit-link extends :x:element { attribute Post post @require; public function willRenderNULL() { $post = $this->getAttribute(‘‘post’’); return $post->author != get_loggedin_user() && !user_is_admin(); } protected function render() { $post = $this->getAttribute(‘‘post’’); if ($this->willRenderNULL()) { return
There should be very very few cases that these solutions won’t cover, but you might wonder then, why Facebook has made it a rule of thumb not to return null? I think it has to do with the fact that we traditionally urge against extending :x:primitive (to simplify engineers’ need to understand the inner-workings of XHP) and we also automatically reference elements in JS, which means if the component doesn’t render there might be JS errors. An edge case for sure, and something that isn’t difficult to get around, but it’s one more thing to think about when building. Really when it boils down to it, I prefer to keep my logic consolidated and have a very hard time with the idea that there is a code path that could violate security checks. I suppose it all depends on how well you know your system and where you want the potential problems to arise.