≡ Menu

Have you ever found yourself passing props down three layers deep just to handle a simple “open/close” state?

It’s a common headache called prop drilling.

In this tutorial, we’re going to build a high-quality, reusable Accordion component using the Compound Component Pattern and React Context.

This approach keeps your code clean, flexible, and easy for other developers to read.


Why Use This Pattern?

Instead of one giant component with 10 different props, we break the accordion into logical pieces: Accordion, AccordionItem, AccordionHeader, and AccordionPanel.

This gives you:

  • Flexibility: You can reorder the header and panel or add custom styles easily.

  • Cleaner State: The parent Accordion manages which items are open, while the children just “consume” that information.


Step 1: Setting Up the Context

First, we need a way to share state across all parts of our accordion without passing props manually.

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

const AccordionContext = createContext();

export const Accordion = ({ children, allowMultiple = false }) => {
  const [openIndices, setOpenIndices] = useState([]);

  const toggleItem = (index) => {
    if (allowMultiple) {
      // Toggle logic for multiple items open at once
      setOpenIndices(prev => 
        prev.includes(index) ? prev.filter(i => i !== index) : [...prev, index]
      );
    } else {
      // Logic for "Accordion" style (only one open)
      setOpenIndices(prev => prev.includes(index) ? [] : [index]);
    }
  };

  return (
    <AccordionContext.Provider value={{ openIndices, toggleItem }}>
      <div className="border rounded-md divide-y shadow-sm">{children}</div>
    </AccordionContext.Provider>
  );
};

Step 2: The Item Wrapper

The AccordionItem acts as a middleman.

It checks the Context to see if its specific index is currently active.

export const AccordionItem = ({ index, children }) => {
  const { openIndices, toggleItem } = useContext(AccordionContext);
  const isOpen = openIndices.includes(index);

  return (
    <div className={`accordion-item ${isOpen ? 'bg-gray-50' : ''}`}>
      {/* We use React.cloneElement to pass the state down to the Header and Panel */}
      {React.Children.map(children, child => 
        React.cloneElement(child, { index, isOpen, toggleItem })
      )}
    </div>
  );
};

Step 3: Header and Panel (The UI)

Now we build the parts the user actually sees. Notice the aria-expanded and role="region" attributes—these are crucial for accessibility (A11y), ensuring screen readers can navigate your component.

The Header:

 

export const AccordionHeader = ({ children, index, isOpen, toggleItem }) => (
  <button
    className="w-full flex justify-between items-center p-4 font-medium text-left transition-colors hover:bg-gray-100"
    onClick={() => toggleItem(index)}
    aria-expanded={isOpen}
  >
    {children}
    <span className={`transform transition-transform ${isOpen ? 'rotate-180' : ''}`}></span>
  </button>
);

The Panel:

export const AccordionPanel = ({ children, isOpen }) => (
  <div 
    className={`overflow-hidden transition-all duration-300 ease-in-out ${
      isOpen ? 'max-h-96 opacity-100 p-4' : 'max-h-0 opacity-0 p-0'
    }`}
    role="region"
  >
    <div className="pb-2 text-gray-600">{children}</div>
  </div>
);

Step 4: Putting It All Together

The beauty of this pattern is how it looks when you actually use it.

It reads almost like HTML.

function App() {
  return (
    <Accordion allowMultiple={false}>
      <AccordionItem index={0}>
        <AccordionHeader>What is React Context?</AccordionHeader>
        <AccordionPanel>It is a way to share values between components without prop drilling.</AccordionPanel>
      </AccordionItem>
      
      <AccordionItem index={1}>
        <AccordionHeader>Is this accessible?</AccordionHeader>
        <AccordionPanel>Yes! It uses ARIA roles and keyboard-friendly button elements.</AccordionPanel>
      </AccordionItem>
    </Accordion>
  );
}

Key Takeaways

  1. allowMultiple: By simply changing this prop on the root component, you switch between a strict accordion and a “collapsible” list.

  2. CSS Transitions: We used max-height and opacity to create a smooth slide-down effect without needing a heavy animation library.

  3. Encapsulation: All the logic lives inside the components, keeping your main page code clean.

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }

