npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-mvvm-hooks

v0.2.4

Published

This package is a **toolset** that will help you to use `MVVM` with `React hooks`

Downloads

4

Readme

Getting Started

This package is a toolset that will help you to use MVVM with React hooks

The pattern

In this project we are using a pattern based on MVVM + Factory.

The base

The structure is divided in 4 layers: Factory > View > ViewModel > Model

Model

This is where the data begins, where the data comes from. In this case we are using UseCases to reach the data. It can be through API requests, cache, localstorage, etc

ViewModel

This is our business logic and state is, it owns the Model. All the handlers and states have to be here. So if you need to access some data using a UseCase, it must be here.

View

The View is our layout, it's the visual layer of the application. It owns the ViewModel and it CAN'T have any logic. This is important. All the the logic must be in the ViewModel, the View will watch the ViewModel and react accordingly.

Factory

The Factory is used to create a instance of the View and connects it to the ViewModel. To do that you need to inject the ViewElement, viewModelFactory and args into the ViewFactoryComponent as props.

Folder Structure

To keep things organized you must follow some kinda pattern for you view folder. The suggestion is this:

src
|--components // shared components, not views
|--view
|   |--Subscription
|   |   |--components // components that will be used only for this View
|   |   |   |--Input
|   |   |   |   |--index.tsx
|   |   |   |   |--[...]
|   |   |--view.tsx
|   |   |--viewModel.ts
|   |   |--types.ts
|   |   |--index.ts // this one is only necessary if you want to create the FactoryComponent
|   |   |--[...]

How to use it

You can follow this example in the examples/injecting-views in this repository.

The first step is Create a View inside the view folder. Let's call it HomePage (Folder: view > HomePage).

Attention to the typing, this is very important.

1. types.ts file

This file is used only to separate the types/interfaces from the actual View, so create the types.ts inside the HomePage folder:

import { View } from 'react-mvvm-hooks';

export interface HomePageState extends View.State {
    count: number;
    handleClickCountButton(): void;
}

2. viewModel.ts

The ViewModel is where the business logic is, and it's very important to keep all the heavy code inside.

import { useCallback, useState } from 'react';
import { ViewModel } from 'react-mvvm-hooks';
import { InnerButtonView } from '../InnerButton/view';
import { useInnerButtonViewModel } from '../InnerButton/viewModel';
import { HomePageState } from './types';

export const useHomePageViewModel: ViewModel.Hook<HomePageState> = () => {
    const [count, setCount] = useState(0);

    const handleClickCountButton = useCallback(() => {
        setCount(count + 1);
    }, [setCount, count]);

    return {
        count,
        handleClickCountButton,
    };
};

The ViewModel is a hook, so remember always to prefix it with use. You can set all the states here. Try to avoid this file to be too big, if necessary create another hook insider of the HomePage folder to help abstracting some logic.

view.tsx

This is where our layout goes. Simple as that, only layout stuff, no logic. Get the state and handlers from props and use it.

import { View } from 'react-mvvm-hooks';
import { HomePageState } from './types';

export const HomePageView: View.Component<HomePageState> = ({
    count,
    handleClickCountButton,
}) => {
    return (
        <div
            style={{
                height: '100vh',
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
            }}
        >
            <div>
                <button onClick={handleClickCountButton}>Count: {count}</button>
            </div>
        </div>
    );
};

Connecting with ViewFactory (src/main.tsx)

Then we have it! Our View is ready, now let's connect it to the ViewModel.

import React from 'react';
import ReactDOM from 'react-dom/client';
import { HomePageView } from './view/HomePage/view';
import { useHomePageViewModel } from './view/HomePage/viewModel';
import { FactoryHelpers } from 'react-mvvm-hooks';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
    <React.StrictMode>
        <FactoryHelpers.ViewFactory
            ViewElement={HomePageView}
            useViewModel={useHomePageViewModel}
        />
    </React.StrictMode>,
);

We use the FactoryHelpers.ViewFactory that is a generic component to connect them.

Custom ViewFactory (/HomePage/index.tsx)

If you want to create your Custom ViewFactory, it's also possible.

import { FactoryHelpers } from 'react-mvvm-hooks';
import { HomePageState } from './types';
import { useHomePageViewModel } from './viewModel';
import { HomePageView } from './view';

export const HomePageViewFactory: React.FC = () => {
    const props = useHomePageViewModel();

    return <HomePageView {...props} />;
};

The FactoryHelpers also has a ViewFactory creator: FactoryHelpers.create<>(). The only thing you need to do is type it:

import { FactoryHelpers } from 'react-mvvm-hooks';
import { HomePageState } from './types';

export const HomePageViewFactory = FactoryHelpers.create<HomePageState>();

Injecting a View inside another View

Sometimes you need to add more functionalities and may want to split in different views. For instance toolbar that is going to be displayed in some pages with different logic inside it.

For this example we're going to keep things simple: Add a seconde Counter inside the first View

Create your new View in our case InnerButton

Inside the view/InnerButton it has the same structure as the other one, so it goes:

/InnerButton/types.ts

import { View } from 'react-mvvm-hooks';

export interface InnerButtonState extends View.State {
    count: number;
    handleClickCountButton(): void;
}

export interface InnerButtonArgs extends View.Args {
    initialCount: number;
}

Now we added the Args. It's needed when you want to inject something in the ViewModel hook.

/InnerButton/viewModel.ts

import { useCallback, useState } from 'react';
import { ViewModel } from 'react-mvvm-hooks';
import { InnerButtonArgs, InnerButtonState } from './types';

export const useInnerButtonViewModel: ViewModel.Hook<
    InnerButtonState,
    InnerButtonArgs
> = ({ initialCount }) => {
    const [count, setCount] = useState(initialCount);

    const handleClickCountButton = useCallback(() => {
        setCount(count + 1);
    }, [setCount, count]);

    return {
        count,
        handleClickCountButton,
    };
};

Pay attention to the type generics, now we have the type ViewModel.Hook with 2 generics: State and Hooks

/InnerButton/view.tsx

import { View } from 'react-mvvm-hooks';
import { InnerButtonState } from './types';

export const InnerButtonView: View.Component<InnerButtonState> = ({
    count,
    handleClickCountButton,
}) => {
    return (
        <button onClick={handleClickCountButton}>Inner Count: {count}</button>
    );
};

Connecing one View to another

To do that we need to go back to the HomePage view and modify somethings:

/HomePage/type.ts

import { View } from 'react-mvvm-hooks';
import { InnerButtonArgs, InnerButtonState } from '../InnerButton/types';

export interface HomePageState extends View.State {
    count: number;
    handleClickCountButton(): void;

    innerButton: View.Reference<InnerButtonState, InnerButtonArgs>;
}

We used the interface View.Reference so the state returned from homePageViewModel knows what to return to the view.

/HomePage/viewModel.ts

import { useCallback, useState } from 'react';
import { ViewModel } from 'react-mvvm-hooks';
import { InnerButtonView } from '../InnerButton/view';
import { useInnerButtonViewModel } from '../InnerButton/viewModel';
import { HomePageState } from './types';

export const useHomePageViewModel: ViewModel.Hook<HomePageState> = () => {
    const [count, setCount] = useState(0);

    const handleClickCountButton = useCallback(() => {
        setCount(count + 1);
    }, [setCount, count]);

    return {
        count,
        handleClickCountButton,
        innerButton: {
            args: { initialCount: 10 },
            ViewElement: InnerButtonView,
            useViewModel: useInnerButtonViewModel,
        },
    };
};

We added to the return the innerButton which was typed in the type.ts file.

/HomePage/view.ts

import { FactoryHelpers, View } from 'react-mvvm-hooks';
import { InnerButtonFactory } from '../InnerButton';
import { HomePageState } from './types';

export const HomePageView: View.Component<HomePageState> = ({
    count,
    handleClickCountButton,
    innerButton,
}) => {
    return (
        <div
            style={{
                height: '100vh',
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
            }}
        >
            <div>
                <button onClick={handleClickCountButton}>Count: {count}</button>
            </div>
            <div>
                <FactoryHelpers.ViewFactory {...innerButton} />
            </div>
        </div>
    );
};

DO NOT import directly the InnerButtonView, always use FactoryHelpers.ViewFactory in this case, it's going to avoid circular dependency.