4.3.0 • Published 12 months ago

react-relink v4.3.0

Weekly downloads
7
License
MIT
Repository
github
Last release
12 months ago

Relink banner NPM

Introduction

Relink is a React state management library inspired by Recoil. Relink provides some APIs that are similar to Recoil to minimize the learning gap.

With Relink,

  • There will be less boilerplate code, just like Recoil;
  • There is no need to wrap components in a Provider;
  • States can be shared across different React components trees.

Relink is not a replacement for Recoil — it can, however serve as an alternative for projects that do not require the full feature-set of Recoil. Relink also has a few downsides as it:

  • Does not support snapshots;
  • Does not provide advanced APIs for scenarios that are hard/impossible to achieve with the currently provided ones;
  • (and possibly other unforeseen concerns — Relink is also experimental).

Usage

# NPM
npm install react-relink

# Yarn
yarn add react-relink

# unpkg (Replace <VERSION> with the version you need)
<script src="https://unpkg.com/react-relink@<VERSION>/dist/umd/index.min.js" crossorigin></script>

Create a Source

Provide a unique key and default state for the source.

import { createSource } from 'react-relink'

const CounterSource = createSource({
  key: 'counter',
  default: 1,
})

Consume a Source

In Relink, you can work with the sources right away without needing to wrap your app inside any provider components. This makes accessing them across different React component trees easy, such as when registerering screen components in React Native Navigation.

To consume a source, pass it as a parameter into any of the following Relink hooks:

  • useRelinkState - returns the state value and a setter function like React's useState
  • useSetRelinkState - returns just the setter function of the state
  • useRelinkValue - returns just the value of the state
  • useResetRelinkState - returns a function that sets the state to its default value
  • useRehydrateRelinkSource - returns a function that sets the state without invoking the didSet lifecycle method

An example with the useRelinkState hook:

function App() {
  const [counter, setCounter] = useRelinkState(CounterSource)
  // `setCounter` accepts either:
  // • A value to replace the state, or
  // • A function that returns a new state.
  return (
    <div>
      <span>Counter: {counter}</span>
      <button onClick={() => { setCounter(5) }}>Set to 5</button>
      <button onClick={() => { setCounter(c => c + 1) }}>Step up</button>
    </div>
  )
}

Examples with other hooks:

const counter = useRelinkValue(CounterSource)
console.log('Counter is ' + counter)
const setCounter = useSetRelinkState(CounterSource)
setCounter(/* new value */)
const resetCounter = useResetRelinkState(CounterSource)
resetCounter()
const rehydrateCounter = useRehydrateRelinkSource(CounterSource)
rehydrateCounter(({ commit }) => {
  commit(/* new value */)
})

Selectors

You can use selectors to narrow down the items passed from useRelinkState and useRelinkValue.

const MessagesSource = createSource({
  key: 'messages',
  default: [
    {
      messageId: 1,
      userId: 1,
      title: '...',
      body: '...',
    },
    {
      messageId: 2,
      userId: 2,
      title: '...',
      body: '...',
    },
    {
      messageId: 3,
      userId: 1,
      title: '...',
      body: '...',
    },
  ],
})

function App() {
  const messages = useRelinkValue(MessagesSource, (allMessages) => {
    return allMessages.filter((m) => m.userId === 1)
  })
  console.log(messages)
  // [
  //   {
  //     messageId: 1,
  //     userId: 1,
  //     title: '...',
  //     body: '...',
  //   },
  //   {
  //     messageId: 3,
  //     userId: 1,
  //     title: '...',
  //     body: '...',
  //   },
  // ]
  return '...'
}

"dangerously-" methods

You can also consume sources outside of a component. Although the hooks mentioned above should be enough for most cases, there are equivalent ordinary functions in case you need them. These APIs have the "dangerously-" prefix as using them might result in hard-to-debug code. They should be used sparingly.

Examples

import {
  createSource,
  dangerouslyGetRelinkValue,
  dangerouslySetRelinkState,
  dangerouslyResetRelinkState,
  dangerouslyRehydrateRelinkSource,
} from 'react-relink'

const CounterSource = createSource({
  key: 'counter',
  default: 1,
})

// Getting the value
const value = dangerouslyGetRelinkValue(CounterSource)

// Set the state...
dangerouslySetRelinkState(newValue, CounterSource) // directly
dangerouslySetRelinkState(c => c + 1, CounterSource) // with a function

// Reset the state
dangerouslyResetRelinkState(Source)

// Rehydrate the source
dangerouslyResetRelinkState(Source, ({ commit }) => {
  commit(newValue)
})

Lifecycle

Sources can be initialized(hydrated), persisted and reset.

Upon creating a new source, the lifecycle.init method will be called. This is the first hydration. Subsequent hydrations can be performed with the useRehydrateRelinkSource hook. This can be useful, for example, when switching accounts and user preferences need to be rehydrated. If you attempt to use an ordinary "setState" to rehydrate a source, it will call the didSet lifecycle method, persisting the just-fetched data back to your server, which is unnecessary.

Upon setting a state, the lifecycle.didSet method will be called while upon resetting a state, the lifecycle.didReset method will be called. You can either persist or reset data here. For example, persisting data into or removing it from localStorage.

Synchronous Example

import { createSource } from 'react-relink'

const defaultUserState = {
  username: 'Unknown',
  preferences: {
    theme: 'system-default',
    language: 'system-default',
  }
}

const UserSource = createSource({
  key: 'user',
  default: defaultUserState,
  lifecycle: {
    init: ({ commit }) => {
      const data = localStorage.getItem('user')
      commit(data ? JSON.parse(data) : defaultUserState)
    },
    didSet: (payload) => {
      localStorage.setItem('user', JSON.stringify(payload))
    },
    didReset: () => {
      localStorage.removeItem('user')
    }
  },
})

Asynchronous Example

First, set options.suspense to true and wrap your components in <Suspense> as they will be suspended while the data is being fetched. This is made possible with React's experimental feature — Suspensed Data Fetching. Since it is experimental, source hydration in Relink will always be synchronous unless explicitly specified in options.suspense.

// Throttling can help reduce the number of network requests made to persist data
// Further reading: https://dev.to/bmsvieira/using-throttle-in-javascript-254g
// Lodash docs: https://lodash.com/docs/4.17.15#throttle
import { throttle } from 'lodash'
import { Suspense } from 'react'
import { createSource } from 'react-relink'

// Mock function
const saveToServer = throttle((data) {
  fetch('https://your-api-endpoint-2', { body: data }).then(() => {
    console.log('saved')
  }).catch((e) => {
    // Perform error handling here...
    console.log(e)
  })
}, 5000) // 5 seconds

const defaultUserState = {
  username: 'Unknown',
  preferences: {
    theme: 'system-default',
    language: 'system-default',
  }
}

const UserSource = createSource({
  key: 'user',
  default: defaultUserState,
  lifecycle: {
    init: ({ commit }) => {
      if (loggedIn) {
        fetch('https://your-api-endpoint-1').then((userData) => {
          commit(userData)
        })
      } else {
        commit(defaultUserState)
      }
    },
    didSet: (payload) => {
      saveToServer(payload)
    },
    didReset: () => {
      saveToServer(defaultUserState)
    }
  },
  options: { suspense: true },
})

Options

There are several options that you can enable when creating a source. The default values for all of these options are false. They are designed to be opt-in because they are either experimental or they can make code more prone to bugs.

const Source = createSource({
  key: 'my-source',
  default: { /* . . . */ },
  options: {
    suspense: true | false,
    mutable: true | false,
    virtualbatch: true | false,
  }
})
optionsDescriptionWarning
suspenseComponents that consume the source will be suspended while hydratingUnstable React feature
mutableAllows slight performance improvement by not deep-copying the values returnedMakes code hard to debug
virtualBatchSlightly improve performance by coalescing the "setState" calls on top of React's batched updatesUnstable Relink feature

Error Codes

Error codes will be thrown instead of messages in production builds to save data.

CodeDescription
1Key must be a string (or number - will be casted into string)
2Duplicate source key
4.2.2

1 year ago

4.2.1

1 year ago

4.3.0

12 months ago

4.1.0

2 years ago

4.2.0

1 year ago

4.0.1

2 years ago

4.0.0

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.0

2 years ago

1.1.2

2 years ago

2.1.2

2 years ago

2.1.1

2 years ago

2.1.3

2 years ago

2.1.0

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

3.1.3

2 years ago

3.1.2

2 years ago

3.1.1

2 years ago

3.1.0

2 years ago

1.0.0-beta.5

2 years ago

3.0.0

2 years ago

1.0.0-beta.6

2 years ago

1.0.0-beta.7

2 years ago

0.3.8

2 years ago

0.3.7

2 years ago

0.3.6

3 years ago

0.3.5

3 years ago

1.0.0-beta.3

3 years ago

1.0.0-beta.2

3 years ago

1.0.0-beta.1

3 years ago

0.3.4

3 years ago

1.0.0-beta.0

3 years ago

1.0.0-alpha.0

3 years ago

0.3.3

3 years ago

0.3.2

3 years ago

0.3.1

3 years ago

0.3.0

3 years ago

0.2.0

3 years ago

0.1.0

3 years ago

0.0.6

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago