• Stars
    star
    223
  • Rank 178,458 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 4 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple πŸ’ͺ fast ⚑️ and small 🎈 (400 bytes) global state management for React (Native)

react-ridge-state πŸ‹οΈβ€β™€οΈ ⚑️ πŸ‹οΈβ€β™‚οΈ

Bundle Size npm version npm

Simple πŸ’ͺ fast ⚑️ and small 🎈 (400 bytes) global state management for React which can be used outside of a React component too!

yarn add react-ridge-state

or

npm install react-ridge-state --save

Why another state library πŸ€”

We were frustrated that the current solutions could often only be used from React or have too complicated APIs. We wanted a lightweight solution with a smart API that can also be used outside React components.

Features πŸ€Ήβ€β™€οΈ

  • React / React Native
  • Simple
  • Fast
  • Very tiny (400 bytes)
  • 100% Typesafe
  • Hooks
  • Use outside React components
  • Custom selectors for deep state selecting

About us

We want developers to be able to build software faster using modern tools like GraphQL, Golang and React Native.

Give us a follow on Twitter: RichardLindhout, web_ridge

Donate

Please contribute or donate so we can spend more time on this library

Donate with PayPal

Getting started πŸ‘ πŸ‘Œ

Create a new state

import { newRidgeState } from "react-ridge-state";

interface CartProduct {
  id: number;
  name: string;
}

export const cartProductsState = newRidgeState<CartProduct[]>([
  { id: 1, name: "Product" },
]);

Use state inside components

import { cartProductsState } from "../cartProductsState";

// same interface and usage as setState
const [cartProducts, setCartProducts] = cartProductsState.use();

// if you only need the value and no setState
const cartProducts = cartProductsState.useValue();

// if you only want to subscribe to part of your state (this example the first product)
const cartProducts = cartProductsState.useSelector((state) => state[0]);

// custom comparison function (only use this if you have heavy child components and the default === comparison is not sufficient enough)
const cartProducts = cartProductsState.useSelector(
  (state) => state[0],
  (a, b) => JSON.stringify(a) === JSON.stringify(b)
);

Supported functions outside of React

The following functions work outside of React e.g. in your middleware but you can also use them in your component.

import { cartProductsState } from "../cartProductsState";

// get the root state
cartProductsState.get();

// set the state directly
cartProductsState.set([{ id: 1, name: "NiceProduct" }]);

// if you want previous state as callback
cartProductsState.set((prevState) => [
  ...prevState,
  { id: 1, name: "NiceProduct" },
]);

// you can also use a callback so you know when state has rendered
cartProductsState.set(
  (prevState) => [...prevState, { id: 1, name: "NiceProduct" }],
  (newState) => {
    console.log("New state is rendered everywhere");
  }
);

// you can reset to initial state too
cartProductsState.reset()

// you can also subscribe to state changes outside React

const unsubscribe = cartProductsState.subscribe((newState, oldState) => {
  console.log("State changed");
});

// call the returned unsubscribe function to unsubscribe.
unsubscribe();

Example

// CartState.ts
import { newRidgeState } from "react-ridge-state";

// this can be used everywhere in your application
export const globalCounterState = newRidgeState<number>(0); // 0 could be something else like objects etc. you decide!

// Counter.tsx
function Counter() {
  // you can use these everywhere in your application the globalCounterState will update automatically  even if set globally
  const [count, setCount] = globalCounterState.use();
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => setCount(count + 1)}>Add 1</button>
    </div>
  );
}

// CounterViewer.tsx
function CounterViewer() {
  // you can use these everywhere in your application the globalCounterState will update automatically even if set globally
  const counter = globalCounterState.useValue();

  return (
    <div>
      <div>Count: {counter}</div>
    </div>
  );
}

Usage in class components

Since we want to keep this library small we are not supporting class components but you could use wrappers like this if you have class components, however we would recommend to use functional components since they are more type safe and easier to use.

class YourComponentInternal extends Component {
  render() {
    <div>
      <div>Count: {this.props.count}</div>
      <button onClick={() => this.props.setCount(count + 1)}>Add 1</button>
    </div>
  }
}

export default function YourComponent(props) {
  const [count, setCount] = globalCounterState.use();
  return <YourComponentInternal {...props} count={count} setCount={setCount}>
}

Persistence example

It's possible to add make your state persistent, you can use storage library you desire. localStorage is even simpler since you don't need async functions

