npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

reactivex-redux

v1.1.5

Published

Redux using Rxjs.

Downloads

15

Readme

RxRedux

Inspired by ReactorKit

RxRedux is a framework for React Web Application to seperate view model from view to make entire codes more testable using Rxjs.

Most of concepts of this project are identical to ReactorKit, You can check the detailed explanation of specific concepts of this framework in its repository.

Installation

npm i reactivex-redux

Reactor (a.k.a. Store)

Reactor is "UI-independent" View-Model layer which manages the state of a view.

Main Concepts

  • Action : Abstraction of the user interaction.
  • Mutation : The stage which can mutate the app's state.
  • State : Abstraction of view's state.

View -> Dispatching Action -> [Action -> Mutation -> State] -> new state -> View Update.

How Reactor works with React-Components.

  1. An element in the component (like a button) dispatches the action.
  2. Reactor's Action receives the action.
  3. Reactor convert this stream to Mutation stream through mutate() method.
  4. Reactor's Mutation stage mutates the states (side effecct happens in this step).
  5. This will call reduce() methods to reduce old state to new state.
  6. new states will be updated in the view component whcih subscribes the Reactor.

Example: Reactor

Reactor is an abstract class, which requires you to implement mutate() and reduce() with Action, Mutation, State types.

Example 1: Creating Type for Action, Mutation, State

Action

export const CLICKTOPIC = "CLICKTOPIC"

export interface CLICKTOPIC {
    type: typeof CLICKTOPIC;
    newTopic: Topic,
}

export type ForumAction = CLICKTOPIC

Mutation

export const SETLOADING = "SETLOADING"
export const FETCHLIST = "FETCHLIST"

export interface SETLOADING {
    type: typeof SETLOADING,
    isLoading: boolean,
}

export interface FETCHLIST {
    type: typeof FETCHLIST,
    list: ListType[],
    page: number
}

type ForumMutation = SETLOADING | FETCHLIST 

State & InitialState

export interface ForumState {
    topic : Topic,
    mode: Mode,
    page: number,
    list: ListType[],
    isLoading:boolean,
    isError:boolean,
    post?: ContentType,
    isLogined: boolean,
}
export const ForumStateInitialState : ForumState = {
    isError: false,
    isLoading: true,
    page: 1,
    mode:"list",
    topic:"tips",
    post: undefined,
    list:[],
    isLogined: false,
}

If you know a better way for typing, go for it. you could check out Redux's action generator library. typesface-actions

Construct Class

You need to implement two method mutate() and reduce().

class ForumReactor extends Reactor<ForumAction, ForumState, ForumMutation> {

   mutate(action: ForumAction): Observable<ForumMutation> {
	... // convert action stream to mutation stream. 
   }
   reduce(state: ForumState, mutation: ForumMutation) {
        ... // reduce old state to new state.
   }
} 

example of mutate() and reduce()

Using Rxjs's concat() methods, you can serialize your stream.

