GithubHelp home page GithubHelp logo

developit / unistore Goto Github PK

View Code? Open in Web Editor NEW
2.9K 41.0 139.0 172 KB

šŸŒ¶ 350b / 650b state container with component actions for Preact & React

Home Page: https://npm.im/unistore

JavaScript 100.00%
preact redux state subscription architecture unistore

unistore's Introduction

unistore
npm travis

unistore

A tiny 350b centralized state container with component bindings for Preact & React.

  • Small footprint complements Preact nicely (unistore + unistore/preact is ~650b)
  • Familiar names and ideas from Redux-like libraries
  • Useful data selectors to extract properties from state
  • Portable actions can be moved into a common place and imported
  • Functional actions are just reducers
  • NEW: seamlessly run Unistore in a worker via Stockroom

Table of Contents

Install

This project uses node and npm. Go check them out if you don't have them locally installed.

npm install --save unistore

Then with a module bundler like webpack or rollup, use as you would anything else:

// The store:
import createStore from 'unistore'

// Preact integration
import { Provider, connect } from 'unistore/preact'

// React integration
import { Provider, connect } from 'unistore/react'

Alternatively, you can import the "full" build for each, which includes both createStore and the integration for your library of choice:

import { createStore, Provider, connect } from 'unistore/full/preact'

The UMD build is also available on unpkg:

<!-- just unistore(): -->
<script src="https://unpkg.com/unistore/dist/unistore.umd.js"></script>
<!-- for preact -->
<script src="https://unpkg.com/unistore/full/preact.umd.js"></script>
<!-- for react -->
<script src="https://unpkg.com/unistore/full/react.umd.js"></script>

You can find the library on window.unistore.

Usage

import createStore from 'unistore'
import { Provider, connect } from 'unistore/preact'

let store = createStore({ count: 0, stuff: [] })

let actions = {
  // Actions can just return a state update:
  increment(state) {
    // The returned object will be merged into the current state
    return { count: state.count+1 }
  },

  // The above example as an Arrow Function:
  increment2: ({ count }) => ({ count: count+1 }),

  // Actions receive current state as first parameter and any other params next
  // See the "Increment by 10"-button below
  incrementBy: ({ count }, incrementAmount) => {
    return { count: count+incrementAmount }
  },
}

// If actions is a function, it gets passed the store:
let actionFunctions = store => ({
  // Async actions can be pure async/promise functions:
  async getStuff(state) {
    const res = await fetch('/foo.json')
    return { stuff: await res.json() }
  },

  // ... or just actions that call store.setState() later:
  clearOutStuff(state) {
    setTimeout(() => {
      store.setState({ stuff: [] }) // clear 'stuff' after 1 second
    }, 1000)
  }

  // Remember that the state passed to the action function could be stale after
  // doing async work, so use getState() instead:
  async incrementAfterStuff(state) {
    const res = await fetch('foo.json')
    const resJson = await res.json()
    // the variable 'state' above could now be old,
    // better get a new one from the store
    const upToDateState = store.getState()

    return {
      stuff: resJson,
      count: upToDateState.count + resJson.length,
    }
  }
})

// Connecting a react/preact component to get current state and to bind actions
const App1 = connect('count', actions)(
  ({ count, increment, incrementBy }) => (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={() => incrementBy(10)}>Increment by 10</button>
    </div>
  )
)

// First argument to connect can also be a string, array or function while
// second argument can be an object or a function. Here we pass an array and
// a function.
const App2 = connect(['count', 'stuff'], actionFunctions)(
  ({ count, stuff, getStuff, clearOutStuff, incrementAfterStuff }) => (
    <div>
      <p>Count: {count}</p>
      <p>Stuff:
        <ul>{stuff.map(s => (
         <li>{s.name}</li>
        ))}</ul>
      </p>
      <button onClick={getStuff}>Get some stuff!</button>
      <button onClick={clearOutStuff}>Remove all stuff!</button>
      <button onClick={incrementAfterStuff}>Get and count stuff!</button>
    </div>
  )
)

export const getApp1 = () => (
  <Provider store={store}>
    <App1 />
  </Provider>
)

export const getApp2 = () => (
  <Provider store={store}>
    <App2 />
  </Provider>
)

Debug

Make sure to have Redux devtools extension previously installed.

import createStore from 'unistore'
import devtools    from 'unistore/devtools'

let initialState = { count: 0 };
let store = process.env.NODE_ENV === 'production' ?  createStore(initialState) : devtools(createStore(initialState));

// ...

