Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Store component #248

Merged
merged 17 commits into from
Oct 3, 2018
Merged

Store component #248

merged 17 commits into from
Oct 3, 2018

Conversation

T4rk1n
Copy link
Contributor

@T4rk1n T4rk1n commented Jul 30, 2018

Available in dash-core-components==0.30.0rc1

pip install dash-core-components==0.30.0rc1

Store component

Alternative to the hidden div method for caching data on the front-end, no need to json.dumps and json.loads.

Three type of storage (storage_type prop):

  • memory - Data is only kept in memory as prop by the component, cleared on refresh.
  • local - Data is kept in window.localStorage, only cleared manually.
  • session - Data is kept in window.sessionStorage, cleared on browser exit.

** Data must be json serializable. **

Example:

import dash

import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Output, Input, State
from dash.exceptions import PreventUpdate

app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Store(id='storage'),
    html.Button('click me', id='btn'),
    html.Button('clear me', id='clear-btn'),
])

@app.callback(Output('storage', 'data'),
              [Input('btn', 'n_clicks')],
              [State('storage', 'data')])
def on_click(n_clicks, storage):
    if n_clicks is None:
        raise PreventUpdate
    storage = storage or {}
    return {'clicked': storage.get('clicked', 0) + 1}


@app.callback(Output('storage', 'clear_data'),
              [Input('clear-btn', 'n_clicks')])
def on_clear(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    return True


if __name__ == '__main__':
    app.run_server(debug=True, threaded=True, port=11111)

cc @plotly/dash

Closes #229

@bpostlethwaite
Copy link
Member

cc @nicolaskruchten - I know you have were thinking about a dcc.Store component.

@nicolaskruchten
Copy link
Contributor

Yep, although I was mostly concerned about security: plotly/dash#262

@mkhorton
Copy link

mkhorton commented Jul 31, 2018

I think this is a great idea, even without server-side encryption this would be a nice improvement over the current hidden div hack.

Is there a way to persist this data between different sessions? Edit: sorry, I see this would be the local option

@mkhorton
Copy link

Also, this seems orthogonal to the issue of whether or not to use e.g. Redis too.

A more comprehensive solution using something like Redis to store user data would still be useful if there's large user-specific data that only needs to exist server-side, since it would reduce traffic needed to pass data between components, but that obviously requires a lot of extra set-up. Being able to use HTML localStorage with 'off-the-shelf' Dash would be really nice in my opinion, and be sufficient for most people (myself included).

@rmarren1
Copy link
Contributor

This seems a lot cleaner than the hidden div hack, and I think it is useful enough to release without encryption and then add encryption as a separate PR, as long as we make it clear in the docs the stored data is available to the client

@chriddyp
Copy link
Member

This looks good to me.

My main feedback is about the name. I wonder if we should be clear that this is client-side storage. Some different name ideas:

  • Store
  • BrowserStore
  • ClientStore
  • Storage
  • BrowserStorage
  • ClientStorage

I think I prefer BrowserStore

I would also like @ned2 's opinion on this.

Otherwise, I'm 💃 on this in principle. I haven't taken a look into the code

@chriddyp
Copy link
Member

local - Data is kept in window.localStorage, only cleared manually.
session - Data is kept in window.sessionStorage, cleared on browser exit.

I'd also like to play around with this a little bit actually. It's not clear to me how this would fit into a set of callbacks.

i.e. how would you structure your code so that the backend knows whether data is already available in the store or it needs to be added?

perhaps we could add a lightweight "date_modified" property and the dev could have that property as an Input to their callback. If it's -1, then they need to supply data. Otherwise, they can raise PreventUpdate:

@app.callback(Output('datastore', 'value'), [Input('datastore', 'date_modified')])
def update_datastore(date_modified):
    if date_modified is None or date_modified == -1:
        raise dash.exceptions.PreventDefault()
    return expensive_computation()

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Aug 13, 2018

i.e. how would you structure your code so that the backend knows whether data is already available in the store or it needs to be added?

That is one of the issue I had, I think adding a timestamp should help, I'll try that.

My main feedback is about the name. I wonder if we should be clear that this is client-side storage.

The api for the local and session is called WebStorage that is why I put Storage.

I prefer ClientStorage.

Copy link
Contributor

@valentijnnieman valentijnnieman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Got a couple of comments though. I'm also wondering why you would need a timestamp or anything as such - wouldn't you just be able to compare the data coming from props to the storage, and update accordingly?


class MemStore {
constructor() {
this._data = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about why you're not using React's state here, as in this.state = {data:{}}? Using React's state and methods like setState would be more clear to React programmers, and then there's no need to write class methods like getItem etc.

}
}

const _localStore = new WebStore(window.localStorage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, wouldn't it be better to define these on the component itself, i.e. Storage? Although a WebStore class is nice, I think it gets a little bit too convoluted for React - one of the nice things of React is writing all your code up in components.

super(props);

if (props.storage_type === 'local') {
this._backstore = _localStore;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're already setting it here, couldn't you just use localStorage here (or window.localStorage, although I think window isn't needed) and use the API as is? i.e. localStorage.setItem() and getItem()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also wrap getItem with JSON.parse and and setItem with JSON.stringify.

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Sep 19, 2018

@plotly/dash Ready for another round of reviews, I added a timestamp for when the data is modified.

Available in version 0.30.0rc1.

I made a poll for the component name: https://strawpoll.com/ed9ychp8

@T4rk1n
Copy link
Contributor Author

T4rk1n commented Oct 2, 2018

Renaming the storage component to Store according to the poll results.

54.55 % (6 votes)Store
18.18 % (2 votes)ClientStore
18.18 % (2 votes)Storage
9.09 % (1 votes)BrowserStore
0 % (0 votes)BrowserStorage
0 % (0 votes)ClientStorage
11 total votes.

@T4rk1n T4rk1n changed the title Storage component Store component Oct 2, 2018
@T4rk1n T4rk1n merged commit 837bd81 into master Oct 3, 2018
@T4rk1n T4rk1n deleted the storage-component branch October 3, 2018 15:12
@tguptaMT
Copy link

tguptaMT commented Feb 19, 2019

dash-core-components==0.30.0rc1 seems to hide my graph's axis labels somehow. If I switch to the latest version of dcc, the axis titles (labels) are visible but the dcc.Storage component isn't available in dcc==0.43.0. If I move to dcc==0.30.0rc1, Storage is available but the axis labels on my graph disappear. Are there any syntax incompatibility issues with the following code:

`layout = go.Layout(
xaxis=dict(
title='Time (Weeks)',
titlefont=dict(
family='Courier New, monospace',
size=18,
color='#07329C')),

        yaxis=dict(
            title='Median Aggresssion',
            titlefont=dict(
                family='Courier New, monospace',
                size=18,
                color='#07329C')))`

@dantp-ai
Copy link

Why is dcc.Storage not available in the latest version of dash-core-components==0.43.0

@lioneltrebuchon
Copy link

@plopd I think it was renamed to dcc.Store based on a previous poll.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants