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

redux-observer-component

v0.1.1

Published

A vanilla JS Redux subscriber with a React-like API.

Downloads

11

Readme

A Redux store observer with a familiar React-like API.

Why?

When using other libraries with lifecycles that don't match or play well with React, it can often be confusing to try to coerce them into the React component API. This library allows you to control these libraries using a familiar React-like API without the need for React or JSX at all (because often they don't actually render anything). They connect to the same Redux store as your React tree, thus allowing these components and your React tree to interact with each other. Because they do not rely on anything but Redux, the library is entirely agnostic to whichever platform you run it on as long as it runs on Javascript.

Why Not?

If you are comfortable with custom rendering logic using React, then this is library is unnecessary. Everything that the ObserverComponent class provides can be achieved with a React component. Think of ObserverComponent as a connected React.Component but its render method always returns null.

Usage Case

This component is great for singleton-type modules like logging, usage tracking, feature flagging, and just about anything that only requires a few instances in your app. You can instantiate those components when your app first loads and they will connect to the Redux store, and you can dispatch actions that they will react to from anywhere else in your app, the components themselves can also dispatch their own actions that your app can conversely react to. The below example lists the exact same functionality implemented with redux-observer-component vs. react and react-redux.

// action-logger.js
import ObserverComponent from 'redux-observer-component'

import logger from 'my-apps-logger-api'

export default class ActionLogger extends ObserverComponent {
    mapStateToProps(state) {
        return {
            lastDispatchedAction = state.dispatchedActions[state.dispatchedActions.length - 1]
        }
    }

    componentDidInitialize() {
        logger.initialize()
    }

    componentDidUpdate(prevProps) {
        if (prevProps.lastDispatchedAction !== this.props.lastDispatchedAction) {
            logger.log(this.props.lastDispatchedAction)
        }
    }
}

// app.js
import ActionLogger from './action-logger'
import reduxStore from './my-app-store'

const actionLogger = new ActionLogger(reduxStore)
// action-logger.js
import React from 'react'
import { connect } from 'react-redux'

import logger from 'my-apps-logger-api'

class ActionLogger extends React.Component {
    componentDidMount() {
        logger.initialize()
    }

    componentDidUpdate(prevProps) {
        if (prevProps.lastDispatchedAction !== this.props.lastDispatchedAction) {
            logger.log(this.props.lastDispatchedAction)
        }
    }

    render() {
        return null
    }
}

const mapStateToProps = state => ({
    lastDispatchedAction = state.dispatchedActions[state.dispatchedActions.length - 1]
})
export default connect(mapStateToProps)(ActionLogger)

// app.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'

import ActionLogger from './action-logger'
import reduxStore from './my-app-store'

const App = () => (
    <Provider store={ reduxStore }>
        <div>
            <ActionLogger />
            <TheRestOfMyApp />
        </div>
    </Provider>
)

render(<App />, document.getElementById('root'))

API

To get started, you can import the component and declare a class that extends from it:

import ObserverComponent from 'redux-observer-component'

class MyComponent extends ObserverComponent {}

Props Management Methods

These optional methods should be defined by you when declaring and implementing the class that extends from ObserverComponent, they are called when the component is first initialized and when the Redux store is updated to map the store state to component-specific props.

mapStateToProps

function mapStateToProps(state: ReduxStateObject): PartialPropsObject

This method receives the Redux store state object and you can return a partial object that will be merged with the return value of mapDispatchToProps to create the props object of your component.

mapDispatchToProps

function mapDispatchToProps(dispatch: ReduxDispatchFunction): PartialPropsObject

This method receives the Redux store dispatch method and you can return a partial object that will be merged with the return value of mapDispatchToProps to create the props object of your component. You can use this to dispatch actions into the Redux store that other components (even React ones) can react to.

Lifecycle Methods

These optional methods should be defined by you when declaring and implementing the class that extends from ObserverComponent, they are called during the lifecycle of the component as it is initialized and disposed.

componentDidInitialize

function componentDidInitialize(): void

This method is called after the component is initialized and it is connected to the Redux store; you can use this to initiate any side effects like calling an API or initializing a third-party library that you are controlling.

