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

@shadowvzs/react-view-router

v0.8.16

Published

Router for ReactJS with MobX+Typescript, decoupled component and logic with introducing the viewStore layer between routes and component which can instantiate outside as well and can inject data for all instantiated viewStore

Downloads

6

Readme

react-view-router

Router for ReactJS with MobX, TypeScript

Why?

  • Current routers doesn't fitted to my needs, they allow to mount an JSX.Element but doesn't help to separate the logic/components, doesn't help too much in testing and always need write our own route guard if needed (which should be enough often ex.: auth),

Goal

  • more testable component, if we split the logic the components then we can test just the logic or component, not just together
  • more cleaner components (separated business logic & dummy component)
  • support both common element based route and the ViewStore way, dynamic, normal, nested paths
  • can expose/instantiate outside the router/history instance and automatically inject into the ViewStore the history
  • easy injection for ViewStore (RouterProvider), which make easier to inject auth/services/configs into the viewStore and not need hooks into components
  • support mounting multiple component/viewstore into the app with same router if needed
  • very easy way to guard the component (lifecycles should be used for that)

ViewStores

  • LifeCycles:

    methods which help to move some logic from component into ViewStore
    • beforeMount - run 1x, can please here an initial data fetch
    • beforeUpdate - called everytime if route data was changed (example: you are in detail view and id in url was changed)
    • beforeUnmount - called before the unmounting the component
  • Guards:

    method args: url data, returns Pomise, if returns false then route will be not changed)
    • canMount - can be mounted - good place for auth check/guard
    • canUpdate - can be updated - good place for discard modals for edit views
    • canUnmount - can be unmounted - good place for discard modals for edit views when leave the component

Injectable data:

// this based on project where it is use, so just an example
export type ViewStoreInjectedData = {
    globalConfig: { baseApiUrl: string; };
    serviceMap: Record<string, unknown>;
    notifyService: (type: 'error' | 'success', message: string) => void;
};

const data = {
    globalConfig: { baseApiUrl: '' },
    serviceMap: { 
        bookService: { 
            getBooks: () => {} 
        }
    },
    notifyService: (type, message) => { console.info(type, message); },
};

Provider:

 <RouterProvider<ViewStoreInjectedData> history={new BrowserHistory()} injectedData={data}>
    {/* children will be here */}
 </RouterProvider>

Routes:

    <Route path='/login' ViewStore={LoginView} Cmp={LoginCmp} />
    <Route path='/signup' ViewStore={SignUpView} Cmp={SignUpCmp} />
    <Route path='/books/:genre/:category' ViewStore={BookListView} Cmp={BookListCmp} />
    <Route path='/books/drama' exact={false} element={<div>show this if route starts with "/books/drama" (exact is false)</div>} />

Links:

  <ul>
      <li><Link to='/'>/</Link></li>
      <li><Link to='/login'>/login</Link></li>
      <li><Link to='/signup'>/signup</Link></li>
      <li><Link to='/books/drama/bestseller?top=12#2'>/pista/222</Link></li>
  </ul>

Examples

Simple view
class SignUpView extends ViewStore implements ISignUpView {
    public onSignUp = (ev: React.FormEvent<HTMLFormElement>) => {
        ev.preventDefault();
        alert('onSubmit');
        return false;
    };
}

Simple component

const SignUpCmp = (props: { store: ISignUpView }): JSX.Element => {
    const { store } = props;
    return (
        <div>
            <h4>Sign Up</h4>
            <form onSubmit={store.onSignUp}>
                <div>
                    <input placeholder='username' type='text' />
                </div>
                <div>
                    <input placeholder='email' type='text' />
                </div>
                <div>
                    <input placeholder='password' type='password' />
                </div>
                <div>
                    <input type='checkbox' /> Agree
                </div>
                <button>Submit</button>
            </form>
        </div>
    );
};

More advanced ViewStore

class BookListView extends ViewStore<ViewStoreInjectedData> implements IBookListView {

    @observable
    public books: Book[] = [];
    @action.bound
    public setBooks(books: Book[]) { this.books = books; }

    @observable
    public loading = false;
    @action.bound
    public setLoading(loading: boolean) { this.loading = loading; }

    constructor() {
        super();
        this.update = this.update.bind(this);
        makeObservable(this);
    }

    // called before the mount
    public beforeMount() { this.loadFromDatabase().catch(console.error); }

    // called at every update
    public beforeUpdate() { this.loadFromDatabase().catch(console.error); }

    // lets override the default update
    public update(urlData: IUrlData) {
        const oldStateWithoutHash = { ...this.props, hash: undefined };
        const newStateWithoutHash = { ...urlData, hash: undefined };

        // if there more difference then the hash, then we update normally
        if (JSON.stringify(oldStateWithoutHash) !== JSON.stringify(newStateWithoutHash)) {
            super.update(urlData);
        } else {
            // but only if the hash changed then we not reload the list
            this.setProps(urlData);
            this.render();
        }
    }

    private loadFromDatabase = async () => {
        this.setLoading(true);
        this.injectedData.notifyService('success', 'loading started');
        try {
            // wait for some random time
            await delayPromise(Math.random() * 1000 + 500);
            // generate some dummy datam, normally the injectData should contain the services for data loading
            const books = new Array(Math.round(Math.random() * 7) + 3).fill(1).map((_, idx) => ({
                id: String(idx + 1),
                title: `Book Nr #${idx + 1}`
            }));
            this.setBooks(books);
        } catch (error: unknown) {
            console.error(error);
        } finally {
            this.injectedData.notifyService('success', 'loading ended');
            this.setLoading(false);
        }
        return false;
    };
}

and component for the view

interface BookListCmpProps {
    store: IBookListView;
    params: { genre: string; category: string; top: string; };
    hash?: string;
}

const BookListCmp = observer((props: BookListCmpProps): JSX.Element => {
    const { store, params, hash } = props;
    const { genre, category, top } = params;

    return (
        <div>
            <h4>Book List {top && `- top(${top})`}</h4>
            <p>Genre: {genre}</p>
            <p>Category: {category}</p>
            <ul>
                {store.loading && <h4>Loading....</h4>}
                {!store.loading && store.books.map(book => (
                    <li key={book.id} style={book.id === hash ? { backgroundColor: 'cyan' } : {}}>
                        <Link to={`/books/${genre}/${category}?top=${top || 10}#${book.id}`}>{book.title}</Link>
                    </li>
                ))}
            </ul>

            <nav>
                History
                <ul>
                    <li><Link to={`/books/history/best-seller`}>Best Seller</Link></li>
                    <li><Link to={`/books/history/recommended`}>Recommended</Link></li>
                    <li><Link to={`/books/history/cheap`}>Cheap</Link></li>
                    <li><Link to={`/books/history/ForEvents`}>For Events</Link></li>
                </ul>
            </nav>
        </div >
    );
});

Credit to Igor Gaponovfor the npm guide https://betterprogramming.pub/how-to-create-and-publish-react-typescript-npm-package-with-demo-and-automated-build-80c40ec28aca#b9e9