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-viewcon

v0.1.2

Published

Easily generate React functional components with view-controller separation.

Downloads

19

Readme

react-viewcon

Easily generate React functional components with view-controller separation.

Features

  • Easy separation of UI and logic by replacing React.FC with MVC function.
  • Controllers swappable at runtime - passed as a prop.
  • Multiple views with same controller or multiple controllers with the same view.
  • Built in, configurable generator that creates directories with all necessary files for you.
  • Easy way to keep your project structure unified.

Requirements

Recomended:

  • sass - for support of .scss files generated from CLI.

Installation

npm: npm i react-viewcon
yarn: yarn add react-viewcon

Usage

Boilerplate

Minimal boilerplate consists of view and controller files.

View (my_new_component.tsx):

import * as React from 'react';
import MVC from 'react-viewcon';
import controller from './my_new_component.controller';

const MyNewComponent = MVC<{}, typeof controller>(controller,
    (p, c) => (
        <div>
            {p.children}
        </div>
    )
);

export default MyNewComponent;

Controller (my_new_component.controller.ts):

import {PropsWithChildren} from 'react';

export default (p: PropsWithChildren<{}>) => {
    return {

    };
};

Similar boilerplate, including scss file and separate props file can be generated using CLI.

Example

For simplicity this example demonstrates a simple button, that changes color upon clicking. The whole approach shows its true strength in complex components.

my_new_component.tsx:

import * as React from 'react';
import MVC from 'react-viewcon';

import {C, $C} from './my_new_component.controller';
import MyNewComponentProps from './my_new_component.props';
import './my_new_component.scss';

const MyNewComponent = MVC<MyNewComponentProps, $C>(C,
        (p, c) => (
                <div className='my-new-component'>
                  <button onClick={c.handleClick}
                          style={{backgroundColor: c.isOn ? 'green' : 'red'}}>
                    {p.buttonName}
                  </button>
                </div>
        )
);

export default MyNewComponent;

my_new_component.controller.ts:

import MyNewComponentProps from './my_new_component.props';
import {PropsWithChildren, useState} from 'react';

export const C = (p: PropsWithChildren<MyNewComponentProps>) => {

    const [isOn, setIsOn] = useState<boolean>(p.initialState);

    return {
        isOn,
        handleClick: () => setIsOn(!isOn)
    };
};

export type $C = typeof C;

my_new_component.props.ts:

export default interface MyNewComponentProps {
    buttonName: string;
    initialState: boolean;
}

Usage:

<MyNewComponent
    buttonName='my button'
    initialState={false}
/>

Now if we want to create another button - that for example always turn red after two seconds - all we have to do is swap controller. my_new_component.controller.timed.ts

import MyNewComponentProps from './my_new_component.props';
import {PropsWithChildren, useEffect, useState} from 'react';

const useTimedController = (p: PropsWithChildren<MyNewComponentProps>) => {

    const [isOn, setIsOn] = useState<boolean>(p.initialState);
    useEffect(() => {
       if (!isOn) return;
       const timeout = setTimeout(() => setIsOn(false), 2000);
       return () => clearTimeout(timeout);
    }, [isOn]);

    return {
        isOn,
        handleClick: () => setIsOn(!isOn)
    };
};

export default useTimedController;

Then all we need to make our button timed is to pass a controller prop:

<MyNewComponent
    buttonName='my button'
    initialState={false}
    controller={useTimedController}
/>

This can also work in other direction. For example we can write two views - for React and React Native, and then use only one controller for logic.

MVC function

The function takes two type variables:

  • P - props,
  • $C - type of controller that extends (p: PropsWithChildren<P>) => ReturnType<$C>.

And two parameters:

  • defaultController - default controller of type $C to use when controller prop is not passed,
  • view - the actual view function that takes p (props) and c (object returned by controller) and returns a ReactComponent.

The return value is a functional component, or to be exact: FC<P & ControlledProps<$C>>

To wrap up:

function MVC<P,$C>(
  defaultController: $C, 
  view: (p: PropsWithChildren<P>, c: ReturnType<$C>) => ReactElement<any, any>
): FC<P & ControlledProps<$C>>

Don't worry, you won't ever have to think about it.

CLI

react-viewcon uses generate-template-files package in order to generate viewcon boilerplate for you. To open generator prompt run: viewcon.

First select location using arrow keys and confirm using enter. Available locations can be configured.

? What do you want to generate? … 
Source root
Some other location

The prompt will ask you to insert component name - don't worry about the case, it will be handled automatically.

✔ What do you want to generate? · Some other location
? Insert component name › my new component 

Confirm or modify generated path:

? Output path: › ./src/example/my_new_component

Generated files:

 src
  ├── example
  │   └── my_new_component
  │       ├── my_new_component.controller.ts
  │       ├── my_new_component.props.ts
  │       ├── my_new_component.scss
  │       └── my_new_component.tsx

Configuration

Generator behavior can be modified using .viewconrc configuration file placed in project root directory. Configuration can be in json or ini format.

Configurable fields include:

  • directories - array of objects consisting of:
    • name - name displayed in the generator prompt,
    • path - actual path in which generator should create component.
  • filenameCase - case of component directory and filenames.
  • fileCase - case of .ts and .tsx files.

Available values of filenameCase and fileCase are:

  • 'noCase'
  • 'camelCase'
  • 'constantCase'
  • 'dotCase'
  • 'kebabCase'
  • 'lowerCase'
  • 'pascalCase'
  • 'pathCase'
  • 'sentenceCase'
  • 'snakeCase'
  • 'titleCase'

Example configuration (json format):

{
    "directories": [
        {
            "name": "Source root",
            "path": "./src"
        },
        {
            "name": "Some other location",
            "path": "./src/example"
        }
    ],
    "filenameCase": "camelCase",
    "fileCase": "snakeCase"
}

Default values:

{
    "directories": [{"name": "root", "path": "."}],
    "filenameCase": "snakeCase",
    "fileCase": "pascalCase"
}