loading...

Render Browser Specific Content with React 🎉

flexdinesh profile image Dinesh Pandiyan ・2 min read

Browser Banner
TL;DR - You can render browser specific content in React with a one-liner.

Have you ever wanted to put up a banner for all your IE users and ask them to try your site in Chrome/Firefox?

With RenderInBrowser component you can render content specific to browsers.

As promised in my post a few weeks back, I've ported the code from my other project, wrote thorough tests and created a standalone React library to render content only in specified browsers.

The syntax is too simple.

If you want to render something only in Chrome,

<RenderInBrowser only chrome>
  <div>Whoa! This super duper text line will be rendered only in Chrome!</div>
</RenderInBrowser>

If you want to render something in all browsers except IE,

<RenderInBrowser except ie>
  <div>Darn, this stuff doesn't work in IE :(</div>
</RenderInBrowser>

This library is available as an NPM package and is still in beta stage (v0.2.0) 'coz I'd like to take feedback and improve on it before publishing v1.0.0.

Here's the link to the GitHub repo.

If you find something that could be improved, pl drop a feedback note and I'd very much welcome it. If you don't find anything that could be improved, you could still drop a Hi and I'll Hi you back :)

You are amazing! Have a good day! ⚡️

Posted on May 27 '18 by:

flexdinesh profile

Dinesh Pandiyan

@flexdinesh

Engineer | Speaker | Blogger | OSS | I build things ☕

Discussion

markdown guide
 

Just my first thoughts on reading through your code:

  • Why the callbacks for the browser object? You could just return { default: { chrome: true, blink: true } }; and be done with it - or do you expect the user agent to change during the execution?
  • It could be beneficial to include an existing detection logic or extend the existing one, so I would suggest an additional optional property for that
  • Electron is becoming rather important these days, so you should probably add it to the browser detection
  • In many cases, distinction between browser versions might be important, too
 

Alex - Thanks for the feedback. Interesting and intriguing observations.

  • The reason for getting the browsers object during render is, if we don't define that as a method, it might be evaluated during build time (say, webpack build). So I thought it'd be a good idea to get the object during render. Although now I think that we could do this only once in the constructor and not during each render.

  • The fact that electron is a browser totally skipped my mind. We should add that to the list of browsers definitely. I'm open to PRs :)

  • Distinction between browser version makes a lot of sense, say we want to put up a banner only in IE10 and not in IE11. Although we have to think through the syntax design as well. 'Coz the syntax of the component looks great now and I'm not sure how to add versions to it without complicating the use case. Gotta think through this.

I really appreciate you taking the time to go through the code and come back with areas of improvement. Thanks again!

 

Code doesn't execute during build time. Though it will execute during SSR. Doing it in constructor/render will make it execute during SSR which will fail. You can do it in componentDidMount and update state to make sure it only executes on client.

You're right. I meant the build time assignment evaluation that happens when we run minification as how uglify plugin does. Still, it wouldn't matter as we wrap all the browser detection as individual methods.

 

Thank you for sharing.

Did you consider using Browserslist and if so what was the reason for rejecting it?

A small couple of points on the the code.

The final else if is redundant and can be replaced with else. All of the tests will still pass because it is impossible to fail to satisfy this condition once reached.

  if (all) {
  } else if (none) {
  } else if (except) {
  } else if (only) {
  } else if (!all && !only && !except && !none) {
  }
  if (all) {
  } else if (none) {
  } else if (except) {
  } else if (only) {
  } else {
  }

As you can see the final two blocks contain identical logic:

  } else if (only) {
    allBrowsers.forEach((browser) => {
      if (props[browser]) allowedBrowsers.push(browser);
      else restrictedBrowsers.push(browser);
    });
  } else {
    allBrowsers.forEach((browser) => {
      if (props[browser]) allowedBrowsers.push(browser);
      else restrictedBrowsers.push(browser);
    });
  }

So this can be simplified further to just:

  if (all) {
  } else if (none) {
  } else if (except) {
  } else {
  }

I think it can be simplified further as the logic is the same for the final two blocks - they just swap the two list variables (allowedBrowsers/restrictedBrowsers) around. I am not sure it is worth extracting it to a function though - it is a trade off between explicitness and duplication.

  } else if (except) {
    allBrowsers.forEach(browser => {
      if (props[browser]) restrictedBrowsers.push(browser);
      else allowedBrowsers.push(browser);
    });
  } else {
    allBrowsers.forEach(browser => {
      if (props[browser]) allowedBrowsers.push(browser);
      else restrictedBrowsers.push(browser);
    });
  }
 

I've had a bit longer to think about the shouldRenderForBrowser function and below are some suggestions:

  1. Defer determining the current browser until it is required (i.e. if the all or none props are provided the current browser is irrelevant).
  2. It is not necessary to maintain two lists (restricted and allowed) because by definition any browser which is restricted cannot be allowed and vice versa.
  3. The number of return statements can be reduced as false is the default.

This leads to something like this:

const shouldRenderForBrowser = (props, browsers) => {
  const { except, all, none } = props;

  if (all) {
    return true;
  }

  if (!none) {
    const currentBrowser = browsers.find(browser => browser.isCurrentBrowser);

    const browserProps = browserCheck.allBrowsers.filter(
      browser => props[browser]
    );

    const browserMatches =
      currentBrowser && browserProps.includes(currentBrowser.name);

    return except ? !browserMatches : browserMatches;
  }

  return false;
};
 

This is brilliant.

I ported this code from an old project which was built in a hurry. As I ported, I failed to look into optimization points thinking it should already be optimized. This is what happens when you trust your code blindly.

Thanks for all these pointers. I'm gonna refactor the component again and loop in all these suggestions. You are amazing!

Also, if you're interested in creating a PR, feel free to play around.

 

Guess what I am going to try out tomorrow? :)

 

I'd love to see a screenshot of it 🍿

 

Maybe a noob question but I saw you using @ in your imports? What does that mean? Do we have to configure webpack to support that syntax?

 

You're right. There's a webpack config for that. We need to specify alias. Refer this link for more info.

webpack alias config