feature: Add reactvvm lib
This commit is contained in:
parent
77b167c695
commit
2c9b9c5b8f
@ -32,6 +32,7 @@
|
||||
"react-cookie": "^7.2.2",
|
||||
"react-dom": "19.0.0-rc-69d4b800-20241021",
|
||||
"react-i18next": "^15.1.0",
|
||||
"reactvvm": "^1.1.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"server-only": "^0.0.1",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
|
@ -15,7 +15,7 @@ type LinkItem = {
|
||||
* so they come together always and there is no need to be connected with interface for reusable
|
||||
* vms.
|
||||
*/
|
||||
export default function navLinkPersonalVM() {
|
||||
export default function useNavLinkPersonalVM() {
|
||||
const pathname = usePathname();
|
||||
// Map of links to display in the side navigation.
|
||||
// Depending on the size of the application, this would be stored in a database.
|
||||
|
@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import navLinkPersonalVM from "@/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm";
|
||||
import useNavLinkPersonalVM from "@/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm";
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function NavLinks() {
|
||||
const { links, isLinkActive } = navLinkPersonalVM();
|
||||
const { links, isLinkActive } = useNavLinkPersonalVM();
|
||||
return (
|
||||
<>
|
||||
{links.map((link) => {
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
import SideNav from "@/app/[lang]/dashboard/components/server/sidenav";
|
||||
import dashboardAppModule from "@/app/[lang]/dashboard/module/dashboard.app-module";
|
||||
import { DiContext } from "@/bootstrap/di/di-context";
|
||||
import { useRef } from "react";
|
||||
import { ReactVVMDiProvider } from "reactvvm";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const di = useRef(dashboardAppModule());
|
||||
return (
|
||||
<DiContext.Provider value={di.current}>
|
||||
<ReactVVMDiProvider diContainer={di.current}>
|
||||
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
|
||||
<div className="w-full flex-none md:w-64">
|
||||
<SideNav />
|
||||
@ -17,6 +17,6 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</DiContext.Provider>
|
||||
</ReactVVMDiProvider>
|
||||
);
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import createInvoiceController from "@/app/[lang]/dashboard/controller/create-in
|
||||
import ButtonVm from "@/app/components/button/button.i-vm";
|
||||
import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action";
|
||||
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
|
||||
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
|
||||
import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
|
||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BaseVM } from "reactvvm";
|
||||
|
||||
/**
|
||||
* Viewmodel for the button view to connect to business logics and all UI logics
|
||||
|
@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import BaseView, { BuildProps } from "@/bootstrap/helpers/view/base-view";
|
||||
import ButtonVm from "@/app/components/button/button.i-vm";
|
||||
import { ReactNode } from "react";
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/bootstrap/helpers/lib/ui-utils";
|
||||
import { BaseView, BuildProps } from "reactvvm";
|
||||
|
||||
export default class Button extends BaseView<ButtonVm> {
|
||||
protected Build(props: BuildProps<ButtonVm>): ReactNode {
|
||||
|
@ -1,19 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import di from "@/bootstrap/di/init-di";
|
||||
import { createContext, use } from "react";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
|
||||
const DiContext = createContext<null | DependencyContainer>(di);
|
||||
|
||||
const useDI = () => {
|
||||
const di = use(DiContext);
|
||||
|
||||
if (!di) {
|
||||
throw new Error("Di has not provided");
|
||||
}
|
||||
|
||||
return di;
|
||||
};
|
||||
|
||||
export { DiContext, useDI };
|
@ -1,147 +0,0 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
/* eslint-disable react/display-name */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
|
||||
"use client";
|
||||
|
||||
import { useDI } from "@/bootstrap/di/di-context";
|
||||
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
||||
import { Component, ReactNode, FC, PropsWithChildren, memo } from "react";
|
||||
import { InjectionToken } from "tsyringe";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Connector Component */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
interface IVvmConnector<IVM, PROPS> extends PropsWithChildren {
|
||||
View: FC<any & { vm: IVM }>;
|
||||
Vm: IBaseVM<IVM>;
|
||||
restProps?: PROPS;
|
||||
memoizedByVM?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is just will be used in
|
||||
*/
|
||||
const VvmConnector = memo(
|
||||
<IVM, PROPS>(props: IVvmConnector<IVM, PROPS>) => {
|
||||
const { View, Vm, restProps, children } = props;
|
||||
const vm = Vm.useVM();
|
||||
|
||||
const allProps = {
|
||||
restProps,
|
||||
vm,
|
||||
};
|
||||
|
||||
return <View {...allProps}>{children}</View>;
|
||||
},
|
||||
(prevProps) => {
|
||||
if (prevProps.memoizedByVM) return true;
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* BaseView */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type IVMParent = Record<string, any>;
|
||||
type IPropParent = Record<string, any> | undefined;
|
||||
|
||||
type BaseProps<PROPS extends IPropParent = undefined> = {
|
||||
restProps?: PROPS;
|
||||
/**
|
||||
* By default it's true.
|
||||
* If you pass true this view will update just by changes of vm not rest props
|
||||
*
|
||||
*/
|
||||
memoizedByVM?: boolean;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
type BasePropsWithVM<
|
||||
IVM extends IVMParent,
|
||||
PROPS extends IPropParent = undefined,
|
||||
> = BaseProps<PROPS> & {
|
||||
/**
|
||||
* Directly instantiated vm
|
||||
*/
|
||||
vm: IBaseVM<IVM>;
|
||||
};
|
||||
|
||||
type BasePropsWithVMKey<PROPS extends IPropParent = undefined> =
|
||||
BaseProps<PROPS> & {
|
||||
/**
|
||||
* TSyringe key for vm to be injected
|
||||
*/
|
||||
vmKey: InjectionToken;
|
||||
};
|
||||
|
||||
export type BuildProps<
|
||||
IVM extends IVMParent,
|
||||
PROPS extends IPropParent = undefined,
|
||||
> = {
|
||||
vm: IVM;
|
||||
restProps: PROPS;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export type ViewProps<
|
||||
IVM extends IVMParent,
|
||||
PROPS extends IPropParent = undefined,
|
||||
> = BasePropsWithVM<IVM, PROPS> | BasePropsWithVMKey<PROPS>;
|
||||
|
||||
/**
|
||||
* Base view is base component for all views in mvvm architecture which gets
|
||||
* vm as props and connect it to the view and memoize the component by default
|
||||
* to just render just on changes of its vm.
|
||||
*/
|
||||
export default abstract class BaseView<
|
||||
IVM extends IVMParent,
|
||||
PROPS extends IPropParent = undefined,
|
||||
> extends Component<ViewProps<IVM, PROPS>> {
|
||||
private vm: IBaseVM<IVM> | undefined;
|
||||
|
||||
constructor(props: ViewProps<IVM, PROPS>) {
|
||||
super(props);
|
||||
this.vm = this.initVm;
|
||||
}
|
||||
|
||||
private get initVm() {
|
||||
if (Object.hasOwn(this.props, "vmKey")) {
|
||||
const { vmKey } = this.props as BasePropsWithVMKey<PROPS>;
|
||||
const di = useDI();
|
||||
return di.resolve(vmKey) as IBaseVM<IVM>;
|
||||
}
|
||||
return (this.props as BasePropsWithVM<IVM, PROPS>).vm;
|
||||
}
|
||||
|
||||
protected get componentName() {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
protected abstract Build(props: BuildProps<IVM, PROPS>): ReactNode;
|
||||
|
||||
render(): ReactNode {
|
||||
const { restProps, memoizedByVM, children, ...rest } = this.props;
|
||||
VvmConnector.displayName = this.componentName;
|
||||
const vm = memoizedByVM ? this.vm : this.initVm;
|
||||
if (!vm) {
|
||||
const isVmKey = Object.hasOwn(this.props, "vmKey");
|
||||
const message = isVmKey
|
||||
? "vm is not defined, check your di configuration"
|
||||
: "pass correct vm";
|
||||
throw new Error(`Vm is not defined${message}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<VvmConnector
|
||||
View={this.Build}
|
||||
Vm={vm}
|
||||
memoizedByVM={typeof memoizedByVM === "undefined" ? true : memoizedByVM}
|
||||
restProps={{ ...restProps, ...rest }}
|
||||
>
|
||||
{children}
|
||||
</VvmConnector>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
||||
import { IBaseVM } from "reactvvm";
|
||||
|
||||
/**
|
||||
* To use with mvvm library to make a vm based on props so you can pass the result to the view
|
||||
|
@ -1,59 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useDI } from "@/bootstrap/di/di-context";
|
||||
import { NoOverride } from "@/bootstrap/helpers/type-helper";
|
||||
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
||||
import { useState } from "react";
|
||||
|
||||
/**
|
||||
* Base class for all viewmodels. It provides
|
||||
* - dependency injection: To get closes di which serves from di provider
|
||||
* - rerender method: to rerender your component manually
|
||||
* - produce method: to produce your vm dynamically by passing and attaching dependencies to it
|
||||
*/
|
||||
export default abstract class BaseVM<
|
||||
IVM,
|
||||
DEP extends object | undefined = undefined,
|
||||
> implements IBaseVM<IVM>
|
||||
{
|
||||
/* ------------------------------ Dependencies ------------------------------ */
|
||||
protected deps!: DEP;
|
||||
|
||||
/* -------------------------------- Abstracts ------------------------------- */
|
||||
abstract useVM(): IVM;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
produce(dep?: DEP) {
|
||||
if (dep) this.deps = dep;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/* --------------------------------- Getters -------------------------------- */
|
||||
/**
|
||||
* You can pass your rerender method after calling useRerender on your vm
|
||||
* so you can access to it in any method
|
||||
*/
|
||||
protected rerender?: () => void;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
protected get di() {
|
||||
return useDI();
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/**
|
||||
* You can use this hook in your useVm method to get rerender method
|
||||
* @returns Rerender Method that when ever you call it you can rerender your component
|
||||
* for showing new values
|
||||
*/
|
||||
protected useRerender(): NoOverride<() => void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, reState] = useState(false);
|
||||
|
||||
const rerender = () => reState((prev) => !prev);
|
||||
return rerender as NoOverride<() => void>;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* All viewmodels should implement this interface.
|
||||
*/
|
||||
export default interface IBaseVM<VM> {
|
||||
useVM(): VM;
|
||||
}
|
@ -6493,6 +6493,11 @@ react@19.0.0-rc-69d4b800-20241021:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
reactvvm@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/reactvvm/-/reactvvm-1.1.0.tgz#2cdb7ebb75ec0fed4121960077c13474536d18e1"
|
||||
integrity sha512-HBb/9SN5gAFhYGaQ5qzxmT+YZ0ML+7hfFfAH0milq8T+hJWY9seUsHUUvYxROuxg1fY+WIR04ENGB1H4HhAYJg==
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||
|
Loading…
x
Reference in New Issue
Block a user