Is this a React Anti-pattern?

date
Jan 5, 2021
published
slug
is-this-a-react-anti-pattern
description
I am working on a permissions component that uses nested children, non reactive state, and global space. It feels like a anti-pattern. But it is so simple and clean and seems to work.
I only recently started to use React again. Truth be told, while it has gotten a lot better. Vue and Svelte feel a lot more modern and easier to use at times. Vue specifically has the name structures in a great place. And Svelte is just so terse and easy to write.
Recently I wanted to create a component that governs access to a feature. It needs to render the content if it is allowed, or render alternate content if disallowed.
A realistic example may be rendering a “Edit” button in a notes app, if you have permissions to edit notes. If you do not, instead render a disabled button so the user understands what permissions they don’t have.
The Feature component is pretty straight forward. It checks against the store to see if the feature requested is allowed. In this case it is using a zustand store. I am accessing it via getState() because I don’t want the component to be reactive, requiring a new login if the permissions change.
import { useAccessStore } from "../lib/stores";

let access = false;

// Only rendered when access is allowed
const Allow = function FeatureAllow({ children }) {
  return access ? children : null;
};

// Only rendered when access is disallowed
const Disallow = function FeatureDisallow({ children }) {
  return access ? null : children;
};

// Evaluates a string of feature name
// Feature name must be present and true to pass
export default function Feature({ children, checks }) {
  // Sets access based on if the feature is present and true within the useAccessStore
  access = useAccessStore().getState().features?.[checks] ?? false;

  return children;
}

Feature.Allow = Allow;
Feature.Disallow = Disallow;
The design is essentially three tags -
  • <Feature> - Handles the check against the access store
  • <Feature.Allow> - Handles what to render when access is granted
  • <Feature.Disallow> - Handles what to render when access is not granted
Here is a example of usage.
<Feature checks="edit_note">
  <Feature.Allow>
    <button className="p-4 text-center text-blue-500 hover:text-blue-400">Edit</button>
  </Feature.Allow>
  <Feature.Disallow>
    <button disabled className="disabled p-4 text-center text-gray-500 cursor-pointer">Edit</button>
  </Feature.Disallow>
</Feature>
I love it, it works, it is simple, it is clear, and easy to maintain. But it feels like a anti-pattern.
  • It’s not reactive. Right now I am using this with SSR, and kind of want to keep it that way. But it feels like it should be reactive for future proof.
  • The “state” of allow, just hangs of global space in the Feature.jsx component file. This seems to work without conflicting with other instances. But I don’t know if this breaks hydration or other more complicated items later on.
  • Attaching the .Allow and .Disallow off of the parent feels like magic. But is it best practice? Not sure how this impacts bundling and tree shaking.
Hit me up on twitter. I am curious if this is the best way to handle this. Here is the thread.