# react-view-controllers

> A State management library for react, based on Mobx

Latest version **1.24.0** (published 2017-11-25) · MIT license · 0 weekly downloads

> **Deprecated.** This package is deprecated.

## Install

```sh
npm install react-view-controllers
pnpm add react-view-controllers
yarn add react-view-controllers
bun add react-view-controllers
```

## Health

**Score 10/100 (F)** — status: deprecated.

Negative: deprecated.

## Facts

| | |
|---|---|
| Version | 1.24.0 |
| Published | 2017-11-25 |
| First published | 2017-11-18 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 2 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | niryosef89@gmail.com |
| Maintainers | niryo |

## Links

- npm: https://www.npmjs.com/package/react-view-controllers
- npm.io page: https://npm.io/package/react-view-controllers

## Dependencies (2)

- [mobx](https://npm.io/package/mobx.md) ^3.3.1
- [lodash](https://npm.io/package/lodash.md) ^4.17.4

## Recent versions

- 1.24.0 (latest) — 2017-11-25
- 1.23.0 — 2017-11-24
- 1.22.0 — 2017-11-24
- 1.21.0 — 2017-11-24
- 1.20.0 — 2017-11-24
- 1.19.0 — 2017-11-23
- 1.18.0 — 2017-11-23
- 1.17.0 — 2017-11-23
- 1.16.0 — 2017-11-23
- 1.15.0 — 2017-11-23
- 1.14.0 — 2017-11-23
- 1.13.0 — 2017-11-23
- 1.12.0 — 2017-11-23
- 1.11.0 — 2017-11-22
- 1.10.0 — 2017-11-21
- … 10 more at https://npm.io/package/react-view-controllers/versions

## README

# React View Controllers
A state management library for react, based on Mobx

[![npm version](https://img.shields.io/npm/v/react-view-controllers.svg)](https://www.npmjs.com/package/react-view-controllers)
## Installation
`npm install react-view-controllers --save`


## Basic usage example:
Inside `NotesListController.js`: 
```javascript
import { Controller } from 'react-view-controllers';

export class NotesListController extends Controller {
  constructor(compInstance) {
    super(compInstance);
    this.state = {
      message: 'hello' 
    };
  }
  getMessage() {
    return this.state.message;
  }
  setMessage(value) {
    this.state.message = value;
  }
}

```

Inside `NotesList.jsx`:
```javascript
import React, { Component } from 'react';
import { observer } from 'react-view-controllers';
import { NotesListController } from './NotesListController';

class NotesList extends Component {
  componentWillMount() {
    this.controller = new NotesListController(this);
  }

  render() {
    return (
      <div>
        <div>{this.controller.getMessage()}</div>
        <button onClick={() => this.controller.setMessage('hello world!')}>Click me to change message</button>
      </div>
    );
  }
}

export default observer(NotesList);

```
## Agenda:
* Data flow is unidirectional- from parent down to the children. A parent cannot fetch data from child controllers.
* Every 'smart component' should have a controller.
* A controller is a plain Javascript class and is not tightly coupled to any view.
* The controller holds a state and methods for manipulating the state. 
* The controller's lifecycle should be bounded to the component's lifecycle. when a component enters the screen, A new fresh controller will be created, and when the component is destroyed the controller will be destroyed.
* A component can get and set data from parents' controllers (not necessarily direct parents), but it cannot use data from the controllers of sibling components.
* Every controller should be explicitly exposed (provided) in order to be used by child components.
* Any changes to the controller state will be automatically reflected by the view.

## Why 
* **Zero boilerplate:** Controllers are just plain javascript classes, and All you need to do in order to make your views react to changes in the controllers, is just to wrap them with `observer` and you are good to go.

* **No need for singleton stores:** If you ever used Redux, you probably knows what happens when you forget to clean your stores when a component leave the screen- the next time it enters the screen, it fetches some old state-related data from the store and bad things happens. You may say that stores should not contain state related data, but sometimes you just need to share state across multiple components, for example, `currentSelectedItem`, `isCartFull`, `canMoveToNextStep` etc. 
`Controllers` lifecycle is binded to the component lifecycle, so you get a fresh controller out of the box whenever a component enters the screen.

* **Reusability:** Each component holds an *instance* of it's Controller (again, no singletons!), so you can create **multiple  instances of a component** (see example project). When you have a singleton store its much more cumbersome to support multiple instance of a component. 

* **Better encapsulation**: A component can fetch data only from it's direct controller and it's parents controllers. You cannot feth data from sibling component's Controllers. If you need some piece of data to be visible for two sibling components, it means that this data should sit within their first common parent. If you need a piece of data to be visible to all other component, put it in your AppController.

* **Mimics React's basic state (native componet's state):** React state is really good for dumb components, but its not so good for smart components. It cannot be esaly shared to deep nested children, it prevents separation of logic and view to different files, and it is not easly testable. React view controllers tackles exactly those points.

## How
Most of the heavy lifting is being done behind the scenes with the help of [Mobx](https://github.com/mobxjs/mobx).

### Example project
Here is a [Simple example project](https://niryo.github.io/react-view-controllers/)
You can see the source code under the example folder: [example/src](example/src)
If you want to run it locally:
After cloning the repository, nevigate to the example folder and type in your terminal:
```
npm install
npm start
```

## Api
#### `Controller(componentInstance)`
Every view should have a controller that extends `Controller`. A controller is a plain javascript class that holds an observable state. a controller should contain only a **state** and methods that manipulate the state.
Make sure to call `super(componentInstance)` from your controller constructor.
Every controller exposes `getParentController()` (See bellow for more details).
* #### state: 
Every controller has a state prop. You should initiate the state inside the controller's constructor.
The observers (React Components that you wrapped within `observer`) will react to any change in the state, even changes of deep nested properties. for example:
```javascript
changeName(){
  this.state.listOfItems[0].name = 'foo';
}
```

* #### `getParentController(controllerName: string)`:
Use this Controller method when you need to fetch data from a parent controller (not necessarily a direct parent).
The name of the parent controller is the name of the class, as returned from Class.name.
If for example your code looks like this: 
```javascript
class SomeParentController extends Controller{}
````
then the name will be 'SomeParentController' (`SomeParentController.name`).
Make sure that the parent controller is provided using `ProvideController`.
You cannot get the controller of a sibling component. If you need to use some data from a sibling component, put this data in the first common parent of the two components.

If you need to interact with a parent controller from your React component, you can do something like this:
```javascript
 componentWillMount() {
    this.controller = new SomeChild(this);
    this.parentController = this.controller.getParentController(SomeParentController.name);
  }
```
Or directly:
```javascript
 componentWillMount() {
    this.parentController = new Controller(this).getParentController(SomeParentController.name);
  }
```

#### Usage example:
```javascript
import {Controller} from 'react-view-controllers';
import {SomeParentController} from './SomeParentController';

export class AppController extends Controller {
  constructor(comp) {
    super(comp);
    this.state = {totalNotesCount: 2}; //the state should be the only property of the controller, 
                                       //and should be initialized in the constructor.
  }

  getTotalNotesCount() {
    return this.state.totalNotesCount;
  }

  increaseCounter() {
    this.state.totalNotesCount ++;
  }
  
  getSomePropFromParentController() {
  const someProp = super.getParentController(SomeParentController.name); //you can use the name of the controller as string,                                                                          //but this way is safer.
  //do something with someProp...
  }
}
```

Your React component will create an instance of the Controller inside **`componentWillMount`** like this:

```javascript
import {AppController} from 'react-view-controllers';

class App extends React.Component {
   componentWillMount() {
    this.controller = new AppController(this);
   }
}
```


#### `observer(ReactComponent)`
To become reactive, every React component that uses a controller should be wrapped within `observer`. 

```javascript
import {observer} from 'react-view-controllers';

class SomeSmartComponent extends React.Component {
...
}

export default observer(SomeSmartComponent)
```


#### `<ProvideController controller={controllerInstance}/>`:
If you want your controller instance to be visible to your child components, you must explicitly provide it using ProvideController.

```javascript
import * as React from 'react';
import SomeParentComponentController from './SomeParentComponentController';
import { observer, ProvideController } from 'react-view-controllers';

class SomeParentComponent extends React.Component {
  componentWillMount() {
    this.controller = new SomeParentComponentController(this);
  }

  render() {
    return (
        <ProvideController controller={this.controller}>
           <SomeChild>
           <AnotherChild>
        </ProvideController>
    );
  }
}

```

In the above example, SomeChild and AnotherChild could make use of `SomeParentComponentController` using `getParentController()`.


## Testing Api
#### `TestUtils.init()`:
You must call this method before using any other test utils.

#### `TestUtils.clean()`:
You must call this method after each test (if you used TestUtils.init());

```javascript
import {TestUtils} from 'react-view-controllers';

beforeEach(() => {
   TestUtils.init();
 });
 
 afterEach(() => {
   TestUtils.clean();
 });

```
#### `getControllerOf(componentInstance)`:
use this method to extract component's controller for testing.

#### `mockStateOf(controllerInstance: controller, state: object)`:
use this method when you need to mock a state of a controller.

```javascript
  it('some Test', () => {
    TestUtils.init();
    const component = mount(<Test />);
    const controller = TestUtils.getControllerOf(component.instance());
    TestUtils.mockStateOf(controller,{ name: 'mockedName' });
    expect(component.find('[data-hook="name"]').text()).toEqual('mockedName');
  });
```

#### `mockParentOf(controllerName: string, ParentController: Controller, parentState?: object)`:
Use this method if you need to mock parent controllers of component.
* **controllerName**: The name of the controller which his parent needs to be mocked
* **ParentController** The parent controller class that will be mocked.
* **parentState** The state of the mocekd parent.

```javascript
  beforeEach(() => {
    TestUtils.init();
    TestUtils.mockParentOf(NotesListController.name, AppController);    
  });

```

---
_Source: https://npm.io/package/react-view-controllers · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
