Skip to main content
Discord’s UI is built with React. BetterDiscord provides utilities for working with React components and the React tree.

Accessing React

React is available through BdApi:
const React = BdApi.React;

// Create elements
const element = React.createElement("div", { className: "my-element" }, "Hello World");

// Use JSX if your build process supports it
const element = <div className="my-element">Hello World</div>;

ReactUtils

The BdApi.ReactUtils namespace provides utilities for working with React internals.

Getting internal instances

Get the React fiber node from a DOM element:
const element = document.querySelector(".message-content");
const fiber = BdApi.ReactUtils.getInternalInstance(element);

if (fiber) {
    console.log("Component:", fiber.type);
    console.log("Props:", fiber.memoizedProps);
    console.log("State:", fiber.memoizedState);
}

Getting owner instances

Find the nearest React component instance (usually a class component):
const element = document.querySelector(".message-content");
const owner = BdApi.ReactUtils.getOwnerInstance(element);

if (owner) {
    console.log("Component instance:", owner);
    console.log("Props:", owner.props);
    console.log("State:", owner.state);
}
Filter by component name:
// Only find components with these names
const owner = BdApi.ReactUtils.getOwnerInstance(element, {
    include: ["Message", "MessageContent"]
});

// Find components excluding these names
const owner = BdApi.ReactUtils.getOwnerInstance(element, {
    exclude: ["Popout", "Tooltip"]
});
Default exclusions: ["Popout", "Tooltip", "Scroller", "BackgroundFlash"] Use a custom filter:
const owner = BdApi.ReactUtils.getOwnerInstance(element, {
    filter: (instance) => {
        return instance.props.message?.author?.id === "123456789";
    }
});

Wrapping HTML elements

Wrap HTML elements in a React component:
const htmlElement = document.createElement("div");
htmlElement.textContent = "Custom content";

const WrappedComponent = BdApi.ReactUtils.wrapElement(htmlElement);

// Use in React
BdApi.showConfirmationModal("Title", [
    BdApi.React.createElement(WrappedComponent)
]);
Wrap multiple elements:
const elements = [
    document.createElement("div"),
    document.createElement("span")
];

const WrappedComponent = BdApi.ReactUtils.wrapElement(elements);

Working with exotic components

Unwrap components from React.memo, React.forwardRef, and React.lazy:
const wrappedComponent = React.memo(MyComponent);
const actualComponent = BdApi.ReactUtils.getType(wrappedComponent);

console.log(actualComponent === MyComponent); // true
This is useful when patching:
const module = BdApi.Webpack.getByKeys("MessageContent");
const Component = BdApi.ReactUtils.getType(module.MessageContent);

// Now you can patch the actual component
BdApi.Patcher.after("MyPlugin", module, "MessageContent", callback);

Rendering functional components

Render functional components outside of React’s normal lifecycle:
const FunctionalComponent = (props) => {
    const [count, setCount] = React.useState(0);
    return React.createElement("div", null, `Count: ${count}`);
};

// Wrap to render with hooks support
const wrapped = BdApi.ReactUtils.wrapInHooks(FunctionalComponent);
const result = wrapped({ someProp: "value" });

console.log(result); // Rendered React element
Provide custom hook implementations:
const wrapped = BdApi.ReactUtils.wrapInHooks(FunctionalComponent, {
    useState(initial) {
        return ["custom value", () => {}];
    },
    useEffect() {
        console.log("Effect called");
    }
});
wrapInHooks uses mock hooks. State changes won’t cause re-renders. Use this only for inspecting components or testing renders.

Creating React components

Functional components

Create simple functional components:
function MyComponent(props) {
    return BdApi.React.createElement("div", {
        className: "my-component",
        style: { color: props.color }
    }, props.children);
}
With JSX:
function MyComponent({ color, children }) {
    return (
        <div className="my-component" style={{ color }}>
            {children}
        </div>
    );
}

Using hooks

