Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store component #248

Merged
merged 17 commits into from Oct 3, 2018

Conversation

Projects
None yet
10 participants
@T4rk1n
Copy link
Contributor

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

This comment has been minimized.

Copy link
Member

commented Jul 30, 2018

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

@nicolaskruchten

This comment has been minimized.

Copy link
Member

commented Jul 31, 2018

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

@mkhorton

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

commented Jul 31, 2018

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

This comment has been minimized.

Copy link
Contributor

commented Jul 31, 2018

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

This comment has been minimized.

Copy link
Member

commented Aug 10, 2018

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

This comment has been minimized.

Copy link
Member

commented Aug 10, 2018

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

This comment has been minimized.

Copy link
Contributor Author

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.

@valentijnnieman
Copy link
Contributor

left a comment

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 = {};

This comment has been minimized.

Copy link
@valentijnnieman

valentijnnieman Aug 22, 2018

Contributor

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);

This comment has been minimized.

Copy link
@valentijnnieman

valentijnnieman Aug 22, 2018

Contributor

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;

This comment has been minimized.

Copy link
@valentijnnieman

valentijnnieman Aug 22, 2018

Contributor

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()

This comment has been minimized.

Copy link
@T4rk1n

T4rk1n Sep 19, 2018

Author Contributor

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

@T4rk1n T4rk1n force-pushed the storage-component branch from 4df22ee to bff4bcf Sep 13, 2018

@T4rk1n

This comment has been minimized.

Copy link
Contributor Author

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 T4rk1n force-pushed the storage-component branch from da5bbf3 to beaef00 Sep 21, 2018

@T4rk1n

This comment has been minimized.

Copy link
Contributor Author

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 force-pushed the storage-component branch from b3e5af7 to ed951fb Oct 2, 2018

@T4rk1n T4rk1n changed the title Storage component Store component Oct 2, 2018

@T4rk1n T4rk1n force-pushed the storage-component branch from ed951fb to 98feaa4 Oct 2, 2018

@T4rk1n T4rk1n merged commit 837bd81 into master Oct 3, 2018

4 checks passed

ci/circleci: python-2.7 Your tests passed on CircleCI!
Details
ci/circleci: python-3.6 Your tests passed on CircleCI!
Details
ci/circleci: python-3.7 Your tests passed on CircleCI!
Details
percy/dash-core-components Visual review automatically approved, no visual changes found.
Details

@T4rk1n T4rk1n deleted the storage-component branch Oct 3, 2018

@tguptaMT

This comment has been minimized.

Copy link

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')))`
@plopd

This comment has been minimized.

Copy link

commented Feb 23, 2019

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

@lioneltrebuchon

This comment has been minimized.

Copy link

commented Feb 27, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.