Skip to content

Local Storage

A browser local storage provider. Synchronizes the given context model with the browser local storage. The provider blocks the rendering of any child components until the the model has been synchronized with the browser.

The context model is stored in the browser's local storage as unencrypted stringified json (see encryption example below).

 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
from reactpy import component, event, html, use_context, use_state

from reactpy_utils import LocalStorageProvider
from reactpy_utils.types import EventArgs

from .app_context import AppContext, AppState


@component
def App():
    app_state, set_app_state = use_state(AppState())

    return AppContext(
        LocalStorageProvider(ExamplePage(), ctx=AppContext, storage_key="local-storage-example"),
        value=(app_state, set_app_state),
    )


@component
def ExamplePage():
    app_state, set_app_state = use_context(AppContext)

    @event
    def on_click(_evt: EventArgs):
        set_app_state(app_state.update(dark_mode=not app_state.dark_mode))

    return html.div(
        html.h2({"id": "h2"}, f"dark_mode={app_state.dark_mode}"),
        html.button({"id": "toggle_btn", "on_click": on_click}, "Toggle Dark Mode"),
    )
1
2
3
4
5
6
7
8
from reactpy_utils import DynamicContextModel, create_dynamic_context


class AppState(DynamicContextModel):
    dark_mode: bool = True


AppContext = create_dynamic_context(AppState)

Encrypting Local Storage Data

The data stored in the browser can easily be encrypted by sub-classing DynamicContextModel. The following example uses Fernet symmetric encryption.

Use Fernet.generate_key() to create keys. The key remains on the server, only encrypted data is sent to the browser.

 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
import base64
import json

from cryptography.fernet import Fernet
from typing_extensions import Self

from reactpy_utils import DynamicContextModel, create_dynamic_context

# Use Fernet.generate_key() to create keys. The key remains on
# the server, only encrypted data is sent to the browser.

fernet = Fernet(b"3qgHqyfztBTIDpPc1AFYt9vPXQ1Ni5lF4vwfhaMzWBs=")


def decode(data: str) -> dict:
    """Decode encrypted json data to a json object"""
    encMessage = base64.b64decode(data)
    plane = fernet.decrypt(encMessage).decode()
    return json.loads(plane)


def encode(plain_text: str) -> str:
    """Encode the plain_text stringified json object to an encrypted, base64, stringified json object"""
    encMessage = fernet.encrypt(plain_text.encode())
    encMessage64 = base64.b64encode(encMessage).decode("utf-8")
    return json.dumps({"data": encMessage64})


class EncryptedDynamicContextBase(DynamicContextModel):
    """Fernet encryption base class. The fields of all models derived from this
    class will be stored in an a encrypted form in the browser"""

    def update(self: Self, **kwargs) -> Self:
        kwargs = decode(**kwargs)
        return super().update(**kwargs)

    def dumps(self, sort_keys=True) -> str:
        plane_text = super().dumps(sort_keys=sort_keys)
        return encode(plane_text)


# Example usage of defining an encrypted user model & context


class UserState(EncryptedDynamicContextBase):
    user_name: str
    password: str


UserContext = create_dynamic_context(UserState)

The same pattern can be applied to any desired encryption method.


Last update: November 28, 2024
Authors: Steve Jones