Examples

README Example on CodeSandbox

API

createStore

Creates a new store, which is a tiny evented state container.

Parameters

  • state Object Optional initial state (optional, default {})

Examples

let store = createStore();
store.subscribe( state => console.log(state) );
store.setState({ a: 'b' });   // logs { a: 'b' }
store.setState({ c: 'd' });   // logs { a: 'b', c: 'd' }

Returns store

store

An observable state container, returned from createStore

action

Create a bound copy of the given action function. The bound returned function invokes action() and persists the result back to the store. If the return value of action is a Promise, the resolved value will be used as state.

Parameters

  • action Function An action of the form action(state, ...args) -> stateUpdate

Returns Function boundAction()

setState

Apply a partial state object to the current state, invoking registered listeners.

Parameters

  • update Object An object with properties to be merged into state
  • overwrite Boolean If true, update will replace state instead of being merged into it (optional, default false)
subscribe

Register a listener function to be called whenever state is changed. Returns an unsubscribe() function.

Parameters

  • listener Function A function to call when state changes. Gets passed the new state.

Returns Function unsubscribe()

unsubscribe

Remove a previously-registered listener function.

Parameters

  • listener Function The callback previously passed to subscribe() that should be removed.
getState

Retrieve the current state object.

Returns Object state

connect

Wire a component up to the store. Passes state as props, re-renders on change.

Parameters

  • mapStateToProps (Function | Array | String) A function mapping of store state to prop values, or an array/CSV of properties to map.
  • actions (Function | Object)? Action functions (pure state mappings), or a factory returning them. Every action function gets current state as the first parameter and any other params next

Examples

const Foo = connect('foo,bar')( ({ foo, bar }) => <div /> )
const actions = { someAction }
const Foo = connect('foo,bar', actions)( ({ foo, bar, someAction }) => <div /> )

Returns Component ConnectedComponent

Provider

Extends Component

Provider exposes a store (passed as props.store) into context.

Generally, an entire application is wrapped in a single <Provider> at the root.

Parameters

  • props Object
    • props.store Store A {Store} instance to expose via context.

Reporting Issues

Found a problem? Want a new feature? First of all, see if your issue or idea has already been reported. If not, just open a new clear and descriptive issue.

License

MIT License Ā© Jason Miller

unistore's People

Contributors

38elements avatar cfenzo avatar cj avatar dan-lee avatar danielrosenwasser avatar developit avatar eduardoborges avatar farskid avatar goblindegook avatar heldr avatar iamclaytonray avatar jakearchibald avatar jaredpalmer avatar joseluisq avatar kadmil avatar kokjinsam avatar marvinhagemeister avatar mathiasbynens avatar metonym avatar namankheterpal avatar notpushkin avatar ouzhenkun avatar pmkroeker avatar smably avatar solarliner avatar srph avatar tbroadley avatar timer avatar vincentaudebert avatar wuweiweiwu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unistore's Issues

cdnjs hosting

Hi

We would like to host you lib on cdnjs, but we have a question about files to host
Could you please help us to answer it?
Thanks

Preact components fail to update when loading Preact as UMD external dep and Unistore as a part of the bundle

First off, thanks, your stuff kicks some serious ass! šŸ‘šŸ»

Now to the issue -

I've been using Preact lately to build all sorts of widgets, they're not parts of the same app, and completely unrelated to each other, however, several widgets can be loaded on the same page. To avoid including Preact in each widget's bundle I opted to use UMD build from unpkg. The approach worked great until yesterday, when I tried to implement Unistore. I loaded react as a UMD module, and bundled Unistore with the app.
Preact component fails to get updated when Unistore updates state. Here's sample code, I removed all parts of actual app to make sure it doesn't work because I've done something stupid. I couldn't reproduce on CodeSandbox, it errors out with DependencyNotFoundError - https://codesandbox.io/s/qzn378wx06

The build happens via rollup.

The issue doesn't arise if both Preact/Unistore are loaded as UMD or bundled.

Here's the screencast: http://recordit.co/OShYYc3DSm

And here's the code:
main.js

let { h, render, Component } = preact;
import createStore from 'unistore'
import { Provider, connect } from 'unistore/preact'

const initialState = {
  dateFilterExpanded: false,
}

export const store = createStore(initialState)

let actions = store => ({
  toggleCb: ({dateFilterExpanded}, event) => { console.log(dateFilterExpanded, event); return { dateFilterExpanded: ! dateFilterExpanded } },
  changeCb: (state, event) => { console.log(event); store.setState({ [event.target.name]: event.target.value }) }
});

