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-fleo

v1.1.8

Published

Redux framework for asynchronous data

Downloads

8

Readme

redux-fleo

NPM JavaScript Style Guide

redux-fleo is a framework that offers a declarative way to manage asynchronous state in your React redux application. It automates everything related to asynchronous data: tracking requests, updating the store, refreshing you components and more.

  • Very easy to use with minimal code
  • Can be plugged anywhere in your redux store without disturbing your existing app
  • Handles triggering api calls
  • Tracks loading and error status
  • Handles side effects with optimistic updates
  • Automates refetching data
  • Uses batch actions to limit multiple renders and improve performance

Installation

npm install redux-fleo

Definitions

Request: An HTTP request call to a source of data (database, backend server…)

Query: A request that reads data from a source.

Mutation: A request that updates the data on a source.

Service: The function that will send the request and return its result. It can use any client (axios, fetch, xhr…)

Getting started

I. Defining your configuration

A configuration is an object that defines your queries and mutations. It can be flat: defining the queries and mutations directly. Or it can be multi leveled, in order to separate your store into multiple slices.

Flat config:

const flatConfig = {
 queries: [
   {
     name: 'user.fetchMany',
     defaultValue: [],
     service: () => client.get('/users'),
   },
   {
     name: 'users.fetchOne',
     service: (id) => client.get(`/users/${id}`),
   },
 ],
 mutations: [
   {
     name: 'user.create',
     service: (data) => client.post('/users', data),
   },
 ],
};

Multi level config:

const multiLevelConfig = {
 product: {
   queries: [],
   mutations: [
     {
       name: 'product.create',
       service: (data) => client.post('/products', data),
     },
   ],
 },
 customer: {
   queries: [
     {
       name: 'customer.fetchMany',
       defaultValue: [],
       service: () => client.get('/customers'),
     },
   ],
   mutations: [],
 },
};

II. Plugging the fleo reducer into your store

redux-fleo provides the static function Fleo.createReducer, which creates a reducer from a configuration. This reducer can be plugged directly into your redux store as a root reducer or as a slice reducer.

Fleo reducer as root reducer:

import { createStore } from 'redux';
import Fleo from 'redux-fleo';
 
const config = { 
	queries: [/* your queries */], 
	mutations: [/* your mutations */] 
};
const store = createStore(Fleo.createReducer(config));

Fleo reducer in a redux slice:

import { configureStore } from '@reduxjs/toolkit';
import Fleo from 'redux-fleo';
 
const config = { queries: [], mutations: [] };
const store = configureStore({
 reducer: {
   posts: postsReducer,
   comments: commentsReducer,
   fleo: Fleo.createReducer(config, ['fleo']),
 },
});

NB: if redux-fleo’s reducer is not your only reducer, you must provide a path as a second argument to the function Fleo.createReducer

III. Using redux-fleo hooks

redux-fleo offers a varierty of hooks to simplify interacting with your requests and with the store. These hooks handle triggering your service, updating the store with the result and returning the updated store.

We will use the configuration below in the coming examples:

const config = {
  queries: [
    {
      name: 'user.fetchByFilters',
      defaultValue: [],
      service: (gender, situation) => {
        const filters = { gender, situation };
        return axios.post('/users', filters).then(({ data }) => data);
      },
    },
    {
      name: 'user.fetchOne',
      service: (id) => client.get(`/users/${id}`),
    },
    {
      name: 'customer.fetchByCity',
      defaultValue: [],
      service: (page, city) => {
        return client.get(`/customers?page=${page}&city=${city}`);
      },
    },
  ],
  mutations: [
    {
      name: 'user.create',
      service: (data) => client.post('/users', data),
    },
  ],
};

useQuery: Fetching data with a single request

useQuery is a hook that takes the name of a query and a list of parameters. After the component is mounted, it will call the service declared in your configuration by matching the name of the query. It returns the result of the query along with the status. If the list of parameters change, the hook will call the service again to refetch the data.

Example:

import { useQuery } from 'redux-fleo'
const UserList = () => {
 const [users, loading, error] = useQuery('user.fetchByFilters', ['F', 'married']);
 if (loading) {
   return <div>Loading...</div>;
 }
 if (error) {
   return <div>Error!</div>;
 }
 return (
   <div>
     {users.map((user) => (
       <div key={user.id}>{user.name}</div>
     ))}
   </div>
 );
};

useMultiQuery: Fetching data with multiple parallel requests

Sometimes we need to send multiple requests at the same time, but unfortunately we can’t use React hooks inside of loops. useMultiQuery takes the name of a query, a matrix of parameters (an array of an array), and sends multiple requests in parallel with each parameters.

Example:

import { useMultiQuery } from 'redux-fleo'
const UsersListByIds = (props) => {
 const { ids } = props;
 const parametersMatrix = ids.map((id) => [id]);
 const [users, loading, error] = useMultiQuery('user.fetchOne', parametersMatrix);
 if (loading) {
   return <div>Loading...</div>;
 }
 if (error) {
   return <div>Error!</div>;
 }
 return (
   <div>
     {users.map((user) => (
       <div key={user.id}>{user.name}</div>
     ))}
   </div>
 );
};

usePaginatedQuery: Fetching paginated data

usePaginatedQuery returns many useful variables to implement pagination, the most important ones are:

  • loadMore is a function that will call your service and append its the result to the store.
  • data is an array that have 3 informations. The first is an array that contains the result of every call made by loadMore. The second and the third is the loading and error status of all calls.
  • clear is a function that will reset the data, it is used when you want to redo the pagination when a dependency changes.
