top of page
Search

Elegant way to implement granular conditional rendering in React

## User Permissions and Conditional Rendering in React: A Granular Approach


Managing user permissions is fundamental to building secure and user-friendly web applications. React, with its component-based architecture, offers an elegant way to implement granular conditional rendering based on these permissions. This article provides a comprehensive guide to achieving fine-grained control over UI elements using TypeScript, contexts, and a JSON-based permission structure.


### Setting up the Permissions Context (TypeScript)


We begin by defining a TypeScript interface (`Permissions`) to represent the structure of our permissions data. This structure is nested, enabling us to specify permissions at the page level and then down to individual UI elements like tabs, buttons, and side panels.  This allows for maximum flexibility and control.


```typescript

interface Permissions {

  pages: {

    [pageName: string]: { // Dynamic page names

      view: boolean;

      create: boolean;

      delete: boolean;

      search: boolean;

      tabs: {

        [tabName: string]: boolean; // Dynamic tab names

      };

      buttons: {

        [buttonId: string]: boolean; // Dynamic button IDs

      };

      sidePanel: boolean;

      [key: string]: any; // Allow for other page-specific properties

    };

  };

  global: { // Global permissions (optional)

    [permissionName: string]: boolean;

  };

}

```


Next, we create a React Context (`PermissionsContext`) to make these permissions accessible throughout our application. A `PermissionsProvider` component wraps the parts of the app where permission-based rendering is needed. This provider fetches the permissions data (typically from an API) and makes it available via the context.


```typescript

import React, { createContext, useContext, useState, useEffect } from 'react';


const PermissionsContext = createContext<Permissions>({

  pages: {}, // Initialize with empty object

  global: {}

});


export const PermissionsProvider = ({ children }: { children: React.ReactNode }) => {

  const [permissions, setPermissions] = useState<Permissions>({ pages: {}, global: {} });


  useEffect(() => {

    const fetchPermissions = async () => {

      try {

        const response = await fetch('/api/permissions'); // Replace with your actual API endpoint

        if (!response.ok) {

          throw new Error(`HTTP error! status: ${response.status}`);

        }

        const data: Permissions = await response.json();

        setPermissions(data);

      } catch (error) {

        console.error('Failed to fetch permissions:', error);

        // Handle error, e.g., set default permissions or show an error message.

        setPermissions({ pages: {}, global: {} }); // Revert to defaults on error.

      }

    };


    fetchPermissions();

  }, []);


  return (

    <PermissionsContext.Provider value={permissions}>

      {children}

    </PermissionsContext.Provider>

  );

};


export const usePermissions = () => useContext(PermissionsContext);

```


### Fetching Permissions (API)


The `useEffect` hook within the `PermissionsProvider` is responsible for fetching the permissions data. In a real-world scenario, this would involve making an API call to your backend. The example uses a placeholder `/api/permissions` endpoint. The API should return a JSON object conforming to the `Permissions` interface structure. It's crucial to handle potential errors during the API call and provide default values or error messages to the user.


### Granular Conditional Rendering: The Customer Page Example


The `CustomerPage` component below demonstrates how to use the `usePermissions` hook to access the permissions and perform granular conditional rendering.  We've added dynamic keys to the permissions structure, so you can add any page, tab, or button without changing the TypeScript.


```typescript

import React from 'react';

import { usePermissions } from './PermissionsContext'; // Import your context


const CustomerPage = () => {

  const permissions = usePermissions();

  const pageName = "customerPage";


  const customerPermissions = permissions.pages[pageName];


  if (!customerPermissions) {

    return <p>No permissions defined for this page.</p>;

  }


  const { view, create, delete: canDelete, search } = customerPermissions;

  const tabs = customerPermissions.tabs || {}; // Handle undefined tabs

  const buttons = customerPermissions.buttons || {}; // Handle undefined buttons

  const sidePanelView = customerPermissions.sidePanel;


  return (

    <div>

      {view ? ( // Conditional rendering for the entire page

        <div>

          <h2>Customer Page</h2>


          {/* Dynamic Tab Visibility */}

          <div>

            {Object.keys(tabs).map(tabName => (

              tabs[tabName] && <button key={tabName}>{tabName}</button>

            ))}

          </div>


          {/* Action Button Visibility */}

          <div>

            {create && <button>Create Customer</button>}

            {search && <button>Search Customers</button>}

            {canDelete && <button>Delete Customer</button>}

          </div>


          {/* Dynamic Button Enable/Disable */}

          <div>

            {Object.keys(buttons).map(buttonId => (

              <button key={buttonId} disabled={!buttons[buttonId]}>{buttonId}</button>

            ))}

          </div>


          {/* Side Panel Visibility */}

          {sidePanelView && (

            <div>

              <h3>Side Panel</h3>

              {/* Side panel content here */}

            </div>

          )}

        </div>

      ) : (

        <p>You do not have permission to view this page.</p>

      )}

    </div>

  );

};


export default CustomerPage;


```


### Example `permissions.json` (Dynamic)


The following JSON file illustrates how permissions might be structured for the customer page example. This structure allows you to define permissions for each page and then for specific elements within that page.  It now uses dynamic keys, so you can add or remove tabs and buttons as needed.


```json

{

  "pages": {

    "customerPage": {

      "view": true,

      "create": true,

      "delete": false,

      "search": true,

      "tabs": {

        "General": true,

        "Job": true,

        "Account": false,

        "Contact": true,

        "Attribute": false,

        "Audit Trail": true

      },

      "buttons": {

        "Save": true,

        "Cancel": false,

        "Refresh": true

      },

      "sidePanel": true,

      "customProperty": "someValue" // Example of a custom property for the page

    },

    "anotherPage": { // Example of another page with its own permissions

        "view": true,

        "tabs": {

            "Summary": true,

            "Details": false

        }

    }

  },

  "global": {

    "accessAdminPanel": false

  }

}

```


### Conclusion


This article has demonstrated a robust approach to implementing granular conditional rendering in React using TypeScript and a JSON-based permission system. This method allows for fine-grained control over UI elements, enhancing both security and user experience. Remember to adapt the structure and permissions to match the specific requirements of your application. Consider implementing caching and efficient permission management strategies as your application scales.  The dynamic key approach gives you a very flexible way to manage permissions without having to rewrite your TypeScript definitions every time you add a new control.

 
 
 

Recent Posts

See All
What we can learn from cats

That's a fascinating observation, and you've touched upon something quite profound about the apparent inner peace that some animals seem...

 
 
 

Comments


Post: Blog2_Post

Subscribe Form

Thanks for submitting!

©2020 by LearnTeachMaster DevOps. Proudly created with Wix.com

bottom of page