Function leptos_use::storage::use_storage

source ·
pub fn use_storage<T, C>(
    storage_type: StorageType,
    key: impl AsRef<str>,
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
where T: Default + Clone + PartialEq, C: Encoder<T, Encoded = String> + Decoder<T, Encoded = str>,
Expand description

Reactive Storage.

The function returns a triplet (read_signal, write_signal, delete_from_storage_fn).

§Demo

Link to Demo

§Usage

Pass a StorageType to determine the kind of key-value browser storage to use. The specified key is where data is stored. All values are stored as UTF-16 strings which is then encoded and decoded via the given *Codec. This value is synced with other calls using the same key on the same page and across tabs for local storage. See UseStorageOptions to see how behavior can be further customised.

Values are (en)decoded via the given codec. You can use any of the string codecs or a binary codec wrapped in Base64.

Please check the codec chapter to see what codecs are available and what feature flags they require.

§Example

// Binds a struct:
let (state, set_state, _) = use_local_storage::<MyState, JsonSerdeCodec>("my-state");

// Binds a bool, stored as a string:
let (flag, set_flag, remove_flag) = use_session_storage::<bool, FromToStringCodec>("my-flag");

// Binds a number, stored as a string:
let (count, set_count, _) = use_session_storage::<i32, FromToStringCodec>("my-count");
// Binds a number, stored in JSON:
let (count, set_count, _) = use_session_storage::<i32, JsonSerdeCodec>("my-count-kept-in-js");

// Bind string with SessionStorage stored in ProtoBuf format:
let (id, set_id, _) = use_storage::<String, Base64<ProstCodec>>(
    StorageType::Session,
    "my-id",
);

// Data stored in JSON must implement Serialize, Deserialize.
// And you have to add the feature "serde" to your project's Cargo.toml
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub struct MyState {
    pub hello: String,
    pub greeting: String,
}

// Default can be used to implement initial or deleted values.
// You can also use a signal via UseStorageOptions::default_value`
impl Default for MyState {
    fn default() -> Self {
        Self {
            hello: "hi".to_string(),
            greeting: "Hello".to_string()
        }
    }
}

§Server-Side Rendering

On the server the returned signals will just read/manipulate the initial_value without persistence.

If you use a value from storage to control conditional rendering you might run into issues with hydration.

let (flag, set_flag, _) = use_session_storage::<bool, FromToStringCodec>("my-flag");

view! {
    <Show when=move || flag.get()>
        <div>Some conditional content</div>
    </Show>
}

You can see hydration warnings in the browser console and the conditional parts of the app might never show up when rendered on the server and then hydrated in the browser. The reason for this is that the server has no access to storage and therefore will always use initial_value as described above. So on the server your app is always rendered as if the value from storage was initial_value. Then in the browser the actual stored value is used which might be different, hence during hydration the DOM looks different from the one rendered on the server which produces the hydration warnings.

The recommended way to avoid this is to use use_cookie instead because values stored in cookies are available on the server as well as in the browser.

If you still want to use storage instead of cookies you can use the delay_during_hydration option that will use the initial_value during hydration just as on the server and delay loading the value from storage by an animation frame. This gets rid of the hydration warnings and makes the app correctly render things. Some flickering might be unavoidable though.

let (flag, set_flag, _) = use_local_storage_with_options::<bool, FromToStringCodec>(
    "my-flag",
    UseStorageOptions::default().delay_during_hydration(true),
);

view! {
    <Show when=move || flag.get()>
        <div>Some conditional content</div>
    </Show>
}