Giter Club home page Giter Club logo

flux-container's Introduction

Sententiaregum

A social network based on Symfony and ReactJS

Build

Build Status Scrutinizer Code Quality SensioLabsInsight

Development

Stories in Ready Join the chat at https://gitter.im/Sententiaregum Latest Stable Version Total Downloads Latest Unstable Version License

What is it?

Sententiaregum is a social network based on a REST api written with Symfony3 and a frontend in ECMAScript6 using React and a custom flux implementation.

Docs

The docs can be found here.

License

This project is under the GPL license. The whole license file can be found here.

flux-container's People

Contributors

benbieler avatar ma27 avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

flux-container's Issues

improve the docs

Description of the issue

the docs are splitted in two parts: the spec of this implementation and a technical documentation which describes the appropriate usage of the API.
The readme shouldn't be too overloaded, therefore a docs directory should be created.

add npm badge

Description of the issue

Add the npm badge to the readme.

find a better profiling approach

the current approach is not very clean as it directly impacts tests with performance analysations and without a proper profiling.

We should evaluate better ideas than the current profiling approach.

array syntax with brackets should be supported when fetching an array item from the state

Description of the issue

the store#getStateValue is able to fetch a property path, but its behavior is incorrect with arrays in the state.

Steps to reproduce

  • create a store with a state like this { value: [ { value: "foo" } ] }
  • fetch a value with `store#getStateValue('value[0].value');

Expected behavior

the value should be resolved properly.
Instead the following property path works: value.0.value. This works, but is semantically incorrect.

Additional information

to avoid BC breaks, both approaches should be supported, but the old one should disappear from the docs.

make the actual dispatcher API private

Description of the issue

API for action creators

as already mentioned in #12 , the public API should be kept as simple as possible and all implementation details of the dispatcher class must not be public.
The only part of the dispatcher which should be used is the dispatch method in action creators.
The concept of those should be refactored to make them compatible with the spec of this implementation:

  • action creators should create an action, not being one.
  • actions should get the dispatch directive injected to deal in a better way with the internal API

This could look like this:

class BookActions {
  persistBook(data) {
    return dispatch => {
      BookWebAPIUtils.save(data, () => {
        dispatch(SAVE_BOOK, { data });
      });
    }
  }
}

component <> action interaction

The components call the actions which are actually a facade for the dispatcher.
The dispatcher calls should be refactored and an API should be introduced for calling actions which could look like this for the example above:

onSubmit(e) {
  this.setState({ loading: true });
  runAction(PortalActions.persistBook(this.state.formData));
  e.preventDefault();
}

fix index.js

Description of the issue

The index.js is pure ES5.
It should be ES6 as it sucks importing es6 stuff in es5 style.

Steps to reproduce

import { BaseStore } from 'sententiaregum-flux-container';
// results in { default: [Function] }

Expected behavior

The proper class should be delivered

Additional information

all systems affected

don't freeze whole store

Description of the issue

the store itself should not be frozen as it should be mockable in unittests

Steps to reproduce

  • build a store using the store() API.
  • try to mock it in a unit test with a mocking engine such as sinon

Expected behavior

store should be mockable

parallel actions

Description of the issue

in some cases (like clicking multiple buttons in the UI) multiple dispatches could be caused by the dispatcher which would cause an invariant violation.
A stateless wrapper at the top of the dispatcher implementation which uses the actual dispatcher internally as state container could help and the actual issue.

[RFC] refactor internal workflows to a more functional approach

Status quo

The first versions were made in order to solve certain issues in Sententiaregum such as the dispatcher handling and the execution tree during single published events. But internally certain ES6 classes that fragment the code and make everything more complex.

Refactoring

Dispatcher

The dispatcher should finally become an implementation detail: in order to achieve that, only one module should be exported which returns a singleton. The dispatcher itself should be completely stateless, but some listeners should be adjusted.

Store

The internal store API is quite fragile. In order to simplify that, the only API should be the store API which uses a simple variable internally to keep the state and build all utility around itself. Furthermore it should return a simple object composed of stateless functions to avoid side-effects, but a useful public API.

Handle store changes

When combining it with React.JS, the component lifecycle will be used to flush store changes into the view. This is quite handy as the view is capable at deciding for itself how to maintain its internal state.
Currently one issue need to be fixed:

  • rename useWith to subscribe as it's more convenient with unsubscribe.

The view engine can be handled using a custom module which connects stuff to react internally.

Actions

The current action system is quite nice, but might experience further improvements to make it easiert to work with.
One step might be to decouple the event related stuff from the actual action-related logic: This could happen by creating one creator per module which is capable at defining multiple actions:

// buildTestActions.js
export default publish => {
  function receiveTestData() {
    ajax.get('/rest/test/data.json')
      .then(r => publish(r);
  }

  return {
    [TEST_RECEIVE_DATA]: receiveTestData,
    // further stuff...
  }
};

This API is somehow consistent to the current store API as it associates event constants to a config which is a flat function in this case which publishes the payload.

This could be called like this from a view;

// ...
runAction(TEST_RECEIVE_DATA, buildTestActions)

A third argument might be introduced which is an array of arguments for the actual action creator. This will be injected into the function returned by the receiveTestData HOF as normalized list.

No event name is needed as it's already configured in the returned list. The publish argument simply needs a payload as argument.

A last note about error/success handling: in this example it's not possible to differ from error and sucess events. This is intended as we build functions for each action in the store (lots of other implementations have huge classes with switch/case instructions or stateless "reducers" for that). However when a certain action is triggered the handler in the should be capable at handling any result comming from that action. To achieve this, a success parameter might be added to the payload.

Tasks

  • NodeJs 6.x and NPM 3 as only supported engines
  • simplify module structure (let functions return objects, don't fragment for multiple classes, kill composite store and freeze)
  • make dispatcher API more strict
  • simplify actions by making it consistent with teh store API
  • custom scope for tokens and state to avoid Object.freeze hacks
  • make initial state lazy when a callback is used
  • refactor store<>view connection
  • implement test utils for actions
  • [bug] don't update state if nothing changed
  • improve the docs

dependency handling for dispatchers

Description of the issue

Some dataflows will be aborted by the dispatcher as it detects the attempt of running into a circular callback dependency loop. In most of the cases this is not true, but the usecase of dispatching stuff inside a flow from action creator to view is necessary for example when affecting parts of the navigation which will cause re-rendering processes. For those cases some kind of after hooks should be implemented.

This should be implemented natively in the dispatcher.

code improvements

Description of the issue

there are still some code pieces lacking clean code that needs to be fixed.

make `BaseStore` private

Introduction

in 1.0.2 the view handling has been refactored by introducing an API that listens to stores without being forced to inherit another abstract react class. Furthermore it is now possible to use any view library since the react dependency has been removed.
The second class which is public is the BaseStore. It should exist internally only as the store must be a dumb item handling data which has been received as payload from the dispatcher before.
Allowing to create classes for the store which should be a simple, dumb entity is a too loose implementation. Therefore a factory for the store should be created returning an instance of a store.

Possible usage

// stores/FooStore.js
import { store } from 'sententiaregum-flux-container';
import BarStore from './BarStore';
import handleFooEvent from '../util/handler/handleFooEvent';

export default store(
  {
    'EVENT_NAME': {
      params: ['values', 'from', 'dispatcher', 'payload'],
      dependencies: BarStore.getToken('EVENT_NAME'),
      hander: handleFooEvent
    }
  },
  'value representing the initial state'
);

The second argument contains a default state. Sometimes is state initialization a bit trickier (fetching credentials stored in localStorage and parse it to tell the security system about the user's permissions). Therefore callbacks should be passable as initial state which return the initial state:

export default store(
  {},
  () => 'any initial state created by the callback'
);

Public API of the created store

The store should contain a public API to return the state and being able to get tokens to declare dispatcher dependencies:

  • getState() - returns the state received by the dispatcher and handled by the handler. If no dispatch cycle has been triggered, the initial state will be returned.
  • getToken(eventName<String>) - returns the dispatch token for a certain event which is necessary for the declaration of dependencies.

The _setState(newState<mixed>) is used internally for internal purposes (the internal handler injects the result of the handler into the store) and is therefore considered to be internal and should not be used elsewhere. In order to ensure the internal state, this method is prefixed with an underscore. Same for _setTokens.

simplify store configuration

Description of the issue

the current store configuration is quite powerful, but quite verbose, too and when having a more complex store which should subscribe multiple events and contains a complex state tree, it's better to make the configuration more intuitive.

Proposal

Instead we could create a subscription API which creates the object internally:

import { subscribe, store } from 'sententiaregum-flux-container';
import { ACTION } from '../constants/Foo';
import handleFoo from './handlers/handleFoo';

export default store({
  [ACTION]: subscribe([/* params */], [/* dependencies */], subscribe.chain()(handleFoo))
});

This would make everything much more readable.

It could be also possible to chain multiple handlers:

import { subscribe, store } from 'sententiaregum-flux-container';
import { ACTION } from '../constants/Foo';
import handleFoo from './handlers/handleFoo';
import handleBar from './handlers/handleBar';

export default store({
  [ACTION]: subscribe([/* params */], [/* dependencies */], subscribe.chain()(handleFoo)(handleBar))
});

And when the state needs to be copied, no function needs to be composed and the argument can be omitted:

import { subscribe, store } from 'sententiaregum-flux-container';
import { ACTION } from '../constants/Foo';

export default store({
  [ACTION]: subscribe([/* params */], [/* dependencies */])
});

New feature: modify state sections

State might be composed of multiple recursive sections:

{
  section: {
    /* values */
  }
}

Now the implementation might look like this:

import { subscribe, store } from 'sententiaregum-flux-container';
import { ACTION } from '../constants/Foo';

export default store({
  [ACTION]: subscribe([/* params */], [/* dependencies */], subscribe.chain()('section'))
});

Then it would gather all the params defined in the first array and merge them into the section.

New feature: old state in callbacks

All handlers should be able to access to old state easily.
To solve this, one last argument should be added to each handler:

src/stores/handlers/handleFoo.js

export default (param1, param2, oldState) => {
  // ...
};

This argument is always the last one and can be omitted, so no BC break here.

find a better class definition approach

the current approach of defining public API internally is done by using anonymous classes (export default new class).
This is quite pretty, but has one downside: it doesn't work properly with IDEs and breaks code completion everywhere. Especially the store API is hardly usage because of this.

Therefore a better approach is needed.

disable `retain-lines` option when publishing tags

Description of the issue

our .babelrc uses the retain-lines option as it is important when debugging with WebStorm ❤️ , but the resulting code is not really readable which is outrageous when trying to read code shipped by NPM in a project.

initializers for data stores

Description of the issue

sometimes stores must be initialized lazily to contain data for certain purposes:
One example is a user store containing data like API authorization keys for the logged in user. This is required for page renderings of sites that should be hidden for guests.

In Sententiaregum we have a private service which fetches data from localStorage, but the store should be used to access data as this store will receive the data at the login workflow and the store should be the data containment component in the business logic, not a private services doing magic things with a localStorage.

Pseudo code

a initializer should be a single function which updates the store's state and should be attached at the store:

// stores/initializer/initUserCredentials.js

export default function initUserCredentials(oldStore) {
  // ...
  return newState;
}

And the store could look like this:

// stores/UserStore.js
class UserStore extends BaseStore {
}

export default createLazyStore(UserStore, initUserCredentials);

This factory aims to create a lazy proxy for the store containing a overriden variant of getState that calls the initializer at first unless already initialized.

Changes

the internal state property must not be accessible anymore as it should be initialized at the first call.
Therefore it must be an internal variable inside the BaseStore module, instead setState and getState should be added to the public API

use a better assertion library

Unfortunately all these JS testing libraries don't provide native assertion support (as PHPUnit does for instance), but using invariant for validation is basically wrong as it's simply responsible for validating broken data during workflows, but we should use a "real" assertion library for the test utils

replace ``BaseComponent`` with a functional connection API

Description of the issue

the BaseComponent is a simple hack in the spec of this implementation which shouldn't exist as public API classes should be avoided (https://medium.com/@dan_abramov/how-to-use-classes-and-sleep-at-night-9af8de78ccb4#.jm9evg8ba) and the BaseComponent is something that should be removed.
Instead a lightweight connection library could be created:

// add.
componentDidMount() {
  connector(storeObj).useWith(this.handler);
}

// remove.
componentWillUnmount() {
  connector(storeObj).unsubscribe(this.handler);
}

What's with the BaseStore

the big issue of the components is the hacky OO approach: we declare properties in the constructor of the component, use super calls in our lifecycle hooks to make the subscription work build a even bigger component structure which is harder to understand.

The BaseStore utilizes the approach of creating a simple and dumb data object which flushes changes in its state to the view. Stores should be instantiated as they're just simple objects and the instance should be passed to module.exports and not the class. Therefore the BaseStore is not a big design issue, but the BaseComponent is.

analyse performance

Description of the issue

the codebase becomes more stable and better, but contains various features that might be optimized.

Tools like the order computation, the state queries or the new configuration utility planned in #34 could be critical for performance somehow in the feature. In order to prevent that, some monitoring should be added and analysed during CI builds.

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.