JavaScript is the backbone of the modern web, but its flexibility is a double-edged sword.

Because it runs both in the browser and on the server (Node.js), it presents a massive attack surface.

Writing “code that works” isn’t enough anymore; you have to write code that survives.

Here is a deep dive into the essential pillars of secure JavaScript development.


1. Defending Against Cross-Site Scripting (XSS)

XSS remains the most common JavaScript vulnerability. It occurs when an attacker injects malicious scripts into content that other users see.

  • Sanitize and Encode: Never trust user input. Use libraries like DOMPurify to sanitize HTML before rendering it.

  • Use textContent over innerHTML: When displaying plain text, use .textContent. It treats the input as literal text rather than parsing it as HTML, effectively neutralizing script tags.

  • Content Security Policy (CSP): Implement a CSP header to tell the browser which scripts are authorized to run. A strong CSP can block inline scripts and unauthorized external domains.


2. Secure Data Handling & Storage

How you store sensitive information in the browser dictates how easily it can be stolen.

  • Avoid LocalStorage for Secrets: Data in localStorage is accessible to any JavaScript running on your domain. If an attacker finds even a small XSS vulnerability, they can steal session tokens stored there.

  • Use HttpOnly Cookies: For session management, use cookies with the HttpOnly and Secure flags. This prevents JavaScript from accessing the cookie, making it immune to XSS-based theft.

  • Validate on the Server: Client-side validation is for user experience; server-side validation is for security. An attacker can easily bypass your frontend checks using tools like Postman or Burp Suite.


3. Preventing Injection Attacks (NoSQL & SQL)

If you are using Node.js, your database queries are high-value targets.

  • Template Literals are not Safety Nets: Simply using backticks doesn’t prevent injection.

  • Use Parameterized Queries: Whether using PostgreSQL or MongoDB, always use parameterized inputs or Object-Document Mappers (ODMs) like Mongoose that handle sanitization for you.

Vulnerable Example:

// Never do this!
const query = `SELECT * FROM users WHERE id = ${userInput}`;

Secure Example:

// Use placeholders
db.query('SELECT * FROM users WHERE id = $1', [userInput]);

4. Dependency Management: The “Hidden” Risk

Modern JavaScript apps rely on hundreds of npm packages. Each one is a potential backdoor.

  • Audit Regularly: Run npm audit frequently to find known vulnerabilities in your dependency tree.

  • Lock Down Versions: Use package-lock.json to ensure that your production environment uses the exact same code you tested in development.

  • Beware of “Typosquatting”: Double-check package names before installing (e.g., lodash vs lo-dash).


5. Secure Use of eval() and setTimeout()

The eval() function executes a string as code. It is one of the most dangerous functions in JavaScript.

  • The Rule: Just don’t use eval(). There is almost always a safer way to achieve your goal using JSON parsing or bracket notation.

  • Dynamic Function Execution: Similarly, avoid passing strings to setTimeout() or setInterval(), as this invokes the same internal mechanism as eval(). Always pass a function reference instead.


6. Protections Against Prototype Pollution

JavaScript’s prototype-based inheritance can be exploited if an attacker can inject properties into Object.prototype. This can lead to logic bypasses or even Remote Code Execution (RCE).

  • Freeze the Prototype: In sensitive environments, use Object.freeze(Object.prototype).

  • Use Map for Key-Value Pairs: Instead of using plain objects for user-controlled data, use Map, which does not inherit from the standard Object prototype in the same way.

  • Validation: Use JSON schema validators to ensure incoming JSON objects only contain the expected keys.


Summary Checklist

Risk Mitigation
XSS Use textContent, DOMPurify, and CSP.
Token Theft Store session IDs in HttpOnly cookies.
Injection Use parameterized queries/ORMs.
Supply Chain Run npm audit and use lockfiles.
Prototype Pollution Use Object.create(null) or Map.

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments }