{"id":28958638,"url":"https://github.com/deniz-blue/discordjsx","last_synced_at":"2025-07-25T07:33:46.858Z","repository":{"id":290373008,"uuid":"974190784","full_name":"deniz-blue/discordjsx","owner":"deniz-blue","description":"React for discord.js","archived":false,"fork":false,"pushed_at":"2025-06-25T16:15:47.000Z","size":311,"stargazers_count":12,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-24T03:42:22.221Z","etag":null,"topics":["components-v2","custom-renderer","discord","discord-js","discordjs","jsx","react","react-reconciler","reactjs","tsx-renderer"],"latest_commit_sha":null,"homepage":"http://djsx.deniz.blue/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/deniz-blue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-28T11:51:01.000Z","updated_at":"2025-06-29T10:24:01.000Z","dependencies_parsed_at":"2025-06-09T03:15:08.628Z","dependency_job_id":null,"html_url":"https://github.com/deniz-blue/discordjsx","commit_stats":null,"previous_names":["deniz-blue/discordjsx"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/deniz-blue/discordjsx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deniz-blue%2Fdiscordjsx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deniz-blue%2Fdiscordjsx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deniz-blue%2Fdiscordjsx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deniz-blue%2Fdiscordjsx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deniz-blue","download_url":"https://codeload.github.com/deniz-blue/discordjsx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deniz-blue%2Fdiscordjsx/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266973214,"owners_count":24014639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-25T02:00:09.625Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["components-v2","custom-renderer","discord","discord-js","discordjs","jsx","react","react-reconciler","reactjs","tsx-renderer"],"created_at":"2025-06-23T23:01:06.017Z","updated_at":"2025-07-25T07:33:46.824Z","avatar_url":"https://github.com/deniz-blue.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# discord-jsx-renderer\n\n[![Discord](https://img.shields.io/discord/1197520507617153064?logo=discord)](https://deniz.blue/discord-invite?id=1197520507617153064)\n[![Static Badge](https://img.shields.io/badge/view_on-github-blue?logo=github)](https://github.com/deniz-blue/discordjsx)\n![NPM Version](https://img.shields.io/npm/v/discord-jsx-renderer)\n![NPM Last Update](https://img.shields.io/npm/last-update/discord-jsx-renderer)\n![GitHub Repo stars](https://img.shields.io/github/stars/deniz-blue/discordjsx)\n\nRender react components for Discord interactions (Components V2 supported!)\n\nYou can use all sorts of react features including **state**, **context**, **effects** and more.\n\n## Examples\n\n![Screenshot](./examples/counter/screenshot.png)\n\n**Code:**\n\n```jsx\nexport const Counter = () =\u003e {\n    const [count, setCount] = useState(0);\n\n    return (\n        \u003cmessage v2 ephemeral\u003e\n            \u003ccontainer\u003e\n                \u003ctext\u003e\n                    Counter: **{count}**\n                \u003c/text\u003e\n                \u003crow\u003e\n                    \u003cbutton\n                        style=\"danger\"\n                        onClick={() =\u003e setCount(c =\u003e c - 1)}\n                    \u003e\n                        -1\n                    \u003c/button\u003e\n                    \u003cbutton\n                        style=\"success\"\n                        onClick={() =\u003e setCount(c =\u003e c + 1)}\n                    \u003e\n                        +1\n                    \u003c/button\u003e\n                \u003c/row\u003e\n            \u003c/container\u003e\n        \u003c/message\u003e\n    )\n};\n```\n\n**More examples:**\n- [TicTacToe (2 players)](https://github.com/deniz-blue/games-bot/blob/c3ce8f573ecdf194d45839fc3303cc8331beec0a/src/games/TicTacToe.tsx)\n- [With react-router](https://github.com/deniz-blue/games-bot/blob/c3ce8f573ecdf194d45839fc3303cc8331beec0a/src/experiments/ReactRouterExperiment.tsx)\n\n## Installation\n\nSimply install `discord-jsx-renderer` and `react` with your package manager of choice:\n\n```sh\nnpm add discord-jsx-renderer react\n```\n\nFor types, install `pure-react-types`:\n\n```sh\nnpm add --save-dev pure-react-types\n```\n\nDon't install `@types/react` because it has a lot of DOM/HTML types built in that make everything difficult.\n\nFinally, you can add these to your `tsconfig.json`:\n\n```json\n{\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"types\": [\"pure-react-types\"],\n    \"lib\": [\"ESNext\"],\n  },\n}\n```\n\nYou *should* specify `lib` because the default value includes `\"DOM\"` so it pollutes your types.\n\n## Usage\n\nYou should create a single `DJSXRendererManager` - it handles every instance of rendered responses to interactions and handles message component events.\n\n```ts\nimport { DJSXRendererManager } from \"discord-jsx-renderer\";\n\nexport const djsx = new DJSXRendererManager();\n```\n\nOn your `InteractionCreate` event, call `djsx.dispatchInteraction`. If you dont, you won't be able to use `onClick` or `onSelect`.\n\n```ts\nclient.on(Events.InteractionCreate, (interaction: Interaction) =\u003e {\n    djsx.dispatchInteraction(interaction);\n});\n```\n\nThats all you need for the setup.\n\nTo render a react component, simply call `djsx.create(target, element)`: \n\n```jsx\nclient.on(Events.InteractionCreate, (interaction) =\u003e {\n    if(!interaction.isChatInputCommand()) return;\n    // Also do command handling logic etc.\n    \n    djsx.create(interaction, \u003cMyComponent /\u003e);\n});\n```\n\n```jsx\n// or if youre using the discordjs.guide way\nmodule.exports = {\n    data: ...,\n    async execute(interaction) {\n        djsx.create(interaction, \u003cMyComponent /\u003e);\n    },\n};\n```\n\nYou can reply to almost all kinds of interactions or even pass a `Message` or any `Channel` to the first argument!\n\n## Elements\n\nSee [docs/Elements.md](./docs/Elements.md) for a list of the built-in JSX elements you can use to structure your components.\n\n## Features\n\n### React Features\n\nWe have support for almost all of React 19 features. The ones we tested are:\n- Hooks\n- State\n- Context\n- FC\n\nWe are in the process of adding Suspense support.\n\nComponents can also re-render without any interaction event like so:\n\n```tsx\nconst [counter, setCounter] = useState(0);\n\nuseEffect(() =\u003e {\n    let i = setInterval(() =\u003e {\n        setCounter(c =\u003e c + 1);\n    }, 10 * 1000);\n    return () =\u003e clearInterval(i);\n}, []);\n\nreturn (\n    \u003ctext\u003e\n        {counter}\n    \u003c/text\u003e\n);\n```\n\n### Event Handlers\n\nButtons and Selects can have inline event handlers:\n\n```jsx\n\u003cbutton onClick={handler}\u003e\n    My Button\n\u003c/button\u003e\n```\n\nIf they don't have an event handler or dont cause a re-render, `discord-jsx-renderer` calls `deferUpdate`\n\n### Auto-Disabling\n\nAny `Interaction` token is valid for up to **15 minutes**. `discord-jsx-renderer` will update the message to have every component disabled just before the interaction token expires for UX purposes.\n\nYou can also use the below snippet to disable components in rendered messages manually before exiting the nodejs process:\n\n```js\nconst beforeExit = () =\u003e {\n    djsx.disable()\n        .catch(e =\u003e console.log(e))\n        .finally(() =\u003e process.exit(0));\n};\n\nprocess.on(\"SIGTERM\", beforeExit);\nprocess.on(\"SIGINT\", beforeExit);\n```\n\n## API\n\nHow does it work?\n\n`discord-jsx-renderer` is compromised of 4 things:\n- `reconciler` (`JSXRenderer`) is a custom react renderer that renders the jsx into our own internal structure and also handles other stuff such as effects/state/hooks managment, re-rendering, commits, scheduling etc.\n- `PayloadBuilder` parses the output from reconciler and builds a discord payload to use for the REST API. It also collects all the event handlers attached to the JSX. *If you are going to use it, please make a new class per message/payload*\n- `MessageUpdater` handles updating the message from all sorts of sources, it also keeps track of interaction expiry, handles unreplied interactions and handles disabling\n- `DJSXRenderer` brings MessageUpdater, PayloadBuilder and reconciler together\n- `DJSXRendererManager` manages multiple renderers and helps dispatch any new interaction events from the `discord.js` client to the renderer. You can use the renderer itself but you will need to handle dispatching interactions and cleanup yourself too.\n\n**Custom Ids:** If you use the `customId` prop on a jsx element, the `onClick` will **not** work. This is because the renderer creates its own customId's when they are missing and the generated ones include a prefix identifying that renderer. This was done so that the renderer can use `interaction.deferUpdate` if the react component does not re-render to cause a reply to the message component interaction.\n\nGenerated customId's will be in the format of `djsx:A:B` where `A` is the UUID key of the renderer and B is a random UUID unique to the message component.\n\n### Hot-Reloading\n\nYou can replace rendered nodes to have some sort of hot-reloading functionality.\n\n```jsx\nlet node = \u003cMyComponent /\u003e;\nlet renderer = new DJSXRenderer(interaction, node);\n\n// Somehow get value of updated node\nlet newNode = \u003cMyComponent /\u003e;\nrenderer.setNode(newNode);\n```\n\n## Known Issues\n\n\u003e AKA To-Do\n\n- `\u003cmodal\u003e`s are still in development\n- Message v1 - `\u003cembed\u003e` not implemented yet\n- Uploading files via components not supported yet\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeniz-blue%2Fdiscordjsx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeniz-blue%2Fdiscordjsx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeniz-blue%2Fdiscordjsx/lists"}