Skip to content

Instantly share code, notes, and snippets.

@fxg42
Last active March 30, 2017 12:38
Show Gist options
  • Save fxg42/ddfb3fd0bb41ed02b0375fb64c618db3 to your computer and use it in GitHub Desktop.
Save fxg42/ddfb3fd0bb41ed02b0375fb64c618db3 to your computer and use it in GitHub Desktop.
Conditional selects...
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