{"id":43394235,"url":"https://github.com/behnamrhp/React-VVM","last_synced_at":"2026-02-02T23:00:53.705Z","repository":{"id":286014946,"uuid":"947141107","full_name":"behnamrhp/React-VVM","owner":"behnamrhp","description":"A lightweight library that provides a reliable MVVM-based architecture to minimize unnecessary re-renders and enhance code maintainability.","archived":false,"fork":false,"pushed_at":"2025-08-07T13:44:20.000Z","size":92,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-28T12:38:40.882Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/behnamrhp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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-03-12T08:11:26.000Z","updated_at":"2025-08-07T13:44:24.000Z","dependencies_parsed_at":null,"dependency_job_id":"9dfdbf8c-33ed-47ef-bb77-3d9ad6c3ed00","html_url":"https://github.com/behnamrhp/React-VVM","commit_stats":null,"previous_names":["behnamrhp/react-vvm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/behnamrhp/React-VVM","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/behnamrhp%2FReact-VVM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/behnamrhp%2FReact-VVM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/behnamrhp%2FReact-VVM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/behnamrhp%2FReact-VVM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/behnamrhp","download_url":"https://codeload.github.com/behnamrhp/React-VVM/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/behnamrhp%2FReact-VVM/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29022774,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T22:20:39.141Z","status":"ssl_error","status_checked_at":"2026-02-02T22:20:37.621Z","response_time":58,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":[],"created_at":"2026-02-02T14:00:29.507Z","updated_at":"2026-02-02T23:00:53.697Z","avatar_url":"https://github.com/behnamrhp.png","language":"TypeScript","readme":"# React vvm [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![npm version](https://img.shields.io/npm/v/reactvvm.svg?style=flathttps://img.shields.io/npm/v/react.svg?style=flat)](https://www.npmjs.com/package/reactvvm)\n\u003c/p align=\"center\"\u003e\n  \u003ca rel=\"noopener\" target=\"_blank\"\u003e\u003cimg width=\"150\" height=\"133\" src=\"./logo.png\" alt=\"React Radio Player logo\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n## Table of contents\n- [React vvm](#react-vvm)\n  - [Table of contents](#table-of-contents)\n  - [Overview](#overview)\n  - [Motivation](#motivation)\n  - [Getting Started](#getting-started)\n    - [Installation](#installation)\n    - [IVM (Interface ViewModel)](#ivm-interface-viewmodel)\n    - [View](#view)\n    - [VM - ViewModel](#vm---viewmodel)\n    - [Connection](#connection)\n  - [Memoization by vm](#memoization-by-vm)\n  - [Dynamic VMs](#dynamic-vms)\n  - [Usage with di](#usage-with-di)\n## Overview\nThis library provides a simple, efficient, and reliable solution for MVVM (Model-View-ViewModel) pattern in React applications. It establishes a shared language for robust architectural coding by combining the MVVM architecture with the Bridge pattern, creating a solid foundation for UI development that maximizes maintainability, testability, and reusability.\n\nBy explicitly defining ViewModels as the UI logic layer for each view, the library effectively prevents unnecessary re-renders that would otherwise propagate from parent components.\n\n## Motivation\nIn an era where most frontend communities—particularly the React ecosystem—focus on discussing new tools, libraries, and immediate development solutions, architectural patterns and software engineering fundamentals often receive less attention. However, in professional software engineering, maintainability, testability, and reusability remain the foundational pillars that every developer should prioritize.\n\nManaging UI components and their associated logic—particularly regarding reusability, maintainability, and testability—remains one of the most persistent challenges for frontend engineers. Finding optimal solutions in this domain continues to be a primary focus of the field.\n\nAmong the various architectural patterns available for frontend development, MVVM (Model-View-ViewModel) stands out as one of the most practical and reliable approaches. While this pattern has been successfully implemented across numerous frameworks and libraries, I noticed a significant gap in the React ecosystem - the absence of a well-designed MVVM solution that properly adapts to React's unique environment and capabilities.\n\nThese considerations led me to create this library - to establish a foundational implementation and shared architectural language for React developers who value proper application structure.\n\nFor deeper insights into the library's design philosophy, MVVM architecture, and relevant design patterns, I recommend these articles:\n\n[Cracking the Code: How the MVVM with Bridge Pattern Saves a Messy Frontend UI (Part 1)](https://dev.to/behnamrhp/cracking-the-code-how-the-mvvm-with-bridge-pattern-saves-a-messy-frontend-ui-part-1-3h4)\n\n[Cracking the Code: How the MVVM with Bridge Pattern Saves a Messy Frontend UI (Part 2)](https://dev.to/behnamrhp/cracking-the-code-how-the-mvvm-with-bridge-pattern-saves-a-messy-frontend-ui-part-2-22oc)\n\n\u003e Note: This library act as ViewModel and View in the MVVM architecture, highly suggested to be used beside of [Reactive Query](https://github.com/Reactive-Query-Lab/reactive-query) which acts as the Model in the MVVM. With these two approaches you can have solid MVVM architecture in your react application in any scale of projects.\n\n## Getting Started\n\n### Installation\nnpm\n```\nnpm install reactvvm\n```\n\nyarn\n```\nyarn add reactvvm\n```\n\npnpm\n```\npnpm install reactvvm\n```\n### IVM (Interface ViewModel)\nThe IVM serves as the foundational bridge between UI components and their business logic. This interface explicitly defines the requirements that a View needs from its UI logic to properly render and function.\n\nExample for a button.\n\n```ts\n// views/button/button-vm.interface.ts\n\nexport default interface ButtonVm {\n  props: {\n    title: string\n    disabled: boolean\n  },\n  onClick(): void\n}\n```\n\u003e Note: Based on best practices, since this interface serves as the dedicated bridge for a specific view, it should be located with its corresponding view component in the same directory.\n\n### View\nThe View is a component that:\n\n- Declares its requirements from the ViewModel through a clear interface\n\n- May receive dependencies via props from parent components\n\n- Maintains strict separation between:\n\n  - Parent-provided dependencies\n\n  - Its own UI logic and state management\n\n**Key Principle:**\nAll UI logic should be encapsulated within the ViewModel, while the View remains focused solely on presentation.\n\nIn typical MVVM implementations, Views should primarily re-render only when their ViewModel (VM) changes, not when parent components update. To achieve this goal, Views by default are memoized to prevent unnecessary parent-triggered re-renders.\n\nAlso rendering isolation ensures Views only update when their VM changes\n\nTo handle these memoization and connection between view and vm, and passing props based on it's ivm, we made a BaseView which handles all these reusable logics and just needed to specify the component in reusable `build` method where you can get your vm and rest of props from parent components.\n\n```tsx\n// views/button/button.ts\nexport default class Button extends BaseView\u003cButtonVm, { className?: string }\u003e {\n  protected Build(\n    props: BuildProps\u003cButtonVm, { className?: string }\u003e,\n  ): ReactNode {\n    const { vm, restProps } = props;\n    const { className } = restProps;\n    return (\n      \u003cButtonUi\n        className={className}\n        disabled={vm.props.disabled}\n        onClick={vm.onClick}\n      \u003e\n        {vm.props.title}\n      \u003c/ButtonUi\u003e\n    );\n  }\n}\n```\n\nIn this example parent components control only visual styling based on specific context by (className).\n\nViewModel handles all ui logic and connection to the logical layer.\n\nIn this architecture, the Button component serves as a self-contained, reusable View - a single source of truth for presenting data to the user.\n\n**Key Benefits:**\n\n- Automatic memoization of View components without any time or space complexity.\n\n- Clean separation between View rendering and VM updates\n\n- Type-safe VM prop passing with isolation between parent props and ui logic props.\n\n- Reduced boilerplate for common MVVM patterns\n### VM - ViewModel\nThe ViewModel serves as the UI logic layer - the component that typically exhibits the most variation in implementation.\n\nFor instance, a button ViewModel might handle:\n- Saving data\n- Canceling operations\n- Deleting items\n- And numerous other context-specific actions\n\n**Problem with Traditional Approach:**\n\n- When using conventional patterns, developers often create parent components to manage multiple child logics. This approach:\n- Violates the Single Responsibility Principle\n- Introduces unnecessary complexity\n- Reduces code readability\n- Makes maintenance more difficult\n\n**So we need a reusable component that:**\n\n- Handles all related ui logic internally.\n\n- Passes processed data to the View.\n\n- Connect to the model or any logical layer.\n\n- Can handle react hooks.\n\nTo standardize this pattern and ensure all VMs follow the same convention for data exchange with Views (compatible with BaseView), we created BaseVM - the foundational class for all ViewModels.\n\n**Implementation Example: Logout Button**\n```ts\n// vms/clear-all-data-vm.ts\nexport default class LogoutButtonVM extends BaseVM\u003cButtonVm\u003e {\n  private model: AuthenticationModel;\n\n  useVM(): ButtonVm {\n    const { t } = useTranslation();\n    return {\n      props: {\n        isDisable: false,\n        title: t(\"logout\"),\n      },\n      onClick() {\n        this.model.logout();\n      },\n    };\n  }\n}\n```\nAs demonstrated in this example, we created a ViewModel by extending BaseVM. To define the required UI contract, we pass the interface to BaseVM as a generic type - the same interface we implemented for the View. This ensures the View remains unaware of any VM variations, simply receiving the interface it needs to render the output.\n\nThis architecture enables thousands of different VMs to be reused with a single Button component.\n\n**In this specific implementation, we:**\n- Managed the button title through translation\n- Connected the click event to the Model's logout method\n- Established a clean connection between View and business logic through the ViewModel\n### Connection\nThe final step is to establish a connection between the ViewModel and the View. This is done by passing the ViewModel as a prop to the View component, which will be handled by a parent component.\n\nIn this case, the parent component determines which UI logic corresponds to which UI element, without concerning itself with the implementation details.\n\n```tsx\nexport default function Sidebar() {\n  const logoutButtonVm = useRef(new LogoutButtonVM());\n\n  return (\n    \u003caside\u003e\n      ...\n        \u003cdiv\u003e\n          \u003cButton\n            memoizedByVM={false}\n            restProps={{\n              className: \"w-full h-14\",\n            }}\n            vm={logoutButtonVm.current}\n          /\u003e\n        \u003c/div\u003e\n      ...\n    \u003c/aside\u003e\n  );\n}\n```\nIn this example, we created a ref to the ViewModel and passed it as a prop to the Button component.\n\nWe also passed other required props to the Button, which are provided by the parent component as a single restProps object.\n\nAdditionally, in this example, we disabled memoization based on the vm prop in the Button component. This means that every re-render of the parent component will trigger a re-render of the Button component.\n\n\u003e Note: By default, the Button component is memoized based on the vm changes to be trigerred just by its ui logic.\n\n\u003e Important: Use this architecture when you have a reusable component (like this Button) that can have multiple implementations. If a view has only one implementation, avoid this approach—instead, a custom hook as a ViewModel (VM) is sufficient.\n\n## Memoization by vm \nAs mentioned before, one of the key benefits of this architecture is the ability to memoize View components based on the VM changes without any additional complexity.\n\nThis approach ensures that a View component only re-renders when its VM changes, preventing unnecessary re-renders caused by parent component updates.\n\nTo control this behavior, you can use the memoizedByVM prop (a boolean flag):\n\n- true (default): The View is memoized based on VM changes.\n\n- false: The View ignores VM-based memoization and may re-render due to parent updates.\n\n## Dynamic VMs\nSome of the VMs might be require dynamic data from outside to be able to work with specially when it comes to render a list of UI which each one of them needs to work with specific data.\n\nLook at this example:\n```tsx\nexport default class UserInfoCardVM extends BaseVM\u003c\n  ICardVM,\n  { userId: string }\n\u003e {\n  ...\n  useVM(): ICardVM {\n    const user = usePromiseValue(() =\u003e this.model.getUserById(this.deps.userId))\n    return {\n      props: {\n        title: user.fullname,\n        subTitle: user.email,\n        icon: LoginIcon,\n      },\n    };\n  }\n  ...\n}\n```\nIn this example we have a VM which implement a card view and needs to get the user id from the outside to render detail information of the user.\n\nYou can see the BaseVM second generic type which is DEP (dependencies) is responsible for getting the dependencies from the outside.\nAnd from inside of VM we can access to this dependencies by using `this.deps`.\n\nHow to use it?\n\n```tsx\nfunction ParentComponent() {\n  ...\n  const userInfoCards = userIds.map((userId) =\u003e {\n    const userInfoCardVm = new UserInfoCardVM().produce({ userId }); \n    return \u003cCard key={userId} vm={userInfoCardVm} /\u003e\n  })\n  ...\n\n  return (\n    ...\n    {userInfoCards}\n    ...\n  )\n}\n```\nIn this example, we have a parent component that maintains a list of user IDs and renders a collection of user information cards. We also have a ViewModel (VM) responsible for providing the necessary data and logic to each card component for rendering user information.\n\nWe pass the required dependencies to the VM using the produce method.\n\n## Usage with di\nIn some frameworks like Next.js, we cannot directly pass ViewModels (VMs) from server components to client components because VMs are not serializable. To work around this limitation, we can use Dependency Injection (DI) to pass a unique key representing the VM instead. The client component can then use this key to retrieve the VM instance through DI.\n\nImplementation Example:\n1. Use `ReactVVMDiProvider` to register your DI container in the React context.\n\n2. Register your VM with the DI container using a unique key.\n\n3. Pass only this key (instead of the full VM) to your view component with `vmKey` prop.\n```tsx \n...\n// In other file\ndi.register(\"EXAMPLE_VM_KEY\", ExampleVM)\n...\n// Some server component\nexport default async function Page() {\n  ...\n  return (\n    \u003cReactVVMDiProvider diContainer={di}\u003e\n      \u003cButton vmKey=\"EXAMPLE_VM_KEY\"\u003e\n    \u003c/ ReactVVMDiProvider\u003e\n  )\n}\n```\n","funding_links":[],"categories":["Code Design"],"sub_categories":["Miscellaneous"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbehnamrhp%2FReact-VVM","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbehnamrhp%2FReact-VVM","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbehnamrhp%2FReact-VVM/lists"}