mutate(...){
....
case "CLICKPAGE":
    return concat(
	// 1. loading on
	of<ForumMutation>({type:"SETLOADING", isLoading: true}),
	// 2. fetching List
	this.fetchList(this.currentState.topic, action.newPage).pipe(
	    takeUntil(this.action.pipe(filter(value => value === action))),
	    map<ListType[], ForumMutation>( res => {
		return {type:"FETCHLIST", list: res, page: 1 } 
	    })
	),
	// 3. Loading off
	of<ForumMutation>({type:"SETLOADING", isLoading: false}),
    }
   ...
reduce(...) {
...
    case "FETCHLIST":
	newState.isLoading = false;
	if (mutation.list.length === 0){
	    newState.isError = true;
	    return newState
	} else {
	    newState.list = mutation.list;
	    newState.page = mutation.page;
	    return newState
	}
 ....
}

Usage

const reactor = new ForumReactor(initialState)
...
//dispatching action.
reactor.dispatch({type:"CLICKPAGE"})

//subscribe reaction from Reactor
reactor.state.subscribe( res => console.log(res) )

Methods

Constructor

| parameter | required | default | |---------------|----------|---------| | initialState | true | none | | isStubEnabled | false | false |

mutate() and reduce()

mutate() receives a dispatched action, and generates an Observable<Mutation>

reduce() reduces old states with Mutation to new states.

See above for detailed examples.

transform()

Before your stream is delivered to one of the stage, you can transform your observable stream.

In this example, messages are logged before your states updated. (i.e. before your stream get to the State stage)

transformState(state: Observable<State>): Observable<State> {
	return state.pipe( tap( _ => console.log("state update!"))
}

View-Binding

RxRedux provides a way to bind your Reactor with React-Component.

withReactor(component)

As of 1.0.4, RxRedux provides higher order component called withReactor()

| parameter | required | default | |---------------|----------|---------| | Component | true | none | | parentFilterMapper | false | false | | transfromStateStreamFromThisComponent | false | true | | skipSync | false | true |

This HOC automatically subscribes reactor's from parents component.

  • parentFilterMapper : You don't have to subscribe all of the properties in state object. you can specify keys you want to subscribe as a mapper function.
  • transfromStateStreamFromThisComponent : If this is true, the mapper function will automatically be applied to child component which is wrapped by withReactor()
  • skipSync : A reactor emits a current state when you start subscribing (i.e. when withReactor(Component) is mounted). You can ignore this call to avoid redundant re-rendering.

(Auomatic) Testing

Testing for Reactor.

You can easily testing Reactor since it is UI-Independent.

for example,

1. Action testing

it('click write -> mode change test ', done  => {
	
	// create reactor
	reactor = new  ForumReactor(initialState);
	
	// dispatch action.
	reactor.dispatch({type:"CLICKWRITE"})
	
	// check its state.
	expect(reactor.currentState.mode).toBe("edit")
	done();
})

2. Testing containing side effect (API Call)

it('5. side effect : click topic -> topic change -> loading -> (success) -> loading -> isError false test', done  => {

	// API Request Mock-up.
	moxios.wait(() => {
		const  request = moxios.requests.mostRecent()
		request.respondWith({ status:  200, response:  listResultMockup }) 
	})

	// create reactor
	reactor = new  ForumReactor(initialState);


	let  state_change = 0;

	//subscribe reactor to check we received expected value.
	from(reactor.state).subscribe(
		
		state  => {
		if(state_change === 1) {
		    expect(state.topic).toBe("tips");
		} else  if (state_change === 2) {
		    expect(state.isLoading).toBeTruthy();
		} else  if (state_change === 3) {
		    expect(state.list.length).toBe(2);
		} else  if (state_change === 4) {
		    expect(state.isLoading).toBeFalsy();
		    expect(state.isError).toBeFalsy();
		    done();
		} else {
		    done.fail();
		}
		    state_change++;
		}
	    )
	    reactor.action.next({type:"CLICKTOPIC", newTopic:  "tips"})
	})

Testing for Component<->Reactor.

Component<->Reactor testing can be tested with Stub

What is Stub?

Stub is a testing utility implemented in Reactor. Stub can log every actions you've dispatched from Component, and it can force state changes.

To enable Stub, you need to set isStubEnbabled as true in constuctor arguments.

const reactor = new ForumReactor(initialState, true)

Example

it('9. View Binding Check', done  => {
	
	// create Reactor with StubEnableMode on.
	reactor = new  ForumReactor(initialState, true);

	// mount by `Enzyme`
	const  wrapper = shallow(<R6Table></R6Table>);

	// check reactor exist to bind with in original code.
	expect((wrapper.instance() as  any).reactor).not.toBe(undefined);

	// inject new reactor in the code.
	(wrapper.instance() as  any).reactor = reactor;

	// you can remount your component or call custom bind() function in your component.
	(wrapper.instance() as  any).bind(reactor);

	// simulate button action using `enzyme` testing.
	wrapper.find('button').at(0).simulate('click')

	// stub can log every actions from view components.
	expect(reactor.stub.lastAction.type).toBe("CLICKBACK");

	// stub also can force new state.
	reactor.stub.state.next({...initialState, mode :  "edit"});

	// you can check new state in Component.
	expect((wrapper.state() as  ForumState).mode).toBe("edit");
	
	done();
})

Additional Tips.

Scheuder

You can specify RxJs's scheduler when you defining Reactor class, but it must be a serial queue.

ReactorHook (experimental)

RxRedux supports functional components using custom hooks, but you need to define new class which extends ReactorHook<Action,State,Mutation>

ReactorHook is same as Reactor class, but it has depedency on react library's native hook methods.

const [reactor, currentState] = SomeHookReactor.use(initialState)

This will also update your functional view component every time current state changes.

RxjsExtension

This provides some Rxjs operator extension. catchErrorJustReturn() & catchErrorReturnEmpty& deepDistinctUntilChanged()

ReactorGroup

This injects Reactor its child.

<ReactorGroup reactor={this.reactor}>
	<R6CommunityNavigation></R6CommunityNavigation>
	<R6List></R6List>
	<R6ListFooter></R6ListFooter>
	<R6PostWrite></R6PostWrite>
	<R6Post></R6Post>
</ReactorGroup>

To-do list

  • [x] initial Commit
  • [X] 비동기 대응.
  • [X] 비동기 처리 에러.
  • [X] 프로젝트 테스트 코드 추가 및 테스트.
  • [X] 테스트 기능.
  • [X] 문서작성.
  • [X] 뷰 .
  • [X] 훅기능 추가 (beta)
  • [ ] 코드 테스트.
  • [ ] 디버깅 기능 추가.

Dependency

  • Rxjs
  • lodash
  • react

업데이트 내역

  • 1.0.4 : withReactor update.
  • 1.0.4 : bug-fix : state is undeliberately mutated.
  • Readme update.
  • 글로벌 스토어 삭제, HOC 바인딩 방식, ReactorGroup & Rxjsextension추가.