@connect(Object.keys(initialState), actions)
export class App extends Component {
  render({dateFilterExpanded, changeCb, toggleCb}, state) {
    return <a onClick={toggleCb}>I will not change <strong>{ `dateFilterExpanded: ${dateFilterExpanded}`}</strong></a>
  }
}

const initWidget = (selector = 'body', props = {}) => {
	render( <Provider store={store}><App /></Provider>, document.querySelector( selector ) )
}

initWidget('#attribution-totals-widget')

index.html

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <title></title>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div id="attribution-totals-widget" class="attribution-totals-widget"></div>
    <script src="https://unpkg.com/[email protected]/dist/preact.min.js"></script>
    <script src="bundle.js"></script>
  </body>
</html>

Access action return value?

For example if an action returns a promise, I'd like to chain on to it after calling it.

const actions = store => ({
  addItem(state, item) {
    return new Promise(resolve => {
      setTimeout(() => {
        resolve({ items: [...state.items, item] })
      }, 500)
    })
  }
})

// elsewhere in component:
this.props.addItem('foo')
  // .then(() => console.log('done'))

Demo:
https://codesandbox.io/s/j43mkl31k3

microbundle should be in devDependencies

You have microbundle listed in dependencies, but shouldn't this be in devDependencies? Or am I missing something?

  "dependencies": {
    "microbundle": "^0.2.3"
  }

combineReducers

Hello! Tell me, please, how i can join reducers, like "combineReducers" in Redux?

selective subscribe and state await

I would like to suggest adding these. They are very common use cases and for each and every redux-like lib i use i usually end up having to write it again. But these aren't edge-cases. To get a ping when a certain slice of state changes is something one would use all the time. Basically, if connect can do it, why can't the user?

The other use case would be being able to await state without having to collect and manage unsubscribes.

function subscribe(selector, callback) {
    let state = selector(store.getState())
    return store.subscribe(() => {
        let selected = selector(store.getState()),
            old = state
        if (typeof selected === 'object' && !Array.isArray(selected)) {
            selected = Object.entries(selected).reduce(
                (acc, [key, value]) =>
                    (state[key] !== value ? { ...acc, [key]: value } : acc),
                state,
            )
        }
        if (state !== selected) callback((state = selected), old)
    })
}

function awaitState(selector, condition) {
    return new Promise(resolve => {
        const unsub = subscribe(selector, props => {
            const result = condition(props)
            if (result) unsub() || resolve(result)
        })
    })
}


// Example #1: await a truthy slice of state
const worker = await awaitState(state => 
    state.collections.workers, workers => workers.find(ID))

// Example #2: responding to state changes
const unsub = subscribe(state => 
    state.workers[ID].alive, () => console.log("state changed"))

// Example #3: responding to mapped state changes
const unsub = subscribe(state => 
    ({ alive: state.workers[ID].alive, running: state.globals.running }),
    ({ alive, running }) => console.log(alive, running)
)

Idea: separate core from bindings/framework implementations?

While tinkering with a render-props implementation, I separated the preact bindings (connect) from the unistore core, making unistore itself not dependent on preact (or any other framework).

...
import { createStore } from "unistore";
import { Provider, connect, Unistore } from "unistore/preact"; //  or similar

let store = createStore({ count: 0 });
...

Example: https://codesandbox.io/s/4z712kp769

How do this line up with your vision for unistore, @developit ?
Is something like that worth considering?

connect to multiple properties

This is probably me not knowing enough about state managing - if so please provide link to where I can educate myself :)
From readme:

