Last active
March 30, 2017 12:38
-
-
Save fxg42/ddfb3fd0bb41ed02b0375fb64c618db3 to your computer and use it in GitHub Desktop.
Conditional selects...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react' | |
import ReactDOM from 'react-dom' | |
import thunk from 'redux-thunk' | |
import logger from 'redux-logger' | |
import { Provider, connect } from 'react-redux' | |
import { createStore, applyMiddleware } from 'redux' | |
import Immutable from 'immutable' | |
import './index.css' | |
// | |
// Simulate server calls | |
// | |
const fetchChildOptions = (parentValue) => | |
Promise.resolve([1, 2, 3].map((x) => `${parentValue}.${x}`)) | |
const fetchSearchResults = (queryParams) => | |
Promise.resolve(["result 1", "result 2", "result 3"]) | |
// | |
// Asynchronous action creator. Fetches list of sub options. | |
// | |
const getChildOptions = (level, parentValue) => async (dispatch) => { | |
const data = await fetchChildOptions(parentValue) | |
dispatch({ type:'FETCH_OPTIONS_SUCCESS', level, data }) | |
} | |
// | |
// Asynchronous action creator. Fetches search results | |
// | |
const doSearch = () => async (dispatch, getState) => { | |
const selectedValues = getState().get('selects').map(s => s.get('value')).toJS() | |
const searchResults = await fetchSearchResults(selectedValues) | |
dispatch({ type:'FETCH_SEARCH_RESULTS_SUCCESS', data: searchResults }) | |
} | |
// | |
// Synchronous action creator. Sets a selected value. | |
// | |
const setValue = (level, value) => | |
({ type: 'SET_SELECT_VALUE', level, data: value }) | |
// | |
// Reducer and initial state. | |
// | |
const emptySelect = Immutable.fromJS({ value: "", enabled: false }) | |
const emptyEnabledSelect = Immutable.fromJS({ value: "", enabled: true }) | |
const emptyOptions = Immutable.fromJS([ { value: "", display: "" } ]) | |
const initialState = Immutable.fromJS({ | |
selects: [ | |
emptySelect, | |
emptySelect, | |
emptySelect, | |
emptySelect, | |
emptySelect, | |
], | |
selectOptions: [ | |
emptyOptions, | |
emptyOptions, | |
emptyOptions, | |
emptyOptions, | |
emptyOptions, | |
], | |
searchEnabled: false, | |
}) | |
const reducer = (state = initialState, action) => { | |
switch (action.type) { | |
case 'SET_SELECT_VALUE': | |
return state | |
// Sets the given value in the `selects` list at the index that corresponds to the given level. | |
.setIn(['selects', action.level, 'value'], action.data) | |
// Enables the search button when selecting a value for the lowest select. | |
.set('searchEnabled', action.level === state.get('selects').size - 1) | |
case 'FETCH_OPTIONS_SUCCESS': | |
return state | |
// Enables the select list at the given level and disables all selects after it. | |
.update('selects', (selects) => selects.map((select, idx) => { | |
if (idx < action.level) return select | |
if (idx === action.level) return emptyEnabledSelect | |
else return emptySelect | |
})) | |
// Sets the given option list at the given level and erases all those after it. | |
.update('selectOptions', (selectOptions) => selectOptions.map((options, idx) => { | |
if (idx < action.level) return options | |
if (idx === action.level) return emptyOptions.concat(Immutable.fromJS(action.data.map((value) => ({ value:value, display:value })))) | |
else return emptyOptions | |
})) | |
case 'FETCH_SEARCH_RESULTS_SUCCESS': | |
// do something useful with results... | |
return state | |
default: | |
return state | |
} | |
} | |
// | |
// Select Component | |
// | |
let Select = ({ select, options, level, onChange }) => | |
<select value={ select.value } disabled={ ! select.enabled } onChange={ onChange(level) }> | |
{options.map((option, idx) => | |
<option key={ idx } value={ option.value }>{ option.display }</option> | |
)} | |
</select> | |
Select = connect(null, (dispatch) => ({ | |
onChange: (level) => (e) => { | |
const value = e.target.value | |
dispatch(setValue(level, value)) | |
dispatch(getChildOptions(level+1, value)) | |
} | |
}))(Select) | |
// | |
// Main Component | |
// | |
let App = ({ searchEnabled, onSearch, selects, options }) => | |
<div> | |
<Select select={ selects[0] } options={ options[0] } level={ 0 } /> | |
<br/> | |
<Select select={ selects[1] } options={ options[1] } level={ 1 } /> | |
<br/> | |
<Select select={ selects[2] } options={ options[2] } level={ 2 } /> | |
<br/> | |
<Select select={ selects[3] } options={ options[3] } level={ 3 } /> | |
<br/> | |
<Select select={ selects[4] } options={ options[4] } level={ 4 } /> | |
<br/> | |
<button disabled={ ! searchEnabled } onClick={ onSearch }>Search</button> | |
</div> | |
App = connect( | |
(state) => ({ | |
selects: state.get('selects').toJS(), | |
options: state.get('selectOptions').toJS(), | |
searchEnabled: state.get('searchEnabled'), | |
}), | |
(dispatch) => ({ | |
onSearch: (e) => dispatch(doSearch()) | |
}) | |
)(App) | |
// | |
// main | |
// | |
const main = () => { | |
const store = createStore(reducer, applyMiddleware(thunk, logger())) | |
ReactDOM.render(<Provider store={ store }><App/></Provider>, document.getElementById('root')) | |
store.dispatch({ type:'FETCH_OPTIONS_SUCCESS', level:0, data: ["0", "1", "2"] }) | |
} | |
document.addEventListener('DOMContentLoaded', main) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment