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

r3-library

v1.2.1

Published

A library for simplifying resource actions in React / Redux application.

Downloads

30

Readme

r3 Library

r3-library (short for React-Redux-Resource) allows users to create and dispatch both synchronous and asynchronous actions in an object oriented fashion.

Features

  • Easily create and dispatch both synchronous and asynchronous actions
  • Built-in async CRUD actions
  • Registering new actions automatically updates your reducers
  • Consistent, scoped naming conventions allows for easy scaling and maintenance and prevents reducer conflicts
  • Gracefully handles errors by dispatching an error action to our store so you can render errors to your users
  • Simple remote requests using superagent
  • No Redux middleware required
  • Chainable commands

View a demo app that uses r3: https://react-workout-tracker.herokuapp.com/

View the source code here: https://github.com/cassaram09/workout-tracker-v2

Installation

To install, run: npm install r3-library --save

Make sure you have redux installed.

1) To create a new Resource, pass an options object during initialization with at least a name property, then export the resource:

A simple example, where we create a new Resource, complete with the default CRUD actions.

// widgetResource.js    
import Resource from 'r3-library'  
  
const Widget = new Resource({  
  name: 'widget',  
})  

Widget.registerRemoteActions();
  
export default Widget;    

A more complex example, where we create a new Resource and register some custom actions.

// widgetResource.js 
import Resource from 'r3-library'

const Widget = new Resource({
  name: 'widget', 
  url: 'http://localhost:3000/widgets', 
  headers: {'Content-Type': "application/json"}, 
  state: []
})

Widget.registerRemoteActions();

// Async action with custom request
Widget.registerAsync({
  name: 'GET_CURRENT_WIDGET', 
  reducerFn:(state, action) => { return action.data }, 
  resourceFn: (data) => {
    return new Promise((resolve, reject) => {
      request
      .post(`/image-upload`)
      .attach('image', data.file)
      .end(function(error, response){
        resolve(response);
      });
    });
  }
})

// Async action with generated request
Widget.registerAsync({
  name: 'GET_MY_WIDGET', 
  url: Widget.url + '/my-widget/:id',
  method: 'GET',
  reducerFn:(state, action) => { return action.data }, 
})

// Sync action
Widget.registerSync({
  name: 'GET_LOCAL_WIDGET', 
  reducerFn: (state, action) => { return action.data }
})

export default Widget;

2) Add the Resource's reducer to the your store:

// store.js
import {combineReducers} from 'redux';
import { createStore } from 'redux'
import Widget from '/src/app/widget/widgetResource'

const rootReducer = combineReducers({
  widgets: Widget.reducer,
})

function configureStore(){
  return createStore(rootReducer)
};

export default configureStore();

3) Configure the Resource module to use your Store's dispatch function:

// index.js
import React from 'react';
import { Provider } from 'react-redux'; 

import Store from '/src/app/store/store'
import Resource from '/src/app/utils/resource';

import App from '/src/app/app';

Resource.configure({dispatch: Store.dispatch})

ReactDOM.render(
  <Provider store={Store}>
    <App />
  </Provider>,
   document.getElementById('root')
);

4) Import the Resource into the appropriate file and start dispatching actions!

// widgetPage.js
import React, {Component} from 'react';  
import {connect} from 'react-redux';  
import Widget from '/src/app/widget/widgetResource'

class WidgetsPage extends Component {
  componentWillMount(){
    Widget.dispatchAsync('$QUERY')
  }

  componentWillUnmount(){
    Widget.dispatchSync('$CLEAR_ERRORS')
  }

  render(){
    if ( this.props.widgetsErrors ){
      const widgets = this.prpos.widgetsErrors.map(error => {
        return (<div className='widget'>{error.detail}</div>)
      })
    }

    if ( this.props.widgetsData ){
      const widgets = this.prpos.widgetsData.map(widget => {
        return (<div className='widget'>{widget.quantity}</div>)
      })
    }

    return (
      <div className='widgets-page'>
        {errors}
        {widgets}
      </div>
    )
  }

}

function mapStateToProps(state, ownProps) { 
  return {
    widgetsData: state.widgets.data,
    widgetsErrors: state.widgets.errors
  };
};

export default connect(mapStateToProps)(WidgetsPage);

Docs

Working with Async actions