Use React hooks in your components:
function Counter() {
    const [count, setCount] = BdApi.React.useState(0);
    
    BdApi.React.useEffect(() => {
        console.log("Count changed:", count);
    }, [count]);
    
    return BdApi.React.createElement("button", {
        onClick: () => setCount(count + 1)
    }, `Count: ${count}`);
}
Common hooks:
  • useState - Component state
  • useEffect - Side effects
  • useCallback - Memoized callbacks
  • useMemo - Memoized values
  • useRef - Persistent references
  • useContext - Context values

Class components

Create class-based components:
class MyComponent extends BdApi.React.Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 };
    }
    
    componentDidMount() {
        console.log("Component mounted");
    }
    
    componentWillUnmount() {
        console.log("Component will unmount");
    }
    
    render() {
        return BdApi.React.createElement("div", null,
            `Count: ${this.state.count}`
        );
    }
}

Modifying React trees

Finding elements

Search through React elements:
function findInTree(tree, filter) {
    if (filter(tree)) return tree;
    
    if (tree?.props?.children) {
        if (Array.isArray(tree.props.children)) {
            for (const child of tree.props.children) {
                const result = findInTree(child, filter);
                if (result) return result;
            }
        } else {
            return findInTree(tree.props.children, filter);
        }
    }
    
    return null;
}

// Usage in a patch
BdApi.Patcher.after("MyPlugin", module, "render", (thisObject, args, returnValue) => {
    const button = findInTree(returnValue, 
        e => e?.props?.onClick && e.type === "button"
    );
    
    if (button) {
        button.props.className += " custom-button";
    }
    
    return returnValue;
});

Modifying children

Safely modify React children:
BdApi.Patcher.after("MyPlugin", module, "render", (thisObject, args, returnValue) => {
    if (!returnValue?.props?.children) return returnValue;
    
    const children = returnValue.props.children;
    
    // Handle arrays
    if (Array.isArray(children)) {
        children.push(BdApi.React.createElement("span", null, "Added!"));
    }
    // Handle single child
    else {
        returnValue.props.children = [
            children,
            BdApi.React.createElement("span", null, "Added!")
        ];
    }
    
    return returnValue;
});

Using React.Children

Utilities for working with children:
function MyComponent({ children }) {
    const childArray = BdApi.React.Children.toArray(children);
    
    // Map over children
    const modifiedChildren = BdApi.React.Children.map(children, child => {
        if (BdApi.React.isValidElement(child)) {
            return BdApi.React.cloneElement(child, {
                className: "modified"
            });
        }
        return child;
    });
    
    return BdApi.React.createElement("div", null, modifiedChildren);
}

Context

Reading context

Access React context values:
const ThemeContext = BdApi.Webpack.getByKeys("ThemeContext")?.ThemeContext;

function ThemedComponent() {
    const theme = BdApi.React.useContext(ThemeContext);
    
    return BdApi.React.createElement("div", {
        style: { backgroundColor: theme === "dark" ? "#000" : "#fff" }
    });
}

Creating context

Create your own context:
const MyContext = BdApi.React.createContext("default value");

function Provider({ children }) {
    return BdApi.React.createElement(MyContext.Provider, {
        value: "provided value"
    }, children);
}

function Consumer() {
    const value = BdApi.React.useContext(MyContext);
    return BdApi.React.createElement("div", null, value);
}

Node patching

Create a node patcher for advanced DOM manipulation:
const nodePatcher = BdApi.ReactUtils.createNodePatcher();

nodePatcher.patch({
    callback: (node) => {
        if (node.classList.contains("message-content")) {
            node.style.backgroundColor = "yellow";
        }
    }
});

// Clean up when done
nodePatcher.unpatch();

Best practices

  • Always clean up in componentWillUnmount or useEffect cleanup
  • Use React.memo for expensive components
  • Avoid creating components inside render methods
  • Handle missing props and children gracefully
  • Use proper dependency arrays in useEffect and useMemo
  • Test your components with React DevTools
Modifying React’s internal fiber structure can cause crashes or unexpected behavior. Always test thoroughly.

Build docs developers (and LLMs) love