Giter VIP home page Giter VIP logo

recycle's Introduction

Join the chat at https://gitter.im/recyclejs npm version npm downloads

DEPRECATED

Please note that this library hasn't been updated for more than two years. It's very rarely used and I consider it deprecated.

Recycle

Convert functional/reactive object description into React component.

You don't need another UI framework if you want to use RxJS.

Installation

npm install --save recycle

Example

Webpackbin example

const Timer = recycle({
  initialState: {
    secondsElapsed: 0,
    counter: 0
  },
 
  update (sources) {
    return [
      sources.select('button')
        .addListener('onClick')
        .reducer(state => {
          ...state,
          counter: state.counter + 1
        }),
      
      Rx.Observable.interval(1000)
        .reducer(state => {
          ...state,
          secondsElapsed: state.secondsElapsed + 1
        })
    ]
  },
 
  view (props, state) {
    return (
      <div>
        <div>Seconds Elapsed: {state.secondsElapsed}</div>
        <div>Times Clicked: {state.counter}</div>
        <button>Click Me</button>
      </div>
    )
  }
})

You can also listen on child component events and define custom event handlers. Just make sure you specify what should be returned:

import CustomButton from './CustomButton'

const Timer = recycle({
  initialState: {
    counter: 0
  },
 
  update (sources) {
    return [
      sources.select(CustomButton)
        .addListener('customOnClick')
        .reducer((state, returnedValue) => {
          counter: state.counter + returnedValue
        })
    ]
  },
 
  view (props, state) {
    return (
      <div>
        <div>Times Clicked: {state.counter}</div>
        <CustomButton customOnClick={e => e.something}>Click Me</CustomButton>
      </div>
    )
  }
})

Replacing Redux Connect

If you are using Redux, Recycle component can also be used as a container (an alternative to Redux connect).

The advantage of this approach is that you have full control over component rerendering (components will not be "forceUpdated" magically).

Also, you can listen to a specific part of the state and update your component only if that property is changed.

export default recycle({
  dispatch (sources) {
    return [
      sources.select('div')
        .addListener('onClick')
        .mapTo({ type: 'REDUX_ACTION_TYPE', text: 'hello from recycle' })
    ]
  },

  update (sources) {
    return [
      sources.store
        .reducer(function (state, store) {
          return store
        })

      /** 
      * Example of a subscription on a specific store property
      * with distinctUntilChanged() component will be updated only when that property is changed
      *
      * sources.store
      *   .map(s => s.specificProperty)
      *   .distinctUntilChanged()
      *   .reducer(function (state, specificProperty) {
      *     state.something = specificProperty
      *     return state
      *   })
      */
    ]
  },

  view (props, state) {
    return <div>Number of todos: {store.todos.length}</div>
  }
})

Effects

If you don't need to update a component local state or dispatch Redux action, but you still need to react to some kind of async operation, you can use effects.

Recycle will subscribe to this stream but it will not use it. It is intended for making side effects (like calling callback functions passed from a parent component)

const Timer = recycle({
 
  effects (sources) {
    return [
      sources.select('input')
        .addListener('onKeyPress')
        .withLatestFrom(sources.props)
        .map(([e, props]) => {
          props.callParentFunction(e.target.value)
        })
    ]
  },
 
  view (props) {
    return (
      <input placeholder={props.defaultValue}></input>
    )
  }
})

API

Component description object accepts following properties:

{
  propTypes: { name: PropTypes.string },
  displayName: 'ComponentName',
  initialState: {},
  dispatch: function(sources) { return Observable },
  update: function(sources) { return Observable },
  effects: function(sources) { return Observable },
  view: function(props, state) { return JSX }
}

In update, dispatch and effects functions, you can use the following sources:

/**
*   sources.select
*
*   select node by tag name or child component
*/
sources.select('tag')
  .addListener('event')

sources.select(ChildComponent)
  .addListener('event')

/**
*   sources.selectClass
*
*   select node by class name
*/
sources.selectClass('classname')
  .addListener('event')

/**
*   sources.selectId
*
*   select node by its id
*/
sources.selectId('node-id')
  .addListener('event')

/**
*   sources.store
*
*   If you are using redux (component is inside Provider)
*   sources.store will emit its state changes
*/
  sources.store
    .reducer(...)

/**
*   sources.state
*
*   Stream of current local component state
*/
  sources.select('input')
    .addListener('onKeyPress')
    .filter(e => e.key === 'Enter')
    .withLatestFrom(sources.state)
    .map(([e, state]) => state.someStateValue)
    .map(someStateValue => using(someStateValue))

/**
*   sources.props
*
*   Stream of current local component props
*/
  sources.select('input')
    .addListener('onKeyPress')
    .filter(e => e.key === 'Enter')
    .withLatestFrom(sources.props)
    .map(([e, props]) => props.somePropsValue)
    .map(somePropsValue => using(somePropsValue))

/**
*   sources.lifecycle
*
*   Stream of component lifecycle events
*/
  sources.lifecycle
    .filter(e => e === 'componentDidMount')
    .do(something)

FAQ

Why would I use it?

  • Greater separation of concerns between component presentation and component logic
  • You don't need classes so each part of a component can be defined and tested separately.
  • Component description is more consistent. There is no custom handleClick events or this.setState statements that you need to worry about.
  • The State is calculated the same way as for redux store: state = reducer(state, action).
  • Redux container looks like a normal component and it's more clear what it does.
  • Easy to use in an existing React application (choose components which you wish to convert).

Why would I NOT use it?

  • Observables are not your thing.
  • You need more control over component lifecycle (like shouldComponentUpdate)

What is this? jQuery?

No.

Although it resembles query selectors, Recycle uses React’s inline event handlers and doesn’t rely on the DOM. Since selection is isolated per component, no child nodes can ever be accessed.

Can I use CSS selectors?

No.

Since Recycle doesn't query over your nodes, selectors like div .class will not work.

How does it then find selected nodes?

It works by monkeypatching React.createElement. Before a component is rendered, for each element, if a select query is matched, recycle sets inline event listener.

Each time event handler dispatches an event, it calls selectedNode.rxSubject.next(e)

Can I use it with React Native?

Yes.

Recycle creates classical React component which can be safely used in React Native.

recycle's People

Contributors

domagojk avatar edunuzzi avatar goodmind avatar midnight-wonderer avatar orlov-vo 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

recycle's Issues

i like the `storePath` feature

I've long wondered why Redux can't also have a quick simple string-based API to enter the state path you're interested in.

Why you removed adapters?

So, how to use this library with different streams. I come from cycle.js and this is really killer-feature for me as well as better effects handling (drivers)

umd build depended on unix machines

Currently, for creating umd bundled script this script is used:

browserify lib/index.js -o dist/recycle.js -s recycle && echo \"recycle = recycle.default;\" >> dist/recycle.js

Making recycleobject to be recycle.default is clearly a hack 😳
Any suggestions how to do this properly?

Using webpack seemed an overkill for this, but maybe I'm wrong?

Pull requests are welcome! :)

About the old versions

Hello @domagojk , I am the member of cdnjs project. We want to host this library. There is a question want to ask. I found that there is no recycle.js in v0.2.1~v0.3.1 and v0.5.0 on npm. Please help me confirm that which file I need to add in these versions. Thanks for your help!

cdnjs/cdnjs#10053

Use this.setState instead of this.state

Using 'this.state = ...' has been deprecated by React. Shouldn't we change line 50 in 'component.js' to this.setState(newState)?

Line 50 in 'component.js'

this.state = this.componentState

Could become

this.setState(this.componenState)

SSR?

Neat library.

I'm wondering if there's an obvious/preferred pattern for using it alongside SSR for universal components?

I have a comprehensive webpack build that defines CLIENT and SERVER constants to indicate which platform React is being rendered on. This makes it trivial to handle RxJS streams with a simple if/else statement to tack on .take(1) to prevent over-subscribing on the server and attempting to setState() on a component after ReactDOM.renderToString() has already been called.

So, far so good.

The problem I have is signalling to my web server that the data stream is 'ready' before rendering the HTML. If I have an async stream, I want the server to 'wait' until it has a value before starting the React chain and throwing back to the initial markup. On the client, it can setState as many times as it wants.

Is there anything built into the Recycle API that would allow me to merge ALL component reducers into a single stream that's emitted when every individual reducer has received its first value, that I can subscribe to outside of the component chain?

That way, I could simply await on a merged stream Promise for that to occur before calling renderToString and be assured that the React chain being built is going to subscribe to a value that's 'hot' and is available before attempting to generate markup.

I started spinning up my own library to handle this, but creating a new 'context' for each new request and then figuring out the best way to decorate components and get access to the original streams within the same context is a rabbit hole I'd rather not get lost down if this lib can already do all/most of it.

Thanks in advance for any suggestions!

Split up adapter for more composability

Combining the React adapter and Observable adapter is limiting. Is there a reason it is necessary? What about something like the following?

Recycle({
  adapters: {
    main: reactAdapter,
    observable: mostAdapter
  }
})

This way, they can be changed independently without the need to create a new adapter for each combination.

Updating docs structure

As @faceyspacey suggested, documentation should look something like this:

Quick Start
Motivation
Concepts
--Stateful Component
--Parent-Child Relationship
--Using React Components
--Store
--Plugins
--Adapters
Examples
--Autocomplete
--WebSocket Echo
--TodoMVC
API Reference

Error when trying to use .withLatestFrom inside effects of a component

I'm really new to this library and Rxjs in general but I haven't found any answer searching through the source.

I'm getting:
Header.js:25 Uncaught TypeError: e.select(...).addListener(...).withLatestFrom is not a function

in browser console. I have no idea why, my example is about as vanilla as it gets.

Where does withLatestFrom come from? Rxjs?

Stepping through in debugger it seems that for whatever reason what is returned from addListener does not include a withLatestFrom function. The call to ref.stream.switch() returns an AnonymousSubject for whatever that's worth to you.

warning with version v2.2.1 on next.js

I'm trying your lib with next.js and been great so far, but with version v2.2.1 I'm getting a warning on the next.js server:

screen shot 2017-05-15 at 15 46 23

The example component I'm rendering is this one:

import React from 'react'
import recycle from 'recycle'
import { Observable } from 'rxjs'

const Timer = recycle({
  initialState: {
    secondsElapsed: 0,
    counter: 0,
  },

  update(sources) {
    return [
      sources.select(Button).addListener('onClick').reducer(state =>
        Object.assign({}, state, {
          counter: state.counter + 1,
        }),
      ),

      Observable.interval(1000).reducer(state =>
        Object.assign({}, state, {
          secondsElapsed: state.secondsElapsed + 1,
        }),
      ),
    ]
  },

  view(props: propsType, state) {
    return (
      <div>
        <div>Seconds Elapsed: {state.secondsElapsed}</div>
        <div>Times Clicked: {state.counter}</div>
        <button className="button">Click Me</button>
      </div>
    )
  },
})

export default Timer

When I comment out the Observable with the interval the warnings go away.

I've fixed the 2.2.0 version in the package.json that don't have this problem.

Redux plugin

Redux plugin implementation.

I believe that it could be similar to store plugin but with global reducers and createStore from Redux

sources.className doesn't work if selected element has multiple classes

If I have multiple classes on the node on which I want to attach the listener and I want to select it by one of its classes, it won't work. Is this expected?

const Timer = recycle({
  initialState: {
    secondsElapsed: 0,
    counter: 0
  },

  update(sources) {
    debugger;
    return [
      sources
        .selectClass("not-working")
        .addListener("onClick")
        .reducer(function(state) {
          debugger;
          state.counter++;
          return state;
        }),

      Rx.Observable.interval(1000).reducer(function(state) {
        state.secondsElapsed++;
        return state;
      })
    ];
  },

  view(props, state) {
    return (
      <div>
        <div>
          Seconds Elapsed: {state.secondsElapsed}
        </div>
        <div>
          Times Clicked: {state.counter}
        </div>
        <button className="not-working f5 avenir link dim ph4 pv3 mb2 mt4 dib white">
          Click Me
        </button>
      </div>
    );
  }
});

Question: state update, immutable approach

Gratz on the library really dig the overal concept.

I however have a simple question that I would like to get answered.
In libraries like redux etc. there is a need for a immutable approach for properly re-rendering all the components (think about shouldComponentUpdate etc.).

In the examples given in the docs (for example here) this concept doesn't seem to be the standard?

Shouldn't especially in a functional approach "pure reducers" be preferred above the current usage?

Benchmark testing

As @m59peacemaker suggested, It would be great to have benchmark tests.
For component rendering compared to react and store plugin compared to Redux

Typescript support??

I am thinking about using this amazing lib into my next project and i would like to hear about your thoughts about supporting typescript. It shouldn't be that hard to do so, and i would be willing to put some effort myself and create a pull a request, if you are ok with it ;)

sources.select doesn't work in React Native elements

When I try to add a listener to a React Native element, nothing happens.

In my update:

sources.select('Button').addListener('onPress')
  .reducer(...)

In my view:

<Button title="Go" />

It seems like recycle cannot select the Button element. Tried to figure it out, but can't find the reason.

State seems shared by component instances

I was facing a weird behavior with sibling instances of a same component, and now I think I was able to isolate the issue in this simple exemple:

const Exemple = recycle({
  initialState: ({
    myBoolean: false,
  }),

  update(sources) {
    return [
      sources.lifecycle
        .filter(e => e === 'componentDidMount')
        .reducer(state => {
          state.myBoolean = !state.myBoolean
          return state
        }),
    ]
  },

  view(props, state) {
    return (
      <p>{state.myBoolean.toString()}</p>
    )
  }
})

I would expect all instances of this component to negate myBoolean on mount, making it true, as the initial state defines it false. But instead, each instance gets a different value, as they were negating the same state reference.

If I do:

<Exemple/>
<Exemple/>
<Exemple/>

I get:

<p>false</p>
<p>true</p>
<p>false</p>

Then, if I navigate and come back to the page, it inverts:

<p>true</p>
<p>false</p>
<p>true</p>

State of this project

I really like how this project brings together rxjs and react, I just don't see any momentum around it anymore so wanted to check is it dead and/or you looking for new maintainer ?

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.