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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skolplattformen/api-hooks

v3.0.0

Published

React hooks for accessing api with cached results

Readme

@skolplattformen/api-hooks

  1. Installing
  2. Login / logout
  3. Get data
  4. Fake mode

Installing

npm i -S @skolplattformen/api-hooks @skolplattformen/embedded-api

yarn add @skolplattformen/api-hooks @skolplattformen/embedded-api

ApiProvider

In order to use api hooks, you must wrap your app in an ApiProvider

import React from 'react'
import { ApiProvider } from '@skolplattformen/api-hooks'
import init from '@skolplattformen/embedded-api'
import { CookieManager } from '@react-native-community/cookies'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { RootComponent } from './components/root'
import crashlytics from '@react-native-firebase/crashlytics'

const api = init(fetch, () => CookieManager.clearAll())
const reporter = {
  log: (message) => crashlytics().log(message),
  error: (error, label) => crashlytics().recordError(error, label),
}

export default () => (
  <ApiProvider api={api} reporter={reporter} storage={AsyncStorage}>
    <RootComponent />
  </ApiProvider>
)

Login / logout

import { useApi } from '@skolplattformen/api-hooks'

export default function LoginController () {
  const { api, isLoggedIn } = useApi()

  api.on('login', () => { /* do login stuff */ })
  api.on('logout', () => { /* do logout stuff */ })

  const [personalNumber, setPersonalNumber] = useState()
  const [bankIdStatus, setBankIdStatus] = useState('')

  const doLogin = async () => {
    const status = await api.login(personalNumber)

    openBankID(status.token)

    status.on('PENDING', () => { setBankIdStatus('BankID app not yet opened') })
    status.on('USER_SIGN', () => { setBankIdStatus('BankID app is open') })
    status.on('OK', () => { setBankIdStatus('BankID signed. NOTE! User is NOT yet logged in!') })
    status.on('ERROR', (err) => { setBankIdStatus('BankID failed') })
  })

  return (
    <View>
      <Input value={personalNumber} onChange={(value) = setPersonalNumber(value)} />
      <Button onClick={() => doLogin()}>
      <Text>{bankIdStatus}</Text>
      <Text>Logged in: {isLoggedIn}</Text>
    </View>
  )
}

Get data

  1. General
  2. useCalendar
  3. useChildList
  4. useClassmates
  5. useMenu
  6. useNews
  7. useNotifications
  8. useSchedule
  9. useUser

General

The data hooks return a State<T> object exposing the following properties:

| Property | Description | |----------|----------------------------------| | status | pending loading loaded | | data | The requested data | | error | Error from the API call if any | | reload | Function that triggers a reload |

The hook will return a useable default for data at first (usually empty []). It then checks the cache (AsyncStorage) for any value and, if exists, updates data. Simultaneously the API is called. This only automatically happens once during the lifetime of the app. If several instances of the same hook are used, the data will be shared and only one API call made. When reload is called, a new API call will be made and all hook instances will have their status, data and error updated.

useCalendar

import { useCalendar } from '@skolplattformen/api-hooks'

export default function CalendarComponent ({ selectedChild }) => {
  const { status, data, error, reload } = useCalendar(selectedChild)
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((item) => (
        <CalendarItem item={item} />
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

useChildList

import { useChildList } from '@skolplattformen/api-hooks'

export default function ChildListComponent () => {
  const { status, data, error, reload } = useChildList()
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((child) => (
        <Text>{child.firstName} {child.lastName}</Text>
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

useClassmates

import { useClassmates } from '@skolplattformen/api-hooks'

export default function ClassmatesComponent ({ selectedChild }) => {
  const { status, data, error, reload } = useClassmates(selectedChild)
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((classmate) => (
        <Classmate item={classmate} />
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

useMenu

import { useMenu } from '@skolplattformen/api-hooks'

export default function MenuComponent ({ selectedChild }) => {
  const { status, data, error, reload } = useMenu(selectedChild)
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((item) => (
        <MenuItem item={item} />
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

useNews

import { useNews } from '@skolplattformen/api-hooks'

export default function NewsComponent ({ selectedChild }) => {
  const { status, data, error, reload } = useNews(selectedChild)
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((item) => (
        <NewsItem item={item} />
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

To display image from NewsItem:

import { useApi } from '@skolplattformen/api-hooks'

export default function NewsItem ({ item }) => {
  const { api } = useApi()
  const cookie = api.getSessionCookie()
  
  return (
    <View>
      { cookie &&
        <Image source={{ uri: item.fullImageUrl, headers: { cookie } }} /> }
    </View>
  )
}

useNotifications

import { useNotifications } from '@skolplattformen/api-hooks'

export default function NotificationsComponent ({ selectedChild }) => {
  const { status, data, error, reload } = useNotifications(selectedChild)
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((item) => (
        <Notification item={item} />
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

To show content of NotificationItem url:

import { useApi } from '@skolplattformen/api-hooks'
import { WebView } from 'react-native-webview'

export default function Notification ({ item }) => {
  const { cookie } = useApi()
  
  return (
    <View>
      <WebView source={{ uri: item.url, headers: { cookie }}} />
    </View>
  )
}

useSchedule

import { DateTime } from 'luxon'
import { useSchedule } from '@skolplattformen/api-hooks'

export default function ScheduleComponent ({ selectedChild }) => {
  const from = DateTime.local()
  const to = DateTime.local.plus({ week: 1 })
  const { status, data, error, reload } = useSchedule(selectedChild, from, to)
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data.map((item) => (
        <ScheduleItem item={item} />
      ))}
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

useUser

import { useUser } from '@skolplattformen/api-hooks'

export default function UserComponent () => {
  const { status, data, error, reload } = useUser()
  
  return (
    <View>
      { status === 'loading' && <Spinner />}
      { error && <Text>{ error.message }</Text>}
      { data &&
        <>
          <Text>{data.firstName} {data.lastName}</Text>
          <Text>{data.email}</Text>
        </>
      }
      { status !== 'loading' && status !== 'pending' && <Button onClick={() => reload()}> }
    </View>
  )
}

Fake mode

To make testing easier, fake mode can be enabled at login. Just use any of the magic personal numbers: 12121212121212, 201212121212 or 1212121212. The returned login status will have token set to 'fake'.

import { useApi } from '@skolplattformen/api-hooks'


import { useApi } from '@skolplattformen/api-hooks'

export default function LoginController () {
  const { api, isLoggedIn } = useApi()

  const [personalNumber, setPersonalNumber] = useState()
  const [bankIdStatus, setBankIdStatus] = useState('')

  api.on('login', () => { /* do login stuff */ })
  api.on('logout', () => { /* do logout stuff */ })

  const doLogin = async () => {
    const status = await api.login(personalNumber)

    if (status.token !== 'fake') {
      openBankID(status.token)
    } else {
      // Login will succeed
      // All data will be faked
      // No server calls will be made
    }
  })

  return (
    <View>
      <Input value={personalNumber} onChange={(value) = setPersonalNumber(value)} />
      <Button onClick={() => doLogin()}>
      <Text>{bankIdStatus}</Text>
      <Text>Logged in: {isLoggedIn}</Text>
    </View>
  )
}