shalala
v2.0.6
Published
If you're like me, you've probably had enough of the overcomplicated approaches to state management in React. You're looking for something that feels native to React without requiring a heavy or rigid structure.
Readme
shalala
If you're like me, you've probably had enough of the overcomplicated approaches to state management in React. You're looking for something that feels native to React without requiring a heavy or rigid structure.
To start with, component state is a solid choice. It's straightforward, easy to grasp, and works well for most use cases.
import { useState } from 'react';
const MyComponent = () => {
const [state, setState] = useState(0);
return (
<>
<label>{state}</label>
<button type='button' onClick={() => setState(state++)}>
Update
</button>
</>
);
};When you're just starting with React, you might not understand all the details, but it's pretty easy to get the overall idea.
The challenge, though, is that this state isn’t easily shared with other components in the tree—and that’s by design. React encourages components to be self-contained and independent.
What we really need is a way to keep the simplicity of component state, but make it sharable across multiple components.
Introducing Shalala
That’s where Shalala comes in.
It’s built to extend the familiar patterns of React hooks and Zustand, offering a simple and intuitive way to manage shared state.
Install
npm i shalalaor
yarn add shalalaMinimal example
import type { ActionsFromStore, BoundedStore, StateFromStore } from 'shalala';
import { createStore } from 'shalala';
const initialState = {
counter: 0,
another: 100,
};
const actions = {
// Using store.set
increment:
({ set }: CounterStore) =>
(amount: number) => {
set((state: CounterState) => ({
...state,
counter: state.counter + amount,
}));
},
// Using state updater function
decrement:
() =>
(amount: number) =>
(state: CounterState): CounterState => ({
...state,
counter: state.counter - amount,
}),
reset:
({ set }: CounterStore) =>
() => {
set((state: CounterState) => ({
...state,
counter: initialState.counter,
}));
},
};
const counterStore = {
state: () => initialState,
actions,
};
const mapStateTo = (state: CounterState) => ({
counter: state.counter,
});
const mapActions = (actions: CounterActions) => ({
increment: actions.increment,
decrement: actions.decrement,
reset: actions.reset,
});
const useCounterStore = createStore(counterStore);
type GlobalStore = typeof counterStore;
interface CounterState extends StateFromStore<GlobalStore> {}
interface CounterActions extends ActionsFromStore<GlobalStore> {}
interface CounterStore extends BoundedStore<CounterState> {}
const Counter = () => {
const { counter, increment, decrement, reset } = useCounterStore(
mapStateTo,
mapActions
);
return (
<div>
<p>
Counter:
{counter}
</p>
<button type='button' onClick={() => increment(1)}>
+1 to global
</button> <button type='button' onClick={() => decrement(1)}>
-1 to global
</button> <button type='button' onClick={() => reset()}>
reset
</button>
</div>
);
};
export default Counter;Nested states
import type { ActionsFromStore, BoundedStore, StateFromStore } from 'shalala';
import { createStore } from 'shalala';
const counterStore = {
state: () => ({
value: 0,
}),
actions: {
// Using store.set
increment:
({ set }: NestedStore) =>
(amount: number) => {
set((state) => ({
...state,
counter: {
value: state.counter.value + amount,
},
}));
},
// Using custom set state function
decrement: () => (amount: number) => (state: NestedState) => ({
...state,
counter: {
value: state.counter.value - amount,
},
}),
reset: () => () => (state: NestedState) => ({
...state,
counter: {
value: 0,
},
}),
},
};
const randomStore = {
state: {
value: 0,
},
actions: {
generate: () => () => (state: NestedState) => ({
...state,
random: {
value: state.random.value + Number(Math.random().toFixed(4)),
},
}),
},
};
const nestedStore = {
counter: counterStore,
random: randomStore,
};
const mapCounterState = (state: NestedState) => state.counter;
const mapCounterActions = (actions: NestedActions) => actions.counter;
const { useStore, connect } = createStore(nestedStore);
type GlobalStore = typeof nestedStore;
interface NestedState extends StateFromStore<GlobalStore> {}
interface NestedActions extends ActionsFromStore<GlobalStore> {}
interface NestedStore extends BoundedStore<NestedState> {}
const Counter = () => {
const { value, increment, decrement, reset } = useStore(
mapCounterState,
mapCounterActions
);
return (
<div>
<p>Counter: {value}</p>
<button type='button' onClick={() => increment(1)}>
+1 to global
</button> <button type='button' onClick={() => decrement(1)}>
-1 to global
</button> <button type='button' onClick={() => reset()}>
reset
</button>
</div>
);
};
const mapRandomState = (state: NestedState) => state.random;
const mapRandomActions = (actions: NestedActions) => actions.random;
const Random = () => {
const { value, generate } = useStore(mapRandomState, mapRandomActions);
return (
<div>
<p>Value: {value}</p>
<button type='button' onClick={() => generate()}>
Generate
</button>
</div>
);
};
const Nested = () => {
return (
<div>
<Counter />
<Random />
</div>
);
};
export default Nested;Connect HOC
Components can be connected to the store to inject state and actions as props.
import type { ActionsFromStore, BoundedStore, StateFromStore } from 'shalala';
import { createStore } from 'shalala';
const counterStore = {
state: () => ({
value: 0,
}),
actions: {
// Using store.set
increment:
({ set }: NestedStore) =>
(amount: number) => {
set((state) => ({
...state,
counter: {
value: state.counter.value + amount,
},
}));
},
// Using custom set state function
decrement: () => (amount: number) => (state: NestedState) => ({
...state,
counter: {
value: state.counter.value - amount,
},
}),
reset: () => () => (state: NestedState) => ({
...state,
counter: {
value: 0,
},
}),
},
};
const randomStore = {
state: {
value: 0,
},
actions: {
generate: () => () => (state: NestedState) => ({
...state,
random: {
value: state.random.value + Number(Math.random().toFixed(4)),
},
}),
},
};
const nestedStore = {
counter: counterStore,
random: randomStore,
};
const mapCounterState = (state: NestedState) => state.counter;
const mapCounterActions = (actions: NestedActions) => actions.counter;
const { connect } = createStore(nestedStore);
type GlobalStore = typeof nestedStore;
interface NestedState extends StateFromStore<GlobalStore> {}
interface NestedActions extends ActionsFromStore<GlobalStore> {}
interface NestedStore extends BoundedStore<NestedState> {}
type CounterProps = NestedState['counter'] & NestedActions['counter'];
const Counter = (props: CounterProps) => {
const { value, another, increment, decrement, reset } = props;
return (
<div>
<p>Counter: {value}</p>
<p>Another: {another}</p>
<button type='button' onClick={() => increment(1)}>
+1 to global
</button>{' '}
<button type='button' onClick={() => decrement(1)}>
-1 to global
</button>{' '}
<button type='button' onClick={() => reset()}>
reset
</button>
</div>
);
};
const ConnectedCounter = connect(mapCounterState, mapCounterActions)(Counter);
export default ConnectedCounter;Sample project
Run npm run dev and look at the source code in ./src directory.
