From fab0ff35f1f8b5e48174d7c1592c71f876b0f860 Mon Sep 17 00:00:00 2001 From: Spencer Ahrens Date: Tue, 6 Sep 2016 14:44:15 -0700 Subject: [PATCH] Introduce UIExplorerStatePersister and use for search filter Reviewed By: fkgozali Differential Revision: D3817864 fbshipit-source-id: 55ca7cc66b53714d881c0593d5441d2ad1e44207 --- .../UIExplorer/js/UIExplorerExampleList.js | 50 ++++++----- .../UIExplorer/js/UIExplorerStatePersister.js | 88 +++++++++++++++++++ 2 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 Examples/UIExplorer/js/UIExplorerStatePersister.js diff --git a/Examples/UIExplorer/js/UIExplorerExampleList.js b/Examples/UIExplorer/js/UIExplorerExampleList.js index dc7434502..1186bbac0 100644 --- a/Examples/UIExplorer/js/UIExplorerExampleList.js +++ b/Examples/UIExplorer/js/UIExplorerExampleList.js @@ -30,35 +30,41 @@ const TextInput = require('TextInput'); const TouchableHighlight = require('TouchableHighlight'); const View = require('View'); const UIExplorerActions = require('./UIExplorerActions'); +const UIExplorerStatePersister = require('./UIExplorerStatePersister'); import type { UIExplorerExample, } from './UIExplorerList.ios'; +import type { + PassProps, +} from './UIExplorerStatePersister'; + +import type { + StyleObj, +} from 'StyleSheetTypes'; + const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2, sectionHeaderHasChanged: (h1, h2) => h1 !== h2, }); +type Props = { + onNavigate: Function, + list: { + ComponentExamples: Array, + APIExamples: Array, + }, + persister: PassProps<*>, + searchTextInputStyle: StyleObj, + style?: ?StyleObj, +}; + class UIExplorerExampleList extends React.Component { - - state = {filter: ''}; - - constructor(props: { - disableTitleRow: ?boolean, - onNavigate: Function, - filter: ?string, - list: { - ComponentExamples: Array, - APIExamples: Array, - }, - style: ?any, - }) { - super(props); - } + props: Props render(): ?ReactElement { - const filterText = this.state.filter || ''; + const filterText = this.props.persister.state.filter; const filterRegex = new RegExp(String(filterText), 'i'); const filter = (example) => filterRegex.test(example.module.title); @@ -111,12 +117,12 @@ class UIExplorerExampleList extends React.Component { autoCorrect={false} clearButtonMode="always" onChangeText={text => { - this.setState({filter: text}); + this.props.persister.setState(() => ({filter: text})); }} placeholder="Search..." style={[styles.searchTextInput, this.props.searchTextInputStyle]} testID="explorer_search" - value={this.state.filter} + value={this.props.persister.state.filter} /> ); @@ -162,6 +168,11 @@ class UIExplorerExampleList extends React.Component { } } +UIExplorerExampleList = UIExplorerStatePersister.createContainer(UIExplorerExampleList, { + cacheKeySuffix: () => 'mainList', + getInitialState: () => ({filter: ''}), +}); + const styles = StyleSheet.create({ listContainer: { flex: 1, @@ -174,9 +185,6 @@ const styles = StyleSheet.create({ fontWeight: '500', fontSize: 11, }, - group: { - backgroundColor: 'white', - }, row: { backgroundColor: 'white', justifyContent: 'center', diff --git a/Examples/UIExplorer/js/UIExplorerStatePersister.js b/Examples/UIExplorer/js/UIExplorerStatePersister.js new file mode 100644 index 000000000..508065a5e --- /dev/null +++ b/Examples/UIExplorer/js/UIExplorerStatePersister.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +const AsyncStorage = require('AsyncStorage'); +const React = require('React'); + +export type PassProps = { + state: State, + setState: (stateLamda: (state: State) => State) => void, +}; + +/** + * A simple container for persisting some state and passing it into the wrapped component as + * `props.persister.state`. Update it with `props.persister.setState`. The component is initially + * rendered using `getInitialState` in the spec and is then re-rendered with the persisted data + * once it's fetched. + * + * This is currently tied to UIExplorer because it's generally not good to use AsyncStorage like + * this in real apps with user data, but we could maybe pull it out for other internal settings-type + * usage. + */ +function createContainer( + Component: ReactClass}>, + spec: { + cacheKeySuffix: (props: Props) => string, + getInitialState: (props: Props) => State, + version?: string, + }, +): ReactClass { + return class ComponentWithPersistedState extends React.Component { + props: Props; + static displayName = `UIExplorerStatePersister(${Component.displayName || Component.name})`; + state = {value: spec.getInitialState(this.props)}; + _cacheKey = `UIExplorer:${spec.version || 'v1'}:${spec.cacheKeySuffix(this.props)}`; + componentDidMount() { + AsyncStorage.getItem(this._cacheKey, (err, value) => { + if (!err && value) { + this.setState({value: JSON.parse(value)}); + } + }); + } + _passSetState = (stateLamda: (state: State) => State): void => { + this.setState((state) => { + const value = stateLamda(state.value); + AsyncStorage.setItem(this._cacheKey, JSON.stringify(value)); + return {value}; + }); + }; + render(): React.Element<*> { + return ( + + ); + } + }; +} + +const UIExplorerStatePersister = { + createContainer, +}; + +module.exports = UIExplorerStatePersister;