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

react-scoped-provider

v1.3.3

Published

Library for DI in react without the need of self-creating context everytime.

Downloads

45

Readme

React Scoped Provider

NPM version codecov npm bundle size Build License

I've always appreciated the concept of scoped dependency injection because it offers a clear and understandable way to manage resources throughout the lifecycle of a component.

In particular, resources associated with the lifecycle of a view should be disposed of when the view is unmounted.

Features

  • Enables dependency injection without the need to manually create a Context for each DataType.
  • Handles scenarios where dealing with multiple nested Contexts becomes cumbersome (often referred to as "Context hell").
  • Provides a way to expose a value to a subtree and easily trigger a re-render with different data, similar to the traditional Context API.
  • Offers the ability to expose a value to a subtree and maintain its persistence between renders.
  • Ensures cleanup of provided resources when the Provider is unmounted.

Usage

Provider

Provide a value

Expose a value to a subtree, this is closest to a traditional Context API's Provider. When used with useState, changes in this value may make dependent re-render with new data.

<Provider source={"value"}>
    <Children />
</Provider>,

Provide a persisted instance

Expose a persisted instance between renders. This value will be kept with useRef and won't change between re-renders.

<Provider source={()=> "value" }>
    <Children />
</Provider>,

These 2 two types only should be used one at a time for each Provider and kept it that way throughout the component's lifeCycle. Use them interchangeably may cause unexpected behavior.

Named Provider

Out of the box, the Provider component will auto infer the type of each passed-in value for primitives and class, then uses them as the name to query instances.

Generally, it will create a map in which the key will be the name of the type, and the value will be the actual injected value.

"tadaa" => "String" // typeof string
false => "Boolean" // typeof boolean
5 => "Number" // typeof number
new ThisIsClassName() => "ThisIsClassName" // Have to be defined with a `class` keyword

You can easily overwrite this default name with the parameter name in ProviderProps.

<Provider source={"value"} name="value-key">
    <Children />
</Provider>,

For TypeScript type and interface, as these will be stripped when compiling to JavaScript, the custom name parameter becomes crucial for retrieving specified data.

type CustomType = {value: string};
const data: CustomType = {value: "text"};

<Provider source={data} name="custom-data-type">
    <Children />
</Provider>,

This mechanism is important for retrieving the value later on, especially when dealing with complex type systems in TypeScript.

Abstract Provider

For class instance, Provider will automatically infer name of the type based on provided value. But in architecture point of view, there are times we would need to hide the type of implementation and expose abstraction superclass only. To do this, we can use the name parameter in Provider

class SuperClass {}

class SubClass extends SuperClasss {}

// map query key will be 'SuperClass'
<Provider name={SuperClass.name} source={new SubClass()}>
    <Children />
</Provider>,

// hooks can then use the type `SuperClass` to retrieve value too

Scoped data overwrite

Be warned that providing duplicated name values for data may lead to overwrite behavior. If multiple instances of Provider use the same name for their data, the previous provided value with the same name will be replaced by the subsequent one.

<Provider source={'firstValue'} name='sharedName'>
  <Provider source={'secondValue'} name='sharedName'>
    <Children />
  </Provider>
</Provider>

In the above example, the second Provider will replace the value provided by the first one, makes any children components using it will receive the value secondValue.

Clean up

Each Provider comes with a cleanUp function inside ProviderProps, providing a mechanism to clean up, dispose of, or perform operations on the data when the Provider is unmounted from the render-tree.

The cleanUp function is executed automatically when the Provider is unmounted to cleanup created resources, ensuring proper handling of resources associated with the provided data.

This function is only called for Provider with Create<T> or ()=> T in the source params to cleanup persisted value. If you use Provider with a source of type T, cleanup function won't be called for that resources

<Provider source={() => 'value'} cleanUp={(value : string)=> {
    // do something here
}}>
    <Children />
</Provider>,

You can customize the cleanUp function based on your specific needs, allowing you to perform cleanup operations tailored to the nature of the provided data.

Retrieve data

Before retrieving data using hooks, ensure that the specified type is provided within the current subtree. If not, attempting to retrieve the data will result in an error, specifically the ResourcesNotProvidedError.

This error serves as a helpful reminder to verify that the necessary data has been provided to the current subtree.

Retrieving Primitive and Class Types

To retrieve primitive and class types, you can use the useProvider hook:

const number = useProvider(Number) // return nearest provided number
const boolean = useProvider(Boolean) // return nearest provided boolean
const text = useProvider(String) // return nearest provided string
const customData = useProvider(ThisIsClassName) // return nearest provided instance of ThisIsClassName

Note that ThisIsClassName must be defined with the keyword class; this function is not compatible with type and interface in TypeScript.

By default, each of the above types will be converted to the default name used as key to query data:

Number => "Number"
Boolean => "Boolean"
String => "String"
ThisIsClassName => "ThisIsClassName"

You can customize this behavior by using the name parameter in ProviderProps. If you do, the data must be queried using the same specified name.

<Provider source={42} name="MyNumber">
    <Children />
</Provider>,

const myNumber = useProvider(Number, 'MyNumber') // return 42

Retrieving Any Types by Name

Due to the fact that type and interface in TypeScript will be stripped during compilation, you can't directly use and pass them into the useProvider hook. Instead, you can utilize the useNamedProvider hook. The only difference is that you need to manually provide the name and type.

This hook works with a variety of DataType, including those supported by useProvider.

<Provider source={42} name="MyNumber">
    <Children />
</Provider>,

const myNumber = useNamedProvider<number>('MyNumber') // return 42
type CustomType = {value: string};
const data: CustomType = {value: "text"};

<Provider source={data} name="custom-data-type">
    <Children />
</Provider>,

const myNumber = useNamedProvider<CustomType>('custom-data-type') // return {value: "text"}

This allows you to dynamically retrieve values based on the specified name and type.

Consumer Component

Similar to the Context API, this library provides a Consumer component that corresponds to the hooks introduced above.

For both primitive and class types, akin to the useProvider hook, you can use:

<Consumer ctor={Number}>{
  (number) =>
    // children

}</Consumer>

<Consumer ctor={Boolean}>{
  (boolean) =>
    // children

}</Consumer>

<Consumer ctor={String}>{
  (text) =>
    // children

}</Consumer>

<Consumer ctor={Counter}>{
  (counter) =>
    // children

}</Consumer>

<Consumer name='customCounterName' ctor={Counter}>{
  (counter) =>
    // children

}</Consumer>

For custom type, interface, or any of the types supported by the useNamedProvider hook, you can utilize the Consumer component as follows:

<Consumer<number> name="customNumberName">{
  (number) =>
    // children

}</Consumer>

<Consumer<boolean> name="customBooleanName">{
  (boolean) =>
    // children

}</Consumer>

<Consumer<string> name="customTextName">{
  (text) =>
    // children

}</Consumer>

<Consumer<Counter> name="customCounterName">{
  (counter) =>
    // children

}</Consumer>

<Consumer<CustomDataType> name='customDataType'>{
  (customData) =>
    // children

}</Consumer>

Allow undefined

Even though throwing ResourcesNotProvidedError when resources can't be located is the default behavior. You can change this to make Consumer, useNamedProvider and useProvider to return undefined instead with the allowUndef flag.

// return type of `customData` will become `CustomData | undefined`
const customData = useProvider(CustomData, { allowUndef: true, name: 'custom' })
// return type of `text` will become `string | undefined`
const text = useNamedProvider<string>('test-text', { allowUndef: true })

Try changing these flag to false, you will see returned type get updated.

For Consumer:

// type of `customData` will become `CustomData | undefined`
<Consumer<CustomDataType> allowUndef name='customDataType'>{
  (customData) =>
    // children

}</Consumer>
// type of `counter` will become `Counter | undefined`
<Consumer allowUndef name='customCounterName' ctor={Counter}>{
  (counter) =>
    // children

}</Consumer>

Context hell

Deeply nested components wrapping each other to provide values can become hard to read and maintain, making the code more difficult to change the order, or add/remove providers. This challenge is commonly known as "Context hell."

This complexity can be simplified using the MultiProvider component.

<MultiProvider
  providers={[
    <Provider source={0} />,
    <Provider source={'test-string'} />,
    <Provider source={true} />,
    <Provider source={() => new Counter(5)} />,
  ]}
>
  <Children />
</MultiProvider>

The above is equivalent to the traditional deeply nested structure:

<Provider source={0}>
  <Provider source={'test-string'}>
    <Provider source={true}>
      <Provider source={() => new Counter(5)}>
        <Children /> // Children using provided values.
      </Provider>
    </Provider>
  </Provider>
</Provider>

Using MultiProvider improves code readability and maintainability, making it easier to manage a large number of providers.

Conclusion

Thank you for exploring and learning about the features and capabilities of the React Scoped Provider library. With its intuitive API and components like Provider, MultiProvider, useProvider, and useNamedProvider, managing and injecting dependencies in your React application becomes more flexible and straightforward.

Whether you're dealing with complex type systems in TypeScript, avoiding "Context hell" with MultiProvider, or providing and retrieving data with specific names, React Scoped Provider aims to enhance the developer experience by offering a versatile and scalable solution.

I hope that this library proves valuable in your React projects. If you have any questions, encounter issues, or want to contribute, feel free to reach out to me. Happy coding!