let store = createStore({ count: 0 })
const App = connect('count', actions)(

But how do I do this: (it doesn't work)

let store = createStore({ count: 0, test: 2 })
const App = connect('count','test', actions)(

Nested objects in store

I saw the assign function in the store codebase and it's just cloning with one level. Does it mean that unistore isn't supporting store objects with nested properties?
For example:

const store = createStore({
  headerItems: {
    list: []
  }
})

Use `window.__REDUX_DEVTOOLS_EXTENSION__` instead of `window.devToolsExtension`

I was stealing getting inspiration from some of the code in devtools.js to write the devtools integration for derpy. Thanks a lot for the inspiration šŸ™

From what I read, newer versions of the extension use window.__REDUX_DEVTOOLS_EXTENSION__ instead of window.devToolsExtension. Source:

Note that starting from v2.7, window.devToolsExtension was renamed to window.__REDUX_DEVTOOLS_EXTENSION__ / window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__.

Perhaps I'm missing something - does window.devToolsExtension still work, or should unistore migrate to window.__REDUX_DEVTOOLS_EXTENSION__ as well?

Resolving cjs files issues

unistore/preact always resolves to a cjs file
and I think that unistore/full/preact resolves to a cjs file too, there are .es.js files in this directory, but Im not aware if any tool picks that up automatically over regular .js files

The issue can be fixed by introducing "proxy" directories like this. Ideally such directories could be outputted by microbundle.

Typescript help

Thanks for creating this new lib! Tiny and practical! I can already see myself using this in future projects. Currently I'm trying to write TS definitions to test it out. Here's what I have so far:

declare module "unistore" {
  import * as Preact from "preact";

  export type Listener<T> = (state: T) => void;

  export interface Store<T> {
    setState(update: object, overwrite: object): void;
    subscribe(f: Listener<T>): void;
    unsubscribe(f: Listener<T>): void;
    getState(): T;
  }

  export function createStore<T>(state: T): Store<T>;

  export function connect<T, S, I>(
    mapStateToProps: string,
    actions?: Function | object
  ): (Child: Preact.AnyComponent<T & I, S>) => Preact.Component<T, S>;

  ## TODO: connect overload

  export interface ProviderProps<T> {
    store: Store<T>;
  }

  export class Provider<T> extends Preact.Component<ProviderProps<T>, {}> {
    render(props: ProviderProps<T>, {}): Preact.VNode;
  }
}

But whenever I try to use connect, it always throws:

Argument of type 'typeof App' is not assignable to parameter of type 'AnyComponent<{ count: number; }, {}>'.
  Type 'typeof App' is not assignable to type 'ComponentConstructor<{ count: number; }, {}>'.
    Type 'App' is not assignable to type 'Component<{ count: number; }, {}>'.
      Types of property 'props' are incompatible.
        Type '{ count: number; } & ComponentProps<App>' is not assignable to type '{ count: number; } & ComponentProps<Component<{ count: number; }, {}>>'.
          Type '{ count: number; } & ComponentProps<App>' is not assignable to type 'ComponentProps<Component<{ count: number; }, {}>>'.
            Types of property 'ref' are incompatible.
              Type '((el: App) => void) | undefined' is not assignable to type '((el: Component<{ count: number; }, {}>) => void) | undefined'.
                Type '(el: App) => void' is not assignable to type '((el: Component<{ count: number; }, {}>) => void) | undefined'.
                  Type '(el: App) => void' is not assignable to type '(el: Component<{ count: number; }, {}>) => void'.
                    Types of parameters 'el' and 'el' are incompatible.
                      Type 'Component<{ count: number; }, {}>' is not assignable to type 'App'.
                        Types of property 'render' are incompatible.
                          Type '(props?: ({ count: number; } & ComponentProps<Component<{ count: number; }, {}>>) | undefined, st...' is not assignable to type '() => Element'.
                            Type 'Element | null' is not assignable to type 'Element'.
                              Type 'null' is not assignable to type 'Element'.

usage:

class App extends Component<{ count: number }, {}> {
  render() {
    return (
      <div>
        <h1>Hello</h1>
      </div>
    );
  }
}

const Foo = connect<{}, {}, { count: number }>("count")(App);

export default Foo;

This is very similar to synacor/preact-i18n#16 (comment) , I'm starting to think that there might be something wrong with Preact's typing. Any guidance would be very much appreciated!

Best practice for "filter" functions?

What is the best practice for functions that don't alter state, but just return a subset of state? Eg. show todos that start with 'a'? Or if I have a cart that is an array of product ids, and I want the array of products based on those ids.

Thanks in advance.

integration build seems broken.

The build appears to be broken for unistore 3.00. This can be seen here: https://codesandbox.io/s/9yk4lzm9xp

The problems as far as I can make out is that the preact.js file which gets built by microbundle has the text module.exports=require('preact'); so while it does also export both connect and Provider none of these makes it through.

I will hopefully look into this tomorrow, and try to provide a pull request if I figure it out.

Can't resolve 'unistore/devtools'

Hi, I'm trying to import devtools from 'unistore/devtools'; with React and Unistore 3.0.3 and I'm getting Can't resolve 'unistore/devtools'

Is this a known issue?

Outdated TypeScript definiton deployed?

On line 8 you can see

export type Action<K> = (state: K, ...args: any[]) => void;

But when I install unistore 3.0.5 it looks like this in my node_modules

screen shot 2018-05-24 at 11 14 26

As you can see ...args: any[] is missing here šŸ¤”

Mistake in docs for createStore

From the docs:

let store = createStore();
   store.subscribe( state => console.log(state) );
   store.setState({ a: 'b' });   // logs { a: 'b' }
   store.setState({ c: 'd' });   // logs { c: 'd' }

I'm pretty sure that last line would actually log { a: 'b', c: 'd' }

Apologies if this is my misunderstanding. Hate to make more work for you, awesome that you were able to make this so small!

How to sync state/store with hotreload & separate actions file

I have following files;

app.js

import { h, Component } from "preact";
import { Router, route } from "preact-router";
import Head from "preact-head";
import FontAwesomeIcon from "@fortawesome/react-fontawesome";
import faBars from "@fortawesome/fontawesome-free-solid/faBars";

// Unistore
import { connect } from "unistore/preact";
import actions from "../store/actions";
// Unistore

// Components
import Header from "./header";
import Drawer from "./drawer";
import Clock from "./clock";
import Error from "./error";
import Modal from "./modal";
import Home from "async!../routes/home";
import Profile from "async!../routes/profile";
// Components

// Hotreload
if (module.hot) {
  require("preact/debug");
}
// Hotreload

// Utils
const now = Date.now();
const curYear = new Date(now).getFullYear("Y");

const jsUcfirst = string => {
  return string.charAt(0).toUpperCase() + string.slice(1);
};
// Utils

class App extends Component {
  /** Gets fired when the route changes.
   *	@param {Object} event		"change" event from [preact-router](http://git.io/preact-router)
   *	@param {string} event.url	The newly routed URL
   */

  handleRoute = e => {
    this.currentUrl = e.url;
  };

  goTo = pathname => {
    route(pathname);
    if (this.state.openDrawer) {
      this.hideDrawer();
    }
  };

  render(props, {}) {
    console.log("props", props);
    const currentUrl = Router.getCurrentUrl();
    const paths = currentUrl.split("/");
    const curPath = paths[1] === "" ? "Home" : jsUcfirst(paths[1]);
    return (
      <div id="app" class="off-canvas">
        <button class="off-canvas-toggle btn btn-action btn-trans" role="button" onClick={props.showDrawer}>
          <FontAwesomeIcon icon={faBars} />
        </button>

        <a class="off-canvas-overlay" onClick={props.hideDrawer} />

        <div class="off-canvas-content">
          <Header goTo={this.goTo} />
          <div class="container">
            <div class="columns">
              <Router onChange={this.handleRoute}>
                <Home path="/" />
                <Profile path="/profile/" user="me" />
                <Profile path="/profile/:user" />
                <Error type="404" default />
              </Router>
            </div>
          </div>
          <footer class="container">
            <div class="footertext">
              <Clock />
            </div>
          </footer>
        </div>
        <Modal />
        <Drawer goTo={this.goTo} hideDrawer={props.hideDrawer} active={props.drawerActive} />
      </div>
    );
  }
}

export default connect("count, login, modalActive, modalTitle, modalContent, drawerActive", actions)(App);

store/actions.js

import createStore from "unistore";

// If actions is a function, it gets passed the store:
let actions = store => ({
  // Actions can just return a state update:
  increment(state) {
    return { count: state.count + 1 };
  },

  // The above example as an Arrow Function:
  increment2: ({ count }) => ({ count: count + 1 }),

  //Actions receive current state as first parameter and any other params next
  //check this function as <button onClick={incrementAndLog}>
  incrementAndLog: ({ count }, event) => {
    console.info("incrementAndLog", event);
    return { count: count + 1 };
  },

  // Async actions can be pure async/promise functions:
  async getStuff(state) {
    let res = await fetch("/foo.json");
    return { stuff: await res.json() };
  },

  // ... or just actions that call store.setState() later:
  incrementAsync(state) {
    setTimeout(() => {
      store.setState({ count: state.count + 1 });
    }, 100);
  },

  // signin process
  signin(state) {
    return { login: true };
  },

  toggleModal: (state, event) => {
    console.log("toggleModal", state);
    console.log("event", event);
    // console.log("content", content);
    return { modalActive: !state.modalActive, modalTitle: title, modalContent: content };
    // return { modalActive: !state.modalActive };
  },

  toggleDrawer(state) {
    return { drawerActive: !state.drawerActive };
  },

  hideDrawer(state) {
    return { drawerActive: false };
  }
});

export default actions;

index.js

import "./spectre/dist/spectre.min.css";
import "./spectre/dist/spectre-exp.min.css";

import "./style";

import App from "./components/app";
import { createStore, Provider } from "unistore/full/preact";
import devtools from "unistore/devtools";
import initState from "./store/initState";

let memoryStore;
if (typeof window !== "undefined") {
  memoryStore = localStorage.getItem("store");
}

// let createStore = createStore(memoryStore ? JSON.parse(memoryStore) : initState);
let store =
  process.env.NODE_ENV === "production"
    ? createStore(memoryStore ? JSON.parse(memoryStore) : initState)
    : devtools(createStore(memoryStore ? JSON.parse(memoryStore) : initState));

export default () => (
  <Provider store={store}>
    <App />
  </Provider>
);

store/initState.js

// init store
let initState = {
  count: 0,
  login: false,
  drawerActive: false,
  modalActive: false,
  modalTitle: "Title modal",
  modalContent: "Content modal here"
};

export default initState;

My questions;

  1. How to sync state/store with hotreload ?
  2. How to separate actions files per category ? for example store/userActions.js will have action signin(), signout() and store/UIActions.js will have toggleDrawer(), toggleModal()

Thank you

Issue with selectors as string

Hi @developit

I've just got an error passing a string to the select function.
For this state = { a: 'b', c: 'd' }

select('a,c') // works fine 
select('a, c') // doesn't work. they key is ' c'

Given that the selector is or might be a string and it is not obvious that it should be csv without spaces on it. I was wondering if your consider worth it to trim the keys. Something like this

export function select(properties) {
    if (typeof properties==='string') properties = properties.split(',').map(p => p && p.trim());
	return state => {
		let selected = {};
		for (let i=0; i<properties.length; i++) {
			selected[properties[i]] = state[properties[i]];
		}
		return selected;
	};
}

Centralized place to handle error from action

Currently, I have to try/catch error for each action to be able to compensate or show friendly message if any error occurs.
I wonder if there is any better way to achieve that? A centralized place like store.subscribe()?

store.subscribe() is not likely for this case, it's only for state change.

Proposal: async actions

Currently, if you have an asynchronous action, you are in charge of calling store.setState manually, while synchronous actions can just return the state update, and setState is called after the return. What if actions could return Promises and setState was called after the Promise resolves? Something like this:

// Action
async incrementAsync(state) {
  const response = await fetch("data.json");
  const data = await response.json();
  return { data };
}

// Mapped action
// https://github.com/developit/unistore/blob/master/unistore.js#L78
Promise.resolve(actions[i](store.getState(), ...args)).then(store.setState);

flow support

Any support or examples for using unstore with flow.js?

Combine standard component with connected component

Is there a way to combine a standard Preact component (see below) with a connected component? This would mean to combine the store state with local state and props.

export default class Counter extends Component {
  state = { count: 100 };

  increment = () => { this.setState({ count: this.state.count + 1 }); };
 
  render({ user }, { count }) {
    return (
      <div class={style.profile}>
        <h1>Profile: {user}</h1>
        <p>
          <button onClick={this.increment}>Click Me</button> Clicked {count} times.
        </p>
      </div>
    );
  }
}

Action required: Greenkeeper could not be activated šŸšØ

šŸšØ You need to enable Continuous Integration on all branches of this repository. šŸšØ

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didnā€™t receive a CI status on the greenkeeper/initial branch, itā€™s possible that you donā€™t have CI set up yet. We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

If you have already set up a CI for this repository, you might need to check how itā€™s configured. Make sure it is set to run on all new branches. If you donā€™t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, youā€™ll need to re-trigger Greenkeeperā€™s initial pull request. To do this, please delete the greenkeeper/initial branch in this repository, and then remove and re-add this repository to the Greenkeeper Appā€™s white list on Github. You'll find this list on your repo or organizationā€™s settings page, under Installed GitHub Apps.

Side effect in `store.setState`

Hi,
I'm the creator of Reless-js (https://github.com/Alber70g/reless-js) which is basically the same thing as unistore.

I've used an update to update things in an async matter, but it's nicer to pass actions in the action and call one of them to update the state. Otherwise you'd have some sort of side-effect which is hard to locate and log with Dev Tools (e.g. redux-dev-tools).

Have a look at how I solved it in reless-js and use it to your advantage

Just my 2 cents ;)

SSR support

This might be out of the scope of the project but is it possible to do server side rendering/ rehydration with unistore?

Incorrect listener is triggered when one listener unsubscribes another

Hi,

First thank you for making web faster!

I am using the version 2.3.0 of the library and encountered the following issue:

Test case:

const store = unistore.createStore()

const handlerA = () => {
  console.log('Handler A invoked')
}

const handlerB = () => {
  console.log('Handler B invoked')
  store.unsubscribe(handlerA)
}

const handlerC = () => {
  console.log('Handler C invoked')
}

const handlerD = () => {
  console.log('Handler D invoked')
}

store.subscribe(handlerA)
store.subscribe(handlerB)
store.subscribe(handlerC)
store.subscribe(handlerD)

store.setState({ foo: 'bar' })

Expected output:

Handler A invoked
Handler B invoked
Handler C invoked
Handler D invoked

Actual output:

Handler A invoked
Handler B invoked
Handler D invoked

Show how to reuse actions within actions in docs?

This works... maybe worth adding to the readme.

// If actions is a function, it gets passed the store:
let actions = store => ({
  // Actions can just return a state update:
  increment(state) {
    return { count: state.count + 1 };
  },
   // The above example as an Arrow Function:
  increment2: ({ count }) => ({ count: count + 1 }),

  // Async actions are actions that call store.setState():
  incrementAsync(state) {
    store.setState(this.increment(state));
    setTimeout(() => {
      store.setState({ count: store.getState().count + 1 });
    }, 1000);
  },
});

TypeScript declaration missing for unistore/preact

I'm getting the following error trying to include connect in TypeScript:

import { connect } from 'unistore/preact'
    TS7016: Could not find a declaration file for module 'unistore/preact'. '[...]/node_modules/unistore/preact.js' implicitly has an 'any' type.
  Try `npm install @types/unistore/preact` if it exists or add a new declaration (.d.ts) file containing `declare module 'unistore/preact';`

I imagine a similar declaration is required for React. The current definitions file declares connect in the base unistore module, however that module does not export the function.

Connect to all properties

It's annoying to pick every property, that developer want to use.
Likely developer want to connect to all properties:

const App = connect('*', actions)(
  ({ count, increment }) => (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  )
)

Or maybe

const App = connect(actions)(
  ({ count, increment }) => (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  )
)

Cannot invoke passed function into child component with unistore

Actions do work, when everything is in one initial file as shown in example in README, increment does increment. Now I am trying to pass action increment, into my child component as property(child component is App container):

import { h, render, Component } from 'preact';
import { Router, RouteProps, Route } from 'preact-router';
import createStore from 'unistore';
import { Provider, connect } from 'unistore/preact';
import { State } from 'interfaces/unistore';

// Import components
import App from 'containers/app/app';

// Create unitstore store
const initialState = {
    count: 0,
    secondcount: 0,
    list: []
  }
  let store = createStore(initialState);

// accept hot module update
if ((module as any).hot) {
    (module as any).hot.accept();
}

// Add actions to store
let actions = store => ({
    // for count
    increment(state) {
      return { count: state.count + 1 };
    },
    // for secondcount
    increment2: ({ secondcount }) =>({ secondcount: secondcount + 1}),
    
    // adds a todo item to the list array
    addTodo: (state, data)  => {
      return {
        ...state,
        list: [...state.list, data]
      }
    },
  });

// Create higher order connect component
const GlobalState = connect(["count", "secondcount", "list"], actions)(({ 
    count, secondcount, list, addTodo, increment, increment2 }) =>
//     <div>
//     <p>Count: {count}</p>
//     <button onClick={increment}>Increment</button>
//     <p>Second count: {secondcount}</p>
//     <button onClick={increment2}>Increment</button>
//   </div>
    <App {...{ count, secondcount, increment, increment2 }} />
)

// Bootstrap preact app
render(<Provider store={store}><App  /></Provider>, document.getElementById('root'), document.getElementById('app'));

// export a function to get the current unistore state
export function getState() { return store.getState(); }

In app container I am then trying to access properties:

// Import node modules
import { h, render, Component } from 'preact';
import { Router, RouteProps, Route, route } from 'preact-router';
import createStore from 'unistore';
import { connect } from 'unistore/preact';

// Import internal modules
import Navigation from 'components/navigation/navigation';
import Home from 'containers/home/home';
import Profile from 'containers/profile/profile';
import Default from 'containers/default/default';
import Signin from 'containers/signin/signin';
import * as constants from 'helpers/constants';
import { State } from "interfaces/unistore";

interface IndexProps { count, secondcount, increment, increment2 }

interface IndexState {}

class InnerComponent extends Component<IndexProps, IndexState> {

    constructor(props) {
        super(props);
    }

    render() {
        return (
                <div>
    <p>Count: {this.props.count}</p>
    <button onClick={this.props.increment}>Increment</button>
    <p>Second count: {this.props.secondcount}</p>
    <button onClick={this.props.increment2}>Increment</button>
  </div>
        )
    }
}

// Connect component to unistore store
const Index = connect(["count", "secondcount", "increment", "increment2"])(({ count, secondcount, increment, increment2 }) => {
    return (
        <InnerComponent {...{ count, secondcount, increment, increment2 }} />
    )
})

// export the module
export default Index;

Increment doesn't work now. What am I missing?

React UMD bundle fails to execute

I tried to use the UMD React build from unpkg, but it failed with Uncaught TypeError: Cannot read property 'Component' of undefined.
I think it's because it expects to find React on the global object under name react but it's actually React (with a capital R).
You can reproduce the issue by opening an html file like this in the browser:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Title</title>
	<script src="https://unpkg.com/unistore/full/react.umd.js"></script>
</head>
<body>
</body>
</html>

Is this library React compatible?

As I understand it, Preact is meant to be approximately API compatible with React, so why is it the specific target of this library? Is there any reason why it shouldn't work with React anyway?

How to run an action from outside a connected component

Similar to #75

You sometimes need to run an action from outside a connected component. I haven't seen a recommended pattern to do this so have come up with a temporary solution

This is basically mapActions but scoped to the current module's actions

export function bindActions(store) {
  const innerActions = actions(store);
  let mapped = {};
  for (let i in innerActions) {
    mapped[i] = store.action(innerActions[i]);
  }
  return mapped;
}

// usage
bindActions(store).setActiveOrder(order);

This also works, but uses a string to reference the action name

export const runAction = (store, actionName, args) => store.action(actions(store)[actionName])(args);

// usage
runAction(store, 'setActiveOrder', order);

Any thoughts on a nice way to do this?

Require module not found - 'unistore/react'

Documentation suggests that you can import the react/preact-specific modules by:

import { connect, Provider } from 'unistore/react';

But this generates errors like 'required module not found'. I had a poke around node_modules, and there doesn't appear to be react/preact directories, so that would make sense:

screen shot 2017-12-14 at 2 49 02 pm

actions broken in latest iteration

In the latest commit, the new createAction function calls the action in stead of returning a callable action-function.

PR on the way..

Error: n.getChildContext(): childContextTypes must be defined in order to use getChildContext()

Hi,

I'm trying to use unistore to rewrite react-accessible-accordion component.
I can't have it to work following the documentation.

I keep getting the following error on my "main" component (Accordion)
Error: n.getChildContext(): childContextTypes must be defined in order to use getChildContext()
(cf attached console screenshot)

My code is fairly basic:

// @flow

import React, { Component } from 'react';
import type { Node } from 'react';
import { createStore, Provider } from 'unistore/full/react';

type AccordionProps = {
    accordion: boolean,
    children: Node,
    className: string,
    onChange: Function,
    activeItems: Array<string | number>,
};

class Accordion extends Component<AccordionProps, *> {
    static defaultProps = {
        accordion: true,
        onChange: () => {},
        className: 'accordion',
        activeItems: [],
    };

    store = createStore({
        accordion: this.props.accordion,
        onChange: this.props.onChange,
        activeItems: this.props.activeItems,
    });

    render() {
        const { className, accordion, children } = this.props;
        return (
            <Provider store={this.store}>
                <div>Test</div>
            </Provider>
        );
    }
}

export default Accordion;

screen shot 2017-12-14 at 2 48 47 pm

skatejs support

Just tried to take a stab at adding support for https://github.com/skatejs/skatejs but ran into a few issues:

  1. Jest seems to not be friendly with custom elements since you cannot extend HTMLElement. I see jsdom/webidl2js#88 and jsdom/jsdom#1030 that have no positive resolution and require fundamental changes in jsdom.
  2. What should an example of using unistore with a skatejs component look like? My current assumption is something like:
import { withComponent } from 'skatejs';
import { connect } from 'unistore/skatejs';

const actions = store => ({
  // ...
});

class TestComponent extends connect('count', actions, withComponent()) {
  render({ count }) {
    return `Count is ${count}`;
  }
}

Thoughts?

Thanks!

abuse of `map function` in documentation

all documentation full of map function even it not necessary at all . it make the code hard to read because something that look at first glance as const or variable at the end figure as callback. is very confusing and make the code look much more complicated then what it is

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.