Async (typically request to remote servers, but any async, promise based function should be fine) actions to query a server should use the super-agent module, as r3 is designed to work with seamlessly with that module. I've found that it's more flexible then the Fetch API and there was less I had to code from scratch. Remote actions should be wrapped in a promise, otherwise r3 will throw an error.

Example of a custom async server request:

const uploadImage = (data) => {
  return new Promise((resolve, reject) => {
    request
    .post(`/image-upload`)
    .set('AUTHORIZATION', `Bearer ${sessionStorage.jwt}`)
    .attach('image', data.file)
    .end(function(error, response){
      resolve(response);
    });
  });
} 

When registering a new Async action, if the resourceFn is ommitted, r3 will apply a default resource action.

Default Remote Actions

r3 has several built in RESTful functions to make remote requests easier. They are:

  • $QUERY
  • $GET
  • $CREATE
  • $UPDATE
  • $DELETE

These predefined functions make RESTful requests to a server simple. Custom requests can generated by calling .registerAsync. Even more customization is possible when passing a resouceFn to the .registerAsync function.

Working with Sync actions

Sync actions use standard Redux actions. When registering a new sync action, we declare the name of the action - all uppercase, as per Redux convention - which will also be used as the name of our case. The reducerFn is added to our reducer.

Error handling

r3 comes equipped with a lightweight error handling system. When a new Resource is created, it's state is an object with two keys:
{data: [], errors: []}
The data key holds object data and functions like a normal Redux state. The errors key is used in conjunction with the built in $ERROR and $CLEAR_ERRORS actions. If a dispatched action's response returns an error ( !response.ok ), an error is thrown ( and caught ). The $ERROR action is then dispatched to our store, where the error is added to the error key. This value can be accessed in our component and rendered to your users like any other property in our store.

The $CLEAR_ERRORS action should be dispatched whnen a component unmounts to reset the errors key or can be used during another point in the component lifecycle to clear errors.

URL Params

When using the dynamically created request or default CRUD requests, r3 will attempt to parse any url params. For example, if we're working with a RESTful resouce of a Widget, the url to get a widget might be ('/widgets/:id'). If we want to fetch a widget at '/widgets/12', our dispatched action should include an id parameter in top level of the the data object:

WidgetResource.dispatchAsync('$GET', {id: 12})

If our param is something different (eg :slug), then our dispatched action would look like:

WidgetResource.dispatchAsync('GET_BY_SLUG', {slug: 'my-slug'})

If the parameter is not in the top level of the hash, r3 will attempt to recursively search through the data object and parse the param. However, this is not recommended and the param should always be included in the first level of the data object.

API

.configure(options)

options = {  
  dispatch: func.isRequired,  
}  

Assign the store's dispatch function to our Resource. This allows us to skip binding our action creators. Call this in your index.js file before your render function.

.registerAsync(options)

Register a new Asynchronous action. Asynchronous actions dispatch an sync request, then once the response is received, dispatches the data to the store or dispatches an Error action. This creates the custom resource action and reducer action, and adds both to the current resource.

options = {  
  name: string.isRequired,  
  url: string.isRequired, 
  method: string.isRequired,  
  reducerFn: func.isRequired,
  resource: func  
}  

.registerSync(options)

Register a new Synchronous action. Synchronous actions dispatch actions directly to the store. This creates a new reducer action, and adds it to the current resource.

options = {  
  name: string.isRequired,  
  reducerFn: func.isRequired  
}  

.updateReducerAction(name, reducerFn)

name: string.isRequired
reducerFn: func.isRequired

Updates a reducer action (such as a default reducer action). Accepts the name of the reducer case (not prefixed), and the callback reducer function.

.updateResourceAction(name, resourceFn)

name: string.isRequired
resourceFn: func

Updates a resource action (such as a default resource action). Accepts the name of the resource action (not prefixed), and the callback resource function.

.registerRemoteActions()

Registers the default remote action and reducers for CRUD operations: query(index), get(individual resource), create, update, and delete. See Docs for reference on how these functions work.

To Do

  • Create CLI for generating a new resource
  • More robust test coverage

Contributing

If you'd like to contribute, fork this repo and create a new feature branch with your changes. Write some tests for the code, and make them pass. Once you submit a pull request, I'll review your code and commit it if everything looks good.