Py.Cafe

johnarban/

Solara Local Storage using Vue template

Example of using browser based localStorage and sessionStorage alongside solara's builtin server cache.

DocsPricing
  • LocalStorage.vue
  • app.py
  • local_storage.py
  • requirements.txt
  • solara_cache.py
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import solara
from local_storage import LocalStorage
from solara_cache import use_cache, init_cache
from typing import Literal

"""
In this experiment I look at two methods of caching the state
1) By using solara's built in cache storage
2) By using local storage (i.e. the browser's local storage)
Which one is best depends on where how much you need to persist. 

## Solara Cache
- Stored in server memory
- Cleared when the server restarts
- Shared across instances/tabs if connected to the same server

## Browser Local Storage

The browser's local storage persists across server restarts

### sessionStorage vs localStorage
- sessionStorage
    - is only available for the current tab
    - is cleared when the tab is closed
    - if a tab is duplicated, the new tab will have its own sessionStorage, but (possibly) copy the original tab's sessionStorage
- localStorage
    - is available across all tabs and windows
    - is cleared when the browser is closed
    - if a tab is duplicated, the new tab will share the same localStorage as the original tab
    - if >1 tab is open, must refresh the tabs to see changes made in other tabs
"""

CACHE_NAME = 'test-cache'
init_cache(CACHE_NAME) # Initialize the cache if it doesn't exist

def safeInt(val: int | str | Literal['null']):
    if val == 'null' or  val is None:
        return 0
    else:
        try:
            return int(val)
        except TypeError:
            print(f"ValueError: Cannot convert {val} to int")
            return -999

@solara.component
def Page():
    solara.lab.theme.dark = True
    print("Page")
    # color = solara.use_reactive("green")
    color = "green"
    
    
    local_storage_clicks = solara.use_reactive(0)
    def increment_local_storage():
        print("Incrementing local storage")
        local_storage_clicks.value += 1
    
    solara_cache_clicks = solara.use_reactive(0)
    def increment_solara_cache():
        print("Incrementing solara cache")
        solara_cache_clicks.value += 1
    
    def increment():
        print("Incrementing")
        local_storage_clicks.value += 1
        solara_cache_clicks.value += 1
        
    
    
    
    
    
    #### Local Storage
    
    
    showDebug = solara.use_reactive(True) 
    # clear_local_storage = solara.use_reactive(False)
    clear_local_storage = solara.use_reactive(0)
    LocalStorage(key = "clicks", 
                 value = local_storage_clicks.value, 
                 on_value=lambda v: local_storage_clicks.set(safeInt(v)), 
                 sessionOnly=False, # if true will persist across sessions (i.e. tabs)
                 clear=clear_local_storage.value,
                 debug=showDebug.value)
    
    
    #### Solara Cache

    # need to do this in a use_effect to avoid
    # calling it each time the component re-renders
    def _cache_value():
        def set_from_cache(val):
            print("Setting from cache", val)
            solara_cache_clicks.set(safeInt(val))

        use_cache(key="clicks", 
                reactive_value=solara_cache_clicks, 
                on_value=set_from_cache,
                default=0, 
                cache_name=CACHE_NAME
                )
        
    solara.use_effect(_cache_value, dependencies=[])
    cache = solara.cache.storage[CACHE_NAME]
    def reset_solara_cache():
        solara.cache.storage[CACHE_NAME] = {}
        
    
    
    solara.Text(f"Solara '{CACHE_NAME}': {cache}", classes=['debug'])
    force_rerender = solara.use_reactive(0)
    force_rerender.value # call this here to force the full component to render
                         # rather than just stuff in the Div
    def reset():
        print('Setting both to zero')
        local_storage_clicks.value = 0
        solara_cache_clicks.value = 0
        reset_solara_cache()
        clear_local_storage.set(clear_local_storage.value + 1)

    ### UI
    with solara.Div(classes=['mx-2']):
        with solara.Column():
            solara.Text(f"Clicks (LocalStorage): {local_storage_clicks.value}")
            solara.Text(f"Clicks (Cache): {solara_cache_clicks.value}")
            
            
            with solara.Column(align="start"):
                solara.Button(label=f"Increment Both", on_click=increment, color='#4c0a8a')
                with solara.Row(justify="center"):
                    solara.Button(label=f"Increment LocalStorage", 
                                on_click=increment_local_storage,
                                    color='black')
                    solara.Button(label=f"Increment Cache", 
                                on_click=increment_solara_cache,
                                    color='black')
            solara.Row()
            with solara.Column(align="start"):
                solara.Button(label="Reset", on_click=reset, color="#8a0a28")
                with solara.Row(justify="center"):
                    solara.Button(label="Clear Local Storage", 
                                on_click=lambda: clear_local_storage.set(clear_local_storage.value + 1)
                                , color="#8a0a28")
                    solara.Button(label="Clear Solara Cache", 
                                on_click=reset_solara_cache, color="#8a0a28")
            solara.Button(label=f"Force Rerender: {force_rerender.value}",
                            on_click=lambda: force_rerender.set(force_rerender.value + 1), color="grey")
                
            # solara.Button(label="Toggle Debug", on_click=lambda: showDebug.set(not showDebug.value))