import { usePaginatedQuery } from 'redux-fleo'
const UserPaginatedList = (props) => {
 const { city } = props;
 const { loadMore, data, clear, loading, error } = usePaginatedQuery('customer.fetchMany');
 
 React.useEffect(() => {
   clear();
   loadMore({ page: 1, city  });
 }, [city]);
 
 if (loading) {
   return <div>Loading...</div>;
 }
 if (error) {
   return <div>Error!</div>;
 }
 return (
   <div>
     <button onClick={() => 
      loadMore({ page: users.length / 10 + 1, city })}
      >
        Load more
     </button>
     {users.map((user) => (
       <div key={user.id}>{user.name}</div>
     ))}
   </div>
 );
};

useMutation: Updating your data

useMutation returns a function that will call your service and dispatch its result in a redux action.

import { useMutation } from 'redux-fleo'
const UserCreate = () => {
 const createUser = useMutation('user.create');
 return <button onClick={() => createUser({ name: `john` })}>Create user</button>;
};

Updating data after a mutation

Injecting mutation data to the store

Usually when we send out a mutation (create, update, delete…) we want to update the result of the queries that are affected by the change without making another call to the server. In order to do that, we will use redux-fleo’s configuration object to specify how we want to update our queries results using the attribute subscribe.

const config = {
 queries: [
   {
     name: 'user.fetchMany',
     defaultValue: [],
     service: () => client.get('/users'),
     subscribe: {
       'user.create': (state, action) => [...state, action.data],
     },
   },
 ],
 mutations: [
   {
     name: 'user.create',
     service: (data) => client.post('/users', data),
   },
 ],
};

subscribe is javascript object, each key represent a mutation name ( or any redux action ), and each corresponding value is a localized reducers that will only update a portion of the store.

Due to the configuration above, each time a user is created, every query 'user.fetchMany' will have its store updated to add the new user, and every components connected to the store (via redux-fleo hooks) will be updated automatically.

Refetching query data after a mutation

Sometimes, the result of the mutation does not have enough data to update our queries results, in this case we are obligated to refetch the whole query. The configuration offers two attributes:

  • refresh will trigger a refetch of the query when a mutation ( or any redux action ) is dispatched, every components will recall the service.
  • refreshIf like refresh, but takes a function that will return a boolean to determine if the component should resend the request or not
const config = {
 queries: [
   {
     name: 'user.fetchMany',
     defaultValue: [],
     service: () => client.get('/users'),
     refresh: ['rates.cancel','SOME_REDUX_ACTION'],
	 refreshIf: {
		 'user.sendNotification': (state, action) => {
			return Boolean(state.find(user=> user.id === action.params[0]))
		 }
	 }
   },
 ],
 mutations: [
   {
     name: 'rates.cancel',
     service: () => client.post('/rates/cancel'),
   },
	{
     name: 'user.sendNotification',
     service: (userId) => client.post(`/users/${userId}/notifications`),
   },
 ],
};

Every time the mutation 'rates.cancel' is called or 'SOME_REDUX_ACTION' is dispatched, all components connected to this query via redux-fleo hooks will refetch their data and update themselves. Every time the mutation 'user.sendNotification' is called, only the components that satisfies the condition of the callback will refetch their data.

Hooks API

useQuery


const config = { queries: [
	{ name: "myquery", service: (a,c)=> {}, defaultValue: 3 } 
]}

const MyComponent = (props) => {
const [
	data, // result of the query, as long as request is not over, value = defaultValue
	loading, // boolean 
	error  // boolean
] = useQuery('myquery', ['a', {'c':'c'}], {disabled: true, dependencies: []})

/*
'myquery'          : name of the query
['a', {'c':'c'}]   : params with which the service will be called
                     => service('a', {'c': c})
{                  : optional config
disabled: true,    : as long as disabled is true, the query will not fire
	                 when it becomes false, the query will fire automatically
					 you can use this if you want to wait for something before call
dependencies       : by default, the depdendencies are the params
                     each time they change, the query will be called again
					 you can introduce your own depdendencies with this

}			
*/		 

useMultiQuery


const config = { queries: [
	{ name: "mymultiquery", service: (a, b)=> {}, defaultValue: 3 } 
]}

const MyComponent = (props) => {
const [
	data, // array of results of each of the request calls
	loading, // boolean 
	error  // boolean
] = useMultiQuery(
	'mymultiquery', [['a1', 'b1'], ['a2', 'b2']],
	{ disabled: true, dependencies: []})

/*
'mymultiquery'                 : name of the query
[['a1', 'b1'], ['a2', 'b2']]   : array of params of each request
                               => service('a1', 'b1') service('a2', 'b2')
{                              : optional config
disabled: true,                : as long as disabled is true, the query will not fire
	                           when it becomes false, the query will fire automatically
					           you can use this if you want to wait for something before call
dependencies                   : by default, the depdendencies are the params
                               each time they change, the query will be called again
				               you can introduce your own depdendencies with this

}
*/	 

usePaginatedQuery

const config = { queries: [
	{ name: "myquery", service: (page)=> page, defaultValue: 3 } 
]}
const { 
	loadMore, // function that calls the service and appends its result to data 
	data,  //  array of results of every page  [1, 2]
	loading, // boolean
	error,  // boolean
	clear,  // function that clears the results (after clear data is [])
	getSentParams // function that returns an array of previous called parameters with loadMore 
} = usePaginatedQuery('myquery');

useEffect(()=>{
	loadMore(1);
	loadMore(2);
}, [])

License

MIT © nachmo5