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
withMVC
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
- [email protected] (should work on other versions, not tested),
- [email protected] or newer.
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 whencontroller
prop is not passed,view
- the actual view function that takesp
(props) andc
(object returned by controller) and returns aReactComponent
.
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"
}