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

Commit bff4bcf

Browse files
committed
Add memory storage option.
1 parent 9a03708 commit bff4bcf

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
@@ -1147,19 +1147,23 @@ def render_content(click, prev_graph):
11471147
def test_storage_component(self):
11481148
app = dash.Dash(__name__)
11491149

1150-
getter = 'return window.sessionStorage.getItem("{}");'
1151-
clicked_getter = getter.format('storage')
1152-
dummy_getter = getter.format('dummy')
1153-
dummy_data = 'Hello world'
1150+
getter = 'return window.{}.getItem("{}");'
1151+
clicked_getter = getter.format('localStorage', 'storage')
1152+
dummy_getter = getter.format('sessionStorage', 'dummy')
1153+
dummy_data = 'Hello dummy'
11541154

11551155
app.layout = html.Div([
11561156
dcc.Storage(id='storage',
1157-
storage_type='session'),
1157+
storage_type='local'),
11581158
html.Button('click me', id='btn'),
11591159
html.Button('clear', id='clear-btn'),
11601160
dcc.Storage(id='dummy',
11611161
storage_type='session',
1162-
data=dummy_data)
1162+
data=dummy_data),
1163+
dcc.Storage(id='memory',
1164+
storage_type='memory'),
1165+
html.Div(id='memory-output')
1166+
11631167
])
11641168

11651169
@app.callback(Output('storage', 'data'),
@@ -1178,6 +1182,17 @@ def on_clear(n_clicks):
11781182
return
11791183
return True
11801184

1185+
@app.callback(Output('memory', 'data'), [Input('storage', 'data')])
1186+
def on_memory(data):
1187+
return data
1188+
1189+
@app.callback(Output('memory-output', 'children'),
1190+
[Input('memory', 'data')])
1191+
def on_memory2(data):
1192+
if data is None:
1193+
return ''
1194+
return json.dumps(data)
1195+
11811196
self.startServer(app)
11821197

11831198
time.sleep(1)
@@ -1187,16 +1202,20 @@ def on_clear(n_clicks):
11871202

11881203
click_btn = self.wait_for_element_by_css_selector('#btn')
11891204
clear_btn = self.wait_for_element_by_css_selector('#clear-btn')
1205+
mem = self.wait_for_element_by_css_selector('#memory-output')
11901206

1191-
for i in range(10):
1207+
for i in range(1, 11):
11921208
click_btn.click()
1193-
time.sleep(0.5)
1209+
time.sleep(1)
11941210

11951211
click_data = json.loads(self.driver.execute_script(clicked_getter))
1196-
self.assertEqual(i+1, click_data.get('clicked'))
1212+
self.assertEqual(i, click_data.get('clicked'))
1213+
self.assertEquals(i, int(json.loads(mem.text).get('clicked')))
11971214

11981215
clear_btn.click()
1199-
time.sleep(0.5)
1216+
time.sleep(1)
12001217

12011218
cleared_data = self.driver.execute_script(clicked_getter)
12021219
self.assertTrue(cleared_data is None)
1220+
# Did mem also got cleared ?
1221+
self.assertFalse(mem.text)

0 commit comments

Comments
 (0)