componentDidUpdate

function componentDidUpdate(props: PropsObject): void

This method is called after the Redux store is updated. It will only be called if the previous props result from mapStateToProps is different from the current result. A shallow equality check is performed between the two props objects to determine if the component should update.

componentWillDispose

function componentWillDispose(): void

This method is called right after you call dispose on the component instance and right before the component is disconnected from the Redux store and all event hooks are disposed; you can use this to clean up any other side effects initiated since the component has mounted.

Instance API

These methods can be called after the component is initialized to control the lifecycle of the component directly.

dispose

function dispose(): void
This method can be called to dispose a component instance and disconnect it from the Redux store.

on

function on(event: string, cb: EventCallbackFunction): ObserverComponent

This method can be called to hook onto component instance lifecycle events from the outside. They are called right after the corresponding lifecycle methods (componentDidInitialize, etc...). This method returns the instance itself so multiple event hook attachments can be chained. It supports the following events:

  • initialize - the callback receives the instance's props object as the first argument
  • update - the callback receives the instance's props object as the first argument
  • dispose - the callback does not receive any arguments

Example

The following example details a small application that controls the lifecycle of a fictional third-party event tracking library, it will control the lifecycle of the library and react to Redux actions dispatched by the React application and call the tracker API accordingly:

/* store.js */

import { createStore } from 'redux'
// action creators
export const trackAction = actionType => ({
    type: 'TRACK_ACTION',
    payload: { actionType },
})
export const trackActionCommit = action => ({
    type: 'TRACK_ACTION_COMMITTED',
    payload: action,
})

// reducer
const initialState = {
    trackedActions: [],
}
const appReducer = (state = initialState, action) => {
    if (action.type === 'TRACK_ACTION') {
        return {
            ...state,
            trackedActions: [...state.trackedActions, action.payload],
        }
    }
    return state
}

export default createStore(appReducer)
/* action-tracker.js */

import ObserverComponent from 'redux-observer-component'
import Tracker from 'third-party-tracker'
import { trackActionCommit } from './store'

export default class ActionTracker extends ObserverComponent {
    mapStateToProps(state) {
        // gets the last tracked action from the Redux state so we can submit it to the third-party API
        return {
            lastTrackedAction: state.trackedActions[state.trackedActions.length - 1],
        }
    }

    mapDispatchToProps(dispatch) {
        // when the third-party API finishes tracking the action, we let the store know about it
        return {
            commitTrackedAction: action => dispatch(trackActionCommit(action)),
        }
    }

    componentDidInitialize() {
        // initialize the third-party API
        this.tracker = Tracker.initialize()
    }

    componentDidUpdate(prevProps) {
        // if the last tracked action has changed, then we know we need to submit it to the API
        if (prevProps.lastTrackedAction !== this.props.lastTrackedAction) {
            const actionToTrack = this.props.lastTrackedAction
            this.tracker.trackEvent(actionToTrack.actionType).then(() => this.props.commitTrackedAction(actionToTrack))
        }
    }

    componentWillDispose() {
        // if the component is disposed, dispose the tracker as well to prevent memory leaks
        this.tracker = Tracker.dispose()
    }
}
/* app.jsx */

import React from 'react'
import { render } from 'react-dom'
import { connect, Provider } from 'react-redux'

import ActionTracker from './action-tracker'
import { trackAction } from './store'
import store from './store'

const actionTracker = new ActionTracker(store)
actionTracker.on('dispose', () => {
    console.log('Tracker is turned off')
})

// when the button in the app is clicked, it will dispatch an action that the action tracker will pick up
const App = props => (
    <div>
        <button onClick={() => props.trackAction('App button clicked')}>Click me</button>
        <button onClick={() => actionTracker.dispose()}>Stop tracking</button>
    </div>
)
const mapDispatchToProps = dispatch => ({
    trackAction: actionType => dispatch(trackAction(actionType)),
})
const ConnectedApp = connect(null, mapDispatchToProps)(App)

render(
    <Provider store={store}>
        <ConnectedApp />
    </Provider>,
    document.getElementById('root')
)