react-ridge-state :weight_lifting_woman: ⚡️ :weight_lifting_man:
Simple :muscle: fast ⚡️ and small :balloon: (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 :thinking:
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 :woman_juggling:
- 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 :clap: :ok_hand:
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";
const [cartProducts, setCartProducts] = cartProductsState.use();
const cartProducts = cartProductsState.useValue();
const cartProducts = cartProductsState.useSelector((state) => state[0]);
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";
cartProductsState.get();
cartProductsState.set([{ id: 1, name: "NiceProduct" }]);
cartProductsState.set((prevState) => [
...prevState,
{ id: 1, name: "NiceProduct" },
]);
cartProductsState.set(
(prevState) => [...prevState, { id: 1, name: "NiceProduct" }],
(newState) => {
console.log("New state is rendered everywhere");
}
);
cartProductsState.reset()
const unsubscribe = cartProductsState.subscribe((newState, oldState) => {
console.log("State changed");
});
unsubscribe();
Example
import { newRidgeState } from "react-ridge-state";
export const globalCounterState = newRidgeState<number>(0);
function Counter() {
const [count, setCount] = globalCounterState.use();
return (
<div>
<div>Count: {count}</div>
<button onClick={() => setCount(count + 1)}>Add 1</button>
</div>
);
}
function CounterViewer() {
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) {}
},
}
);
async function setInitialState() {
try {
const item = await AsyncStorage.getItem("@key");
if (item) {
const initialState = JSON.parse(item);
authState.set(initialState);
}
} catch (e) {}
}
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
}
}
})
characterState.set(previous => ({
...previous,
stats: {
...previous.stats,
battle: {
...previous.stats.battle,
mana: 200
},
profession: {
...previous.stats.profession,
herbalism: 20
}
}
}))
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