A scoped model
represents a shared business logic within a local component tree. Components within the same parent, which is the provider of the scoped model
, can access the scoped model
's state and can update immediately whenever the state updates.
In preact-scoped-model
, scoped model
's business logic and state relies on the use of React hooks.
createModel
, the function for creating scoped models, is exported as default from the package.
import createModel from 'preact-scoped-model';
The createModel
function accepts two parameters:
modelHook
: A hook function based on Preact hooks that provides the business logic for model. The value returned from the hook serves as the state of the model.options
: Optional.displayName
: Serves as the display name for both the model (for error logs) and theProvider
component.shouldUpdate
: Serves as condition for render memoization or "when should the<Provider>
component re-render given the new props. This is similar toReact.PureComponent
'sshouldUpdate
.shouldNotify
: Serves as condition for state memoization.defaultProps
The model returns an object with the property Provider
which injects the model state as well as runs the model hook within the component tree.
/**
* One of the basic models for state management, a model that has two actions:
* - Increment = Adds 1 to count.
* - Decrement = Subtracts 1 to count.
*/
const Counter = createModel(() => {
// The state of our model
const [count, setCount] = useState(0);
// An action for our model
const increment = useCallback(() => {
// update the state by getting the current state, adding 1
setCount(c => c + 1);
}, []); // No dependencies means no new instances every update.
// Another action
const decrement = useCallback(() => {
// update the state by getting the current state, subtracting 1
setCount(c => c - 1);
}, []);
// Return the shape of our model
return {
count,
increment,
decrement,
};
});
To mount a model to our app, we can use the Provider
component provided as a property from our instances.
<Counter.Provider>
{/* children */}
</Counter.Provider>
Components added to the children can always access the model's state.
Model hooks can also access props provided through the Provider
component.
<Counter.Provider initial={0}>
{/* */}
</Counter.Provider>
// Model
const Counter = createModel(({ initial }) => {
const [state, setState] = useState(initial);
// ...
});
If the model itself is mounted as a children from the same instance, components within that model will access a different state from the parent model. This is what you call "context shadowing".
<Counter.Provider initial={0}>
{/* Children here will receive the state of 0 */}
<Counter.Provider initial={100}>
{/* Children here will receive the state of 100 */}
</Counter.Provider>
{/* Children here will receive the state of 0 */}
</Counter.Provider>
Components outside of the model's component tree that attempts to access it's state will throw an error.