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

mobx-react-viewmodel

v0.2.0

Published

Tiny package (~0.9 KB) which can make a huge difference for your React/Mobx app architecture.

Downloads

1,262

Readme

mobx-react-viewmodel

Tiny package (~0.9 KB) which can make a huge difference for your React/Mobx app architecture.

Simple hooks useViewModel and useViewModelFactory allow you to move all component's state and logic from the render function to a separate stateful view-model class. Which unleashes the full power of Mobx and helps you escape React Hooks Hell.

Written in TypeScript and 100% type-safe 🎯

Installation

npm install mobx-react-viewmodel --save

or

yarn add mobx-react-viewmodel

Simple Example

import { observer } from 'mobx-react'
import { useViewModel } from 'mobx-react-viewmodel'

class MyViewModel {
  @observable
  title: string = '';

  constructor() {
    makeObservable(this);
  }

  @action
  setTitle(title: string) {
    this.title = title;
  }
}

const MyComponent = observer(() => {
  const viewModel = useViewModel(MyViewModel); // MyViewModel instance will be created only once on first render 

  return (
    <div>
      <input
        type="text"
        value={viewModel.title}
        onChange={e => viewModel.setTitle(e.target.value)}
        placeholder="Type title..."
      />
      <p>Title: {viewModel.title}</p>
    </div>
  );
});

Benefits

  • View-layer (component's render function) stays clean and contains only rendering code
  • Taking full advantage of Mobx using @observable / @computed / @action / reaction / when primitives for powerful reactive programming
  • No need to use useState / useEffects / useCallback / useMemo anymore (goodbye spaghetti code, stale closure bugs and dependency list for every hook)
  • At some point, if you need to move some component's state to a global Mobx-store, you don't have to rewrite old hooks-code with Mobx. You can just cut and paste what you need from view-model class to the store class

Advanced Example

// MyPageViewModel.ts

import { action, comparer, computed, makeObservable, observable, reaction } from 'mobx';
import { User, UsersStore } from 'our/users/module'

interface MyPageViewModelProps {
  userId: string;
}

class MyPageViewModel extends ViewModel<MyPageViewModelProps> {
  @observable
  user?: User;

  constructor(props: MyPageViewModelProps, private usersStore: UsersStore) {
    super(props);
    makeObservable(this);
  }
  // is invoked immediately after a component is mounted 
  init(){
    this.disposers.push(
      reaction(
        () => this.props.userId,
        userId => this.getUserFromStore(userId),
        { fireImmediately: true }
      )
    );
  }
  // is invoked immediately before a component is unmounted and destroyed
  dispose() {
    super.dispose();
    this.user = undefined;
  }

  @action
  updateField = (field: keyof User, value: string) => {
    if (!this.user) return;
    this.user[field] = value;
  }

  @action
  save = () => {
    if (!this.user) return;
    this.usersStore.updateUser(this.user);
  }

  @computed
  isPristine() {
    const storeUser = this.usersStore.getUser(this.props.userId);
    return this.user && storeUser && comparer.shallow(this.user, storeUser);
  }

  @action
  private getUserFromStore = (userId: string) => {
    const user = this.usersStore.getUser(userId);
    if (user) {
      this.user = { ...user };
    } else {
      console.error(`Couldn't find user: ${userId}`);
    }
  }
}
// MyPage.tsx

import { observer } from 'mobx-react'
import { useViewModel } from 'mobx-react-viewmodel'
import { useParams } from 'react-router-dom';
import { useStores } from 'our/stores/path';

const MyPage = observer(() => {
  // Get the userId param from the URL.
  const { userId } = useParams();
  // Get our custom store from hook or context
  const { userStore } = useStores();
  
  const viewModel = useViewModelFactory(
    props => new MyPageViewModel(props, userStore), // Factory-function will be called only once on first render
    { userId } // These `props` are reactive. It will be passed and updated in view-model every time it changes without creating new instance of view-model 
  );
  
  //** or
  // const viewModel = useViewModel(
  //    MyPageViewModel, 
  //   { userId },
  //   [userStore] // Extra dependencies for the view-model constructor
  // );

  return (
    <div>
      <input
        type="text"
        value={viewModel.user.first_name}
        onChange={e => viewModel.updateField('first_name', e.target.value)}
        placeholder="First Name"
      />
      
      <input
        type="text"
        value={viewModel.user.last_name}
        onChange={e => viewModel.updateField('last_name', e.target.value)}
        placeholder="Last Name"
      />
      
      <button type="button" disabled={viewModel.isPristine} onClick={viewModel.save}>Save</button>
    </div>
  );
});

You can find more examples here.

API documentation

useViewModel(ViewModelClass, props?, args?)

Basic hook that creates an instance of the ViewModelClass on the first render and keeps it alive during all further renders. If props is passed, it will be set to the ViewModelClass instance props property every time it changes. If args is passed, it will be passed to the ViewModelClass constructor on the first render. You should use only permanent references to the object there, for example, a reference to singleton objects. If ViewModelClass implements init() or dispose() methods, they will be called on the component's mount and unmount events.

useViewModelFactory(factoryFn, props?)

Hook for more advanced view-model instantiation. It allows to inject extra dependencies to the view-model constructor inside the custom factoryFn. Life-cycle and props update logic is the same as with useViewModel

ViewModel

Base class with observable props property which is automatically updated by useViewModel or useViewModelFactory. Also, it has a built-in disposers array for storing all disposer-functions from reaction, when, or any other custom ones that you need to call on unmount. You can extend your custom view-model classes from this class for cutting down boilerplate code.