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

Commit 4df22ee

Browse files
committed
Add memory storage option.
1 parent 011d384 commit 4df22ee

File tree

2 files changed

+101
-37
lines changed

2 files changed

+101
-37
lines changed

src/components/Storage.react.js

Lines changed: 72 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,110 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33

4-
/**
5-
* Wrapper around the Web Storage api.
6-
* Persistent data storage on the client side. Keep the data upon refreshes.
7-
*/
8-
export default class Storage extends React.Component {
9-
constructor(props) {
10-
super(props);
11-
this._backstore = props.storage_type === 'local' ?
12-
window.localStorage : window.sessionStorage;
13-
this.onStorageChange = this.onStorageChange.bind(this);
4+
5+
class MemStore {
6+
constructor() {
7+
this._data = {};
8+
}
9+
10+
getItem(key) {
11+
return this._data[key];
12+
}
13+
14+
setItem(key, value) {
15+
this._data[key] = value;
16+
}
17+
18+
removeItem(key) {
19+
delete this._data[key];
20+
}
21+
}
22+
23+
class WebStore {
24+
constructor(storage) {
25+
this._storage = storage;
1426
}
1527

1628
getItem(key) {
17-
return JSON.parse(this._backstore.getItem(key));
29+
return JSON.parse(this._storage.getItem(key));
1830
}
1931

2032
setItem(key, value) {
21-
this._backstore.setItem(key, typeof value === 'string' ?
33+
this._storage.setItem(key, typeof value === 'string' ?
2234
value : JSON.stringify(value));
2335
}
2436

2537
removeItem(key) {
26-
this._backstore.removeItem(key);
38+
this._storage.removeItem(key);
39+
}
40+
}
41+
42+
const _localStore = new WebStore(window.localStorage);
43+
const _sessionStore = new WebStore(window.sessionStorage);
44+
45+
/**
46+
* Easily keep data on the client side with this component.
47+
* The data is not inserted in the DOM.
48+
* Data can be in memory, localStorage or sessionStorage.
49+
* The data will be kept with the id as key.
50+
*/
51+
export default class Storage extends React.Component {
52+
constructor(props) {
53+
super(props);
54+
55+
if (props.storage_type === 'local') {
56+
this._backstore = _localStore;
57+
} else if (props.storage_type === 'session') {
58+
this._backstore = _sessionStore;
59+
} else if (props.storage_type === 'memory') {
60+
this._backstore = new MemStore();
61+
}
62+
63+
this.onStorageChange = this.onStorageChange.bind(this);
2764
}
2865

2966
onStorageChange(e) {
30-
const { id, setProps} = this.props;
67+
const { id, setProps } = this.props;
3168
if (e.key === id && setProps && e.newValue !== e.oldValue) {
3269
setProps({data: JSON.parse(e.newValue)});
3370
}
3471
}
3572

3673
componentWillMount() {
37-
window.addEventListener('storage', this.onStorageChange);
38-
const { setProps, id, data } = this.props;
74+
const { setProps, id, data, storage_type } = this.props;
75+
if (storage_type !== 'memory') {
76+
window.addEventListener('storage', this.onStorageChange);
77+
}
78+
3979
if (setProps) {
4080
// Take the data from storage, ignore the prop data on load.
41-
const data = this.getItem(id);
42-
if (data) {
43-
setProps({data});
81+
const d = this._backstore.getItem(id);
82+
if (d !== data) {
83+
setProps({data: d});
4484
return;
4585
}
4686
}
87+
4788
if (data) {
48-
this.setItem(id, data);
89+
this._backstore.setItem(id, data);
4990
}
5091
}
5192

5293
componentWillUnmount() {
53-
window.removeEventListener('storage', this.onStorageChange);
94+
if (this.props.storage_type !== 'memory') {
95+
window.removeEventListener('storage', this.onStorageChange);
96+
}
5497
}
5598

5699
componentDidUpdate() {
57100
const { data, id, clear_data, setProps } = this.props;
58101
if (clear_data) {
59-
this.removeItem(id);
102+
this._backstore.removeItem(id);
60103
if (setProps) {
61104
setProps({clear_data: false, data: null})
62105
}
63106
} else if (data) {
64-
this.setItem(id, data);
107+
this._backstore.setItem(id, data);
65108
}
66109
}
67110

@@ -71,7 +114,7 @@ export default class Storage extends React.Component {
71114
}
72115

73116
Storage.defaultProps = {
74-
storage_type: 'local',
117+
storage_type: 'memory',
75118
clear_data: false
76119
};
77120

@@ -83,10 +126,12 @@ Storage.propTypes = {
83126

84127
/**
85128
* The type of the web storage.
86-
* local -> window.localStorage: data is kept after the browser quit.
87-
* session -> window.sessionStorage: data is cleared once the browser quit.
129+
*
130+
* memory: only kept in memory, reset on page refresh.
131+
* local: window.localStorage, data is kept after the browser quit.
132+
* session: window.sessionStorage, data is cleared once the browser quit.
88133
*/
89-
storage_type: PropTypes.oneOf(['local', 'session']),
134+
storage_type: PropTypes.oneOf(['local', 'session', 'memory']),
90135

91136
/**
92137
* The stored data for the key.

test/test_integration.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -797,19 +797,23 @@ def test_confirm_dialog_provider(self):
797797
def test_storage_component(self):
798798
app = dash.Dash(__name__)
799799

800-
getter = 'return window.sessionStorage.getItem("{}");'
801-
clicked_getter = getter.format('storage')
802-
dummy_getter = getter.format('dummy')
803-
dummy_data = 'Hello world'
800+
getter = 'return window.{}.getItem("{}");'
801+
clicked_getter = getter.format('localStorage', 'storage')
802+
dummy_getter = getter.format('sessionStorage', 'dummy')
803+
dummy_data = 'Hello dummy'
804804

805805
app.layout = html.Div([
806806
dcc.Storage(id='storage',
807-
storage_type='session'),
807+
storage_type='local'),
808808
html.Button('click me', id='btn'),
809809
html.Button('clear', id='clear-btn'),
810810
dcc.Storage(id='dummy',
811811
storage_type='session',
812-
data=dummy_data)
812+
data=dummy_data),
813+
dcc.Storage(id='memory',
814+
storage_type='memory'),
815+
html.Div(id='memory-output')
816+
813817
])
814818

815819
@app.callback(Output('storage', 'data'),
@@ -828,6 +832,17 @@ def on_clear(n_clicks):
828832
return
829833
return True
830834

835+
@app.callback(Output('memory', 'data'), [Input('storage', 'data')])
836+
def on_memory(data):
837+
return data
838+
839+
@app.callback(Output('memory-output', 'children'),
840+
[Input('memory', 'data')])
841+
def on_memory2(data):
842+
if data is None:
843+
return ''
844+
return json.dumps(data)
845+
831846
self.startServer(app)
832847

833848
time.sleep(1)
@@ -837,16 +852,20 @@ def on_clear(n_clicks):
837852

838853
click_btn = self.wait_for_element_by_css_selector('#btn')
839854
clear_btn = self.wait_for_element_by_css_selector('#clear-btn')
855+
mem = self.wait_for_element_by_css_selector('#memory-output')
840856

841-
for i in range(10):
857+
for i in range(1, 11):
842858
click_btn.click()
843-
time.sleep(0.5)
859+
time.sleep(1)
844860

845861
click_data = json.loads(self.driver.execute_script(clicked_getter))
846-
self.assertEqual(i+1, click_data.get('clicked'))
862+
self.assertEqual(i, click_data.get('clicked'))
863+
self.assertEquals(i, int(json.loads(mem.text).get('clicked')))
847864

848865
clear_btn.click()
849-
time.sleep(0.5)
866+
time.sleep(1)
850867

851868
cleared_data = self.driver.execute_script(clicked_getter)
852869
self.assertTrue(cleared_data is None)
870+
# Did mem also got cleared ?
871+
self.assertFalse(mem.text)

0 commit comments

Comments
 (0)