const authStorageKey = "auth";
const authState = newRidgeState<AuthState>(
  { loading: true, token: "" },
  {
    onSet: async (newState) => {
      try {
        await AsyncStorage.setItem("@key", JSON.stringify(newState));
      } catch (e) {}
    },
  }
);

// setInitialState fetches data from localStorage
async function setInitialState() {
  try {
    const item = await AsyncStorage.getItem("@key");
    if (item) {
      const initialState = JSON.parse(item);
      authState.set(initialState);
    }
  } catch (e) {}
}

// run function as application starts
setInitialState();

Managing complex/nested state with Immer

Sometimes you might need to update values that are deeply nested, code for this can end up looking verbose as you will likely need to use many spread operators. A small utility library called Immer can help simplify things.

const characterState = newRidgeState<CharacterState>({
  gold: 100,
  stats: {
    spells: {
      fire: 10,
      watter: 10
    },
    battle: {
      health: 100,
      mana: 100
    },
    profession: {
      mining: 10,
      herbalism: 10
    }
  }
})

// Update mana and herbalism without immer
characterState.set(previous => ({
  ...previous,
  stats: {
    ...previous.stats,
    battle: {
      ...previous.stats.battle,
      mana: 200
    },
    profession: {
      ...previous.stats.profession,
      herbalism: 20
    }
  }
}))

// Update mana and herbalism using immer
import { produce } from "immer";

characterState.set(previous =>
  produce(previous, updated => {
    updated.stats.battle.mana = 200
    updated.stats.profession.herbalism = 20
  })
)

Testing your components which use react-ridge-state

You can find examples of testing components with global state here: https://github.com/web-ridge/react-ridge-state/blob/main/src/tests/Counter.test.tsx

Jest

Jest keeps the global state between tests in one file. Tests inside one file run synchronous by default, so no racing can occur.

When testing in different files (test1.test.js, test2.test.js), the global state is new for every file. You don't have to mock or reset the state even if the tests run in parallel.

Mocha

In Mocha you will need to reset the state the initial value before each test since the state is shared across all tests. You could do that with the code below and not using the --parallel mode of Mocha.

beforeEach(()=> {
    characterState.reset()
})

Checkout our other libraries

More Repositories

1

react-native-paper-dates

Smooth and fast cross platform Material Design date and time picker for React Native Paper
TypeScript
607
star
2

react-native-paper-tabs

Smooth and fast cross platform Material Design Tabs for React Native Paper
TypeScript
179
star
3

react-native-ridge-navigation

Simple, performant & type-safe cross platform navigation in React Native / React Native Web
TypeScript
107
star
4

react-native-use-form

Simple form library for React Native with great UX for developer and end-user
TypeScript
81
star
5

gqlgen-sqlboiler

This is a plugin for gqlgen to generate converts + filter queries and resolvers for sqlboiler
Go
74
star
6

react-ridge-translations

Simple πŸ’ͺ fast ⚑️ and small 🎈 (400 bytes) translation library for React / React Native
TypeScript
65
star
7

create-react-native-web-application

A simple starting point for creating iOS, Android en webapp in 1 codebase with React Native (Web)
JavaScript
41
star
8

react-native-paper-autocomplete

The autocomplete package you wished for on all platforms (iOS, Android, web)
TypeScript
17
star
9

react-native-web-hover

Some enhanced elements for react-native-web to support hover on the web.
TypeScript
16
star
10

contact-tracing

A privacy-first contact tracing solution for Android and iOS where users have full control over their data without the Google or Apple Contact Tracing API's
TypeScript
13
star
11

react-native-ridge-list

FlatList abstraction which uses react-window on the web to create better list performance
TypeScript
9
star
12

gqlgen-sqlboiler-examples

Examples of https://github.com/web-ridge/gqlgen-sqlboiler
Go
8
star
13

sqlboiler-graphql-schema

MOVED TO https://github.com/web-ridge/gqlgen-sqlboiler
Go
7
star
14

email-reply-parser

Parses a plain text email and returns it without signatures and other things
Go
6
star
15

detox-web

Abstraction on top of puppeteer to support Detox test to run on the web platform
Java
4
star
16

dbifier

Aims to create a tool to which is easy to use to model your database
TypeScript
3
star
17

utils-go

Go
2
star
18

rem-to-px

rem-to-px cli program which you can put in your package.json. It replaces rem with px.
JavaScript
2
star
19

ridgecontent

[Future plans] Modern CMS system and very simple for end-user and developer
1
star
20

graphql-schema-react-native-app

The goal for this repository is to generate a fully working CRUD app based on an graphql schema with using Relay.dev (experimental)
1
star