State Management Guide
This guide explains how to manage state in your Snakeskin applications.
Component State Basics
Each Snakeskin component can maintain its own internal state. State is a dictionary of values that can change over time and trigger re-renders.
from snakeskin.framework import Component
class Counter(Component):
def __init__(self, **props):
super().__init__(**props)
# Initialize state
self.state = {"count": 0}
def increment(self):
# Update state
self.set_state({"count": self.state["count"] + 1})
def render(self):
return f"""
<div>
<p>Count: {self.state["count"]}</p>
<button onclick="increment_{id(self)}()">Increment</button>
<script>
function increment_{id(self)}() {{
// In a real app, you would use AJAX or WebSockets
console.log("Increment clicked");
}}
</script>
</div>
""" The set_state Method
The set_state method is the primary way to update a component's
state:
- It merges the new state with the existing state
-
It triggers the component's
before_updatelifecycle hook - It notifies all observers of the state change
- It triggers the component's
updatedlifecycle hook -
It returns the result of calling the component's
rendermethod
def set_state(self, new_state: dict):
# Call lifecycle hook
self._call_hooks('before_update')
# Update state
self.state.update(new_state)
# Notify observers
for observer in self._observers:
observer(self.state)
# Call lifecycle hook
self._call_hooks('updated')
# Re-render
return self.render() Reactive State with Observers
Snakeskin provides an observer pattern for reactive state management. You can register observers that will be notified whenever the state changes:
from snakeskin.framework import Component
class ObservableCounter(Component):
def __init__(self, **props):
super().__init__(**props)
self.state = {"count": 0}
# Add an observer to log state changes
self.observer_id = self.observe(self.log_state_change)
def log_state_change(self, state):
print(f"State changed: {state}")
def increment(self):
self.set_state({"count": self.state["count"] + 1})
def cleanup(self):
# Remove the observer when no longer needed
self.unobserve(self.observer_id)
def render(self):
return f"""
<div>
<p>Count: {self.state["count"]}</p>
<button onclick="increment_{id(self)}()">Increment</button>
<script>
function increment_{id(self)}() {{
console.log("Increment clicked");
}}
</script>
</div>
""" Lifecycle Hooks and State
Lifecycle hooks provide a way to respond to state changes:
from snakeskin.framework import Component
class DataFetcher(Component):
def __init__(self, **props):
super().__init__(**props)
self.state = {
"loading": True,
"data": None,
"error": None
}
# Register lifecycle hooks
self.on('mounted', self.fetch_data)
self.on('updated', self.log_update)
def fetch_data(self, component):
# Simulate fetching data
try:
# In a real app, this would be an API call
data = {"name": "Example", "value": 42}
self.set_state({"loading": False, "data": data})
except Exception as e:
self.set_state({"loading": False, "error": str(e)})
def log_update(self, component):
print(f"Component updated with state: {self.state}")
def render(self):
if self.state["loading"]:
return """<div>Loading...</div>"""
elif self.state["error"]:
return f"""<div>Error: {self.state["error"]}</div>"""
else:
return f"""
<div>
<h2>{self.state["data"]["name"]}</h2>
<p>Value: {self.state["data"]["value"]}</p>
</div>
""" Sharing State Between Components
There are several ways to share state between components:
1. Props Passing
Pass state from parent to child components through props:
from snakeskin.framework import Component
from .child import ChildComponent
class ParentComponent(Component):
def __init__(self, **props):
super().__init__(**props)
self.state = {"shared_value": 42}
def render(self):
# Pass state as props to child component
child = ChildComponent(value=self.state["shared_value"])
return f"""
<div>
<h2>Parent Component</h2>
<p>Value: {self.state["shared_value"]}</p>
{child.render()}
</div>
""" 2. Callback Functions
Pass callback functions to allow child components to update parent state:
from snakeskin.framework import Component
from .child_with_callback import ChildWithCallback
class ParentWithCallback(Component):
def __init__(self, **props):
super().__init__(**props)
self.state = {"shared_value": 42}
def update_value(self, new_value):
self.set_state({"shared_value": new_value})
def render(self):
# Pass state and callback to child component
child = ChildWithCallback(
value=self.state["shared_value"],
on_change=self.update_value
)
return f"""
<div>
<h2>Parent Component</h2>
<p>Value: {self.state["shared_value"]}</p>
{child.render()}
</div>
""" # child_with_callback.py
from snakeskin.framework import Component
class ChildWithCallback(Component):
def increment(self):
# Call the parent's callback function
current_value = self.props.get("value", 0)
on_change = self.props.get("on_change")
if on_change:
on_change(current_value + 1)
def render(self):
value = self.props.get("value", 0)
return f"""
<div>
<h3>Child Component</h3>
<p>Value: {value}</p>
<button onclick="increment_{id(self)}()">Increment</button>
<script>
function increment_{id(self)}() {{
console.log("Child increment clicked");
}}
</script>
</div>
""" 3. Global State
For more complex applications, you can implement a simple global state manager:
# state_manager.py
class StateManager:
def __init__(self):
self.state = {}
self.observers = []
def get_state(self):
return self.state.copy()
def set_state(self, new_state):
self.state.update(new_state)
self._notify_observers()
def observe(self, callback):
self.observers.append(callback)
return len(self.observers) - 1
def unobserve(self, observer_id):
if 0 <= observer_id < len(self.observers):
self.observers.pop(observer_id)
def _notify_observers(self):
for observer in self.observers:
observer(self.state)
# Create a singleton instance
global_state = StateManager() # component_with_global_state.py
from snakeskin.framework import Component
from .state_manager import global_state
class GlobalStateComponent(Component):
def __init__(self, **props):
super().__init__(**props)
self.state = {"local_value": 0}
# Observe global state
self.global_observer_id = global_state.observe(self.on_global_state_change)
# Register cleanup
self.on('before_unmount', self.cleanup)
def on_global_state_change(self, global_state_data):
# Update local state based on global state
self.set_state({"local_value": global_state_data.get("counter", 0)})
def increment_global(self):
current = global_state.get_state().get("counter", 0)
global_state.set_state({"counter": current + 1})
def cleanup(self, component):
# Clean up global state observer
global_state.unobserve(self.global_observer_id)
def render(self):
return f"""
<div>
<h3>Global State Component</h3>
<p>Local value: {self.state["local_value"]}</p>
<button onclick="incrementGlobal_{id(self)}()">Increment Global</button>
<script>
function incrementGlobal_{id(self)}() {{
console.log("Increment global clicked");
}}
</script>
</div>
""" Best Practices for State Management
- Keep state minimal: Only store what you need in state
- Use props for configuration: Pass configuration through props
- Lift state up: Move shared state to the nearest common ancestor
- Use callbacks for child-to-parent communication
- Consider global state only when necessary
- Clean up observers: Always remove observers when components are unmounted
- Use immutable patterns: Treat state as immutable and create new objects when updating
- Document state shape: Add comments describing the expected state structure