Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tzilist/Sunfish


https://github.com/tzilist/Sunfish

Last synced: 25 days ago
JSON representation

Awesome Lists containing this project

README

        

# Sunfish

Sunfish is a functional transaction based state management library. Updating React can be expensive. Updates to state only happen when the developer deems it necessary.

### Getting started
run either
```bash
npm install sunfish
```
or
```bash
yarn add sunfish
```

### API

Sunfish has a fairly intuitive, functionaly driven api. The basic usage is as such

```js
// While in your react component after connecting (see below for example)
const { createTransaction } = this.props;

createTransaction()
.pipe(this.props.someAction)
.pipe(this.someComponentFunction)
.pipe(this.props.someOtherAction)
.update();
```

Sunfish creates a transaction and does not update the state until the transaction is told to do so. You can also update in the midst of a group of actions as such

```js
const { createTransaction } = this.props;

createTransaction()
.pipe(this.someComponentFunction)
.pipeAndUpdate(this.props.someAction)
.pipe(this.props.someOtherAction)
.update();
```

It is important to call the update function at the end of your group of function calls as this is what will ultimately set the new state and remove the transaction from Sunfish's state management.

### Functions

#### createTransaction

This function instantiates a new transaction in the state manager. It takes no params and is the first thing that must be called when starting a new chain of functions.

#### pipe
```
pipe(function action(state, context), function conditional (state, context))
```

The pipe function takes in an action callback and a conditional callback. If passed a conditional callback, the pipe function will check whether the callback returns true or false. If it returns false, the action will not be run.

The actions supplied to pipe can return several things. The most notible is that they may return any of the following keys in their return object

```
{
state,
break,
context,
}
```

If nothing is returned, the previous `state` and `context` are preserved from other actions preformed in the transaction. This can be useful when work needs to be preformed in the view layer without updating the transaction/state.

When `break` is supplied, this tells Sunfish to skip any subsequent steps (except for the `update` function).

When `context` is supplied, data is stored within the transaction. This allows the developer to easily pass data from one call to the next without needing to set it in state explicitly.

#### pipeAndUpdate

Tells Sunfish to merge the current transaction into the current state but allows the developer to continue passing information (such as context) along.

#### update

The final function call, updates the current transaction into state and delete the transaction from memory. Must be called during the final step

Here is a quick example:

```js
class User extends React.PureComponent {
fetchUserData = async () => {
const data = await fetch('/api/userData');
// only context is passed here
// Sunfish will not update the current state, but will only update the context
// in the current transaction
return { context: { data }};
}

setUserDataFetchPending = (state) => {
// The return here does not have state, context, or break key
// Sunfish assumes the return is the new state
return {
state: {
...state,
userFetchPending: true,
}
}
}

checkForFetchError = (state, { data }) => data.status !== 200;

setUserDataFetchError = (state) => {
// This function will tell Sunfish to skip any subsequent steps
// It is important to functions that return a `break` statement need
// a conditional on them to ensure they aren't run if they aren't needed
return {
state: {
...state,
userError: true,
userErrorMessage: 'failed to load data',
userFetchPending: false,
},
break: true,
}
}

setUserDataFetchSuccess = (state, { data }) => {
const results = data.json();
return {
state: {
...state,
user: results.user
userFetchPending: false,
},
}
}

componentDidMount() {
const { createTransaction } = this.props;

createTransaction()
.pipeAndUpdate(this.setUserDataFetchPending)
.pipe(this.fetchUserData)
.pipe(this.setUserDataFetchError, this.checkForFetchError)
.pipe(this.setUserDataFetchSuccess)
.update()
}

render() {
// jsx goes here
}
}
```

### Examples

First, we need to initialize our state.
```js
// state.js
import { initState } from 'sunfish';

const INITIAL_STATE = {
counter: 0,
};

export default initState(INITIAL_STATE);

```

Next, let's connect our state to our app

```js
// app.js
import React from 'react';
import ReactDOM from 'react-dom';

import { Provider } from 'sunfish';

import Counter from './components/Counter';
import state from './state';

const rootEl = document.getElementById('root');

const render = () => ReactDOM.render(


,
rootEl,
);

render();
```

Now we need to create our actions
```js
// actions/counter.js
export const incrementCounter = ({ counter }) => ({ counter: counter + 1 });

export const decrementCounter = ({ counter }) => ({ counter: counter - 1 });

// here we can return an object containering the state and break keys
// By doing so, all subsequent actions will be ignored
export const incrementCounterWithBreak = ({ counter }) => (
{
state: { counter: counter + 1 },
break: true,
}
);

// here we can also return context, which will be passed to subsequent pipe functions
export const fetchData = async (state) => {
const data = await fetch('http://mysite.com/api')
.then(res => res.json);

return { state, context: data };
}

```

```js
// components/Counter.js

import { connect } from 'sunfish';
import * as CounterActions from '../actions/counter';

const mapStateToProps = (state) => {
const { counter } = state;

return { counter };
};

const mapActionsToProps = () => ({ counterActions: CounterActions });

class Counter extends PureComponent {
static propTypes = {
createTransaction: func.isRequired,
counterActions: shape({
incrementCounter: func.isRequired,
decrementCounter: func.isRequired,
}).isRequired,
counter: number.isRequired,
}

onIncreaseHandler = () => {
const { createTransaction, counterActions } = this.props;

// this transaction has extra actions on purpose to illustrate when actions
// will and will not be run
createTransaction()
.pipe(counterActions.incrementCounter)
// here is an example with a conditional function that will not run since it returns false
.pipe(counterActions.incrementCounter, (state) => {
console.log('here!')
return false;
})
.pipe(counterActions.decrementCounter)
.pipe(counterActions.incrementCounterWithBreak)
// this function will never be run since we return a break statement above
.pipe(counterActions.decrementCounter)
.update();
}

onDecreaseHandler = () => {
const { createTransaction, counterActions } = this.props;

createTransaction()
.pipe(counterActions.decrementCounter)
.update();
}

render() {
return (

+
{this.props.counter}
-

);
}
}

export default connect(mapStateToProps, mapActionsToProps)(Counter);

```