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

@adamdickinson/react-service

v2.0.0

Published

Expose internal application APIs as a service.

Downloads

8,784

Readme

React Service

Internal APIs for your application.

A React Service is one answer to the questions:

How should I manage state in my application? Redux has a lot of boilerplate and slows us down, MobX is tricky to debug and is kinda magic, component state + prop drilling is annoying and adds unnecessary complexity, React context is annoying to set up. What do?

Installation

npm install @adamdickinson/react-service

or

yarn add @adamdickinson/react-service

API

There's only one method you really need to know about:

createService(hook) -> [ProviderComponent, UseServiceHook]

Creates the provider and access hook for a new service.

Parameters

hook

A react hook function used to expose data.

Returns

[ProviderComponent, UseServiceHook]

Much like React's useState method, will return two values - the definition of a provider component, and the definition of a service hook.

The provider component is designed to be rendered in your application, providing access to the defined service to all child components.

The service hook is designed to be used within child components as needed to access service data.

Usage

Let's cut the discourse - it's demo time. Here are a handful of ways to implement what is effectively the same thing:

Basic Usage

As a contrived example, let's say we want to make a random number service.

// 1. We define the service
const [RandomProvider, useRandom] = createService(({ max }: { max: number }) => {
  const [number, setNumber] = useState<number>()
  return {
    update: () => setNumber(Math.floor(Math.random() * max)),
    number,
  }
})

// 2. We use the service with the `useRandom` hook in some components
const Displayer = () => {
  const random = useRandom()
  if (!random) return null
  return <p>The number is {random.number}</p>
}

const Changer = () => {
  const random = useRandom()
  if (!random) return null
  return <button onClick={random.update}>Randomize!</button>
}

// 3. We expose the service to those components that use it by rendering a parent `RandomProvider` component
ReactDOM.render(
  <RandomProvider max={100}>
    <Displayer />
    <Changer />
  </RandomProvider>,
  document.getElementById('root')
)

Usage as an Auth Service

Here's how we define a service. We start by simply and concisely defining data and processes...

// src/services/auth.ts
import createService from '@adamdickinson/react-service'
import store from 'store'
import { useEffect, useState } from 'react'

// Define our contract
interface User {
  id: string
  firstName: string
  lastName: string
  email: string
}

interface AuthAPI {
  logIn(username: string, password: string): Promise<User>
  logOut(): void
  user?: User
}

// Define our API as a hook so we can leverage React functionality
const useAuthAPI = ({ serverUrl }: { serverUrl: string }) => {
  const [user, setUser] = useState<User>()

  const onChangeUser = (newUser?: User) => {
    // Set local state, and persist change to storage
    store.set('user', newUser)
    setUser(newUser)
  }

  useEffect(() => {
    // Restore existing user from persistent storage into local state
    const existingUser = store.get('user')
    if (existingUser) {
      setUser(existingUser)
    }
  }, [])

  const api: AuthAPI = {
    logIn: async (username: string, password: string) => {
      const response = await fetch(`${serverUrl}/login`)
      if (!response.ok) {
        throw new Error(response.statusText)
      }

      onChangeUser(await response.json())
    },
    logOut: () => onChangeUser(undefined),
    user,
  }

  return api
}

const [AuthService, useAuth] = createService<AuthAPI>(useAuthAPI)

export { AuthService, useAuth }

... then we expose it to our application...

// src/index.tsx
import ReactDOM from 'react-dom'
import App from './App'
import { AuthService } from './services/auth'

ReactDOM.render(
  <AuthService serverUrl="http://api.myapp.com">
    <App />
  </AuthService>,
  document.getElementById('root')
)

... and finally, start using it wherever we need it!

// src/App.tsx
import { useAuth } from './services/auth'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'

export default () => {
  const auth = useAuth()
  return (
    <Router>
      <Switch>
        // Unauthed routes
        {!auth?.user && <Route path="/" component={LogIn} />}
        // Authed routes
        {!!auth?.user && <Route path="/" component={Welcome} />}
      </Switch>
    </Router>
  )
}
// src/containers/LogIn.tsx
import { useAuth } from './services/auth'
import { useState } from 'react'

export default () => {
  const auth = useAuth()
  const [loggingIn, setLoggingIn] = useState(false)
  const [error, setError] = useState('')

  const login = async () => {
    setLoggingIn(true)
    try {
      await auth.logIn('me', 'my-pass')
    } catch (error) {
      setError(error?.message || error)
      setLoggingIn(false)
    }
  }

  return (
    <>
      {error && <p>{error}</p>}
      <button onClick={login}>Log In</button>
    </>
  )
}
// src/containers/Welcome.tsx
import { useAuth } from './services/auth'

export default () => {
  const auth = useAuth()
  return `Welcome ${auth?.user?.firstName}!`
}

Limitations

Any good library will identify where it falls down and where it wants to improve. In an attempt to make this a good library too, here goes:

Too many services spoil the broth

Using multiple services at once poses some interesting challenges:

1. Nesting Nightmares

Look at this (if you dare):

const App = ({ children }) => (
  <AuthProvider>
    <APIProvider>
      <CommentsProvider>
        <PlaylistProvider>{children}</PlaylistProvider>
      </CommentsProvider>
    </APIProvider>
  </AuthProvider>
)

It only uses four randomly named services, but it's already becoming a nested mess.

2. Inter-service Relationships

Let's say we have an Auth service that uses an API service to communicate on its behalf, but the API service needs an auth token. Very quickly, we discover that we have a circular dependency. There are ways to handle this - namely delegating the task of token association to the caller rather than have the API service itself associate it to a call, but that's adding more complexity.

3. Performance Risks

While no performance issues have arisen in our extensive experimentation with this library, there is a risk of running extra renders when they are simply not required, again due to the nested nature of services, however more research is required here to confirm or deny this to any real extent.

There ain't much structure

Redux has set standard processes when it comes to its actions and reducers. It's all explicit and all verbose. While this is often seen as a curse, it can also be a blessing as it also defines a structure that many developers can work with simultaneously.

There is very little boilerplate, and therefore very few explicit structure requirements for services. While this does mean getting more done with much less code, it also makes things less verbose and less structured.

Got more?

Spin up an issue!

Got a solution?

Spin up a PR! Standards are pretty high, but feedback is a cornerstone of any good pull request so expect much love for any contributions.