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

questrar

v2.0.1

Published

A simple React request state manager and request reporting

Downloads

6

Readme

Questrar is a simple React request state tracker for managing states of requests.

npm version Maintainability Test Coverage Build Status

Installation

yarn add questrar 

npm install questrar

Why Questrar?

The state of an app request is necessary to user's interactivity and experience. Continually, App needs to fetch data from server or do a complex computation. It's important to let the user know if request is still loading, failed or successful. This can be repetitive and hard to work around with and you are likely going to need in every application which fetches data from server or anywhere else.

Questrar alleviates those needs into a simple <Request /> wrapper - with the help of React's composability making it easy to control, track request, show loading icons and failed messages and other UI transformations based on a state of specific request.

Working with redux

See API section

A request state is typed as

// @flow
type RequestState = {
    id: RequestId,
    pending: boolean,
    success: boolean,
    failed: boolean, 
    
    clean: boolean,
 
    successCount: number, // default 0
    failureCount: number, // default 0
    
    message?: any | { title: any, body: any },
    
}

type RequestProp = {
    data: RequestState,
    actions: {
       pending: (id: string | number, message?: any) => void, // set request state of id to pending
       failed: (id: string | number, message?: any) => void,  // set request state of id to failed
       success: (id: string | number, message?: any) => void, // set request state of id to success
       remove: (id: string) => void, //remove completely
       clean: (id: string) => void, // set request as untouched
       dirty: (id: string) => void, //  set request as touched
    }    
}

A default request state (initial request state or provided when request state is not found by id)

const defaultRequestState: RequestState = {
    id: 'newRequestId',
    pending: false,
    success: false,
    failed: false,
    successCount: 0,
    failureCount: 0,
    clean: true,
    message: undefined
}

A requestState is simple to use in a React component by wrapping the component tree with the Provider.

stateProvider prop is required.

import { Provider } from 'questrar';
import type { StateProvider } from 'questrar';
import defaultRequestStateProvider  from 'questrar/store';

const stateProvider: StateProvider = defaultRequestStateProvider();

export const AppMain = () => (
  <Provider stateProvider={stateProvider}>
    <App />
  </Provider>
)

Now you can use the Request component in any of your components anywhere deep, anyhow deep.

const App = () => (
<Request id="id">
  <View />
</Request>
)

A Request component requires an id which will be used to track a request. An id is recommended to be a string or number. Symbol should be used carefully since Symbol() creates a new different object every time.

A request id should be a bit unique to a particular subject context. For instance, if you have a user account which can have many queries to api, (fetch user, edit user, delete user, upload photo), its bearable not to use a single request id (which obviously may be userId) for all these actions on the user. Instead, you can just concat to do

const fetchProfileRequestId = `${userId}_fetch`;
const deleteUserId = `${userId}_delete`

The consequence of using same id looks like showing a pending delete when indeed user is uploading a photo.

You don't have to create a request state inside a component. You just provide an id. If the id has no requestState, a default one is returned but never saved. A request state is saved if there is an update action on the request state

import { Request } from 'questrar'; 

export const UserProfile = ({ userId, data }) => {
    
    return (
      <Request id={userId} pendOnMount >
        <ProfileView data={data} />
      </Request>
    );
}

Where you explicitly want to access the request state object, you can use the inject prop on Request;

<Request id={userId} pendOnMount inject>
  <ProfileView data={data} />
</Request>

inject can be a boolean or a function with signature (request: RequestProp) => Object which would be received by the underlying component.

 export const UserProfile = ({ userId, data }) => {
   
 const mapToButtonProps = (request: RequestProp) => {
    return {
      loading: request.data.pending,
      disable: request.data.pending || request.data.success || request.data.failureCount > 5,//disable after 5 request failures
      onClick: () => request.actions.success(request.data.id)
    };
 };
 
 return (
  <Request id={stringOrNumberId} inject={mapToButtonProps} >
    <Button >Fetch Profile</Button>
  </Request>
 );
}

At some point where the Request component isn't that helpful to use with your component, you can try with the withRequest HOC and expect the request state as a prop.

import { withRequest } from 'questrar';

type Props = {
  userId: string,
  data: Object,
  request: RequestProp
}

 const UserProfile = ({ userId, data, request }: Prop) => {
    if(request.data.dirty){
        const requestId = request.data.id;
        request.actions.remove(requestId)
    }
    
    return (
      <div>
        {request.data.failed && <Banner content={request.data.message} />}
        {request.data.success && <ProfileView data={data} />}
       <Button
        disable={request.data.pending || request.data.success}
        loading={request.data.pending}
       >
        Fetch User Profile
       </Button>
      </div>
    );
};

export default withRequest({ id: (props) => props.userId })(UserProfile);

withRequest takes these options:

type RequestComponentOptions = {
  id?: RequestId | Array<RequestId> | (props: Props) => (RequestId | Array<RequestId>),
  mergeIdSources?: boolean, //  merge options.id and props.id (if there is any),
  stateProvider: StateProvider, // this stateProvider overrides the Provider stateProvider 
}

export default withRequest(options?: RequestComponentOptions)(Component)

withRequest takes an optional single id or optional list of ids or optional function that can convert the props of the wrapped component to a single id or list of ids.

withRequest automatically takes options.id (if there is) if props.id is empty. Thats to say props.id has precedence over options.id. However, if you require props.id and options.id to merge, set mergeIdSources to true. This will merge props.id and options.id with no duplicates and return an object typed as RequestMapProp similar to RequestProp.

type RequestMapProp = {
   data: { [id: RequestId ]: RequestState }, //instead of data: RequestState
   actions: {
      pending: (id: string | number, message?: any) => void, //set request state of id to pending
      failed: (id: string | number, message?: any) => void,  //set request state of id to failed
      success: (id: string | number, message?: any) => void
   } 
}

Any of props.id or options.id is optional. If no id is found, an empty object is returned

Note: If a single id is provided by props.id or options.id or both(if mergeIdSources is true), a RequestProp is returned instead of RequestMapProp

Feedback

Any feedback and PR contributions will be appreciated regardless.

Credits

  • Inspired by Christian Kaps @akkie