diff options
-rw-r--r-- | src/client/react/components/container/Results.jsx | 20 | ||||
-rw-r--r-- | src/client/react/components/container/Search.jsx | 10 | ||||
-rw-r--r-- | src/client/react/components/presentational/Result.jsx | 16 | ||||
-rw-r--r-- | src/client/react/reducers/search.js | 26 | ||||
-rw-r--r-- | src/client/react/users.js | 66 |
5 files changed, 96 insertions, 42 deletions
diff --git a/src/client/react/components/container/Results.jsx b/src/client/react/components/container/Results.jsx index 9be2639..911ea27 100644 --- a/src/client/react/components/container/Results.jsx +++ b/src/client/react/components/container/Results.jsx @@ -4,27 +4,22 @@ import { connect } from 'react-redux'; import classnames from 'classnames'; import Result from '../presentational/Result'; -const Results = (({ results, selectedResult }) => ( +const Results = (({ results, isExactMatch, selectedResult }) => ( <div className={classnames('search__results', { - 'search__results--has-results': results.length > 0, + 'search__results--has-results': !isExactMatch && results.length > 0, })} > - {results.map(user => ( - <Result key={user.value} user={user} selected={user === selectedResult} /> + {!isExactMatch && results.map(userId => ( + <Result key={userId} userId={userId} isSelected={userId === selectedResult} /> ))} </div> )); Results.propTypes = { - results: PropTypes.arrayOf(PropTypes.shape({ - type: PropTypes.string, - value: PropTypes.string, - })).isRequired, - selectedResult: PropTypes.shape({ - type: PropTypes.string, - value: PropTypes.string, - }), + results: PropTypes.arrayOf(PropTypes.string).isRequired, + isExactMatch: PropTypes.bool.isRequired, + selectedResult: PropTypes.string, }; Results.defaultProps = { @@ -33,6 +28,7 @@ Results.defaultProps = { const mapStateToProps = state => ({ results: state.search.results, + isExactMatch: state.search.isExactMatch, selectedResult: state.search.selectedResult, }); diff --git a/src/client/react/components/container/Search.jsx b/src/client/react/components/container/Search.jsx index 50917dd..babe0c4 100644 --- a/src/client/react/components/container/Search.jsx +++ b/src/client/react/components/container/Search.jsx @@ -7,14 +7,10 @@ import SearchIcon from 'react-icons/lib/md/search'; import { inputChange, changeSelectedResult } from '../../actions/search'; +import users from '../../users'; import Results from './Results'; import IconFromUserType from '../presentational/IconFromUserType'; -const userShape = { - value: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}; - class Search extends React.Component { constructor(props) { super(props); @@ -73,7 +69,7 @@ class Search extends React.Component { <div className="search__input-wrapper"> <div className="search__icon-wrapper"> <IconFromUserType - userType={isExactMatch ? selectedResult.type : null} + userType={isExactMatch ? users.byId[selectedResult].type : null} defaultIcon={<SearchIcon />} /> </div> @@ -95,7 +91,7 @@ class Search extends React.Component { Search.propTypes = { value: PropTypes.string.isRequired, - selectedResult: PropTypes.shape(userShape), + selectedResult: PropTypes.string, isExactMatch: PropTypes.bool.isRequired, dispatch: PropTypes.func.isRequired, }; diff --git a/src/client/react/components/presentational/Result.jsx b/src/client/react/components/presentational/Result.jsx index 80f65d4..0b9e024 100644 --- a/src/client/react/components/presentational/Result.jsx +++ b/src/client/react/components/presentational/Result.jsx @@ -1,26 +1,24 @@ import React from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import users from '../../users'; import IconFromUserType from './IconFromUserType'; -const Result = ({ user, selected }) => ( +const Result = ({ userId, isSelected }) => ( <div className={classnames('search__result', { - 'search__result--selected': selected, + 'search__result--selected': isSelected, })} > - <div className="search__icon-wrapper"><IconFromUserType userType={user.type} /></div> - <div className="search__result__text">{user.value}</div> + <div className="search__icon-wrapper"><IconFromUserType userType={users.byId[userId].type} /></div> + <div className="search__result__text">{users.byId[userId].value}</div> </div> ); Result.propTypes = { - user: PropTypes.shape({ - value: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - }).isRequired, - selected: PropTypes.bool.isRequired, + userId: PropTypes.string.isRequired, + isSelected: PropTypes.bool.isRequired, }; export default Result; diff --git a/src/client/react/reducers/search.js b/src/client/react/reducers/search.js index 6ef0f4d..cad491b 100644 --- a/src/client/react/reducers/search.js +++ b/src/client/react/reducers/search.js @@ -1,43 +1,42 @@ -/* global USERS */ import fuzzy from 'fuzzy'; +import users from '../users'; const DEFAULT_STATE = { input: '', results: [ - { type: 's', value: '18561' }, + 's/18562', ], selectedResult: null, isExactMatch: false, }; -function getSearchResults(query) { +function getSearchResults(allUsers, query) { if (query.trim() === '') { return []; } - const allResults = fuzzy.filter(query, USERS, { + const allResults = fuzzy.filter(query, allUsers, { extract: user => user.value, }); const firstResults = allResults.splice(0, 4); - const users = firstResults.map(result => result.original); + const userIds = firstResults.map(result => result.original.id); - return users; + return userIds; } const search = (state = DEFAULT_STATE, action) => { switch (action.type) { case 'SEARCH/INPUT_CHANGE': { - let results = getSearchResults(action.typedValue); + const results = getSearchResults(users.allUsers, action.typedValue); let selectedResult = null; let isExactMatch = false; // Is the typed value exactly the same as the first result? Then show the // appropiate icon instead of the generic search icon. - if ((results.length > 0) && (action.typedValue === results[0].value)) { + if ((results.length === 1) && (action.typedValue === users.byId[results[0]].value)) { [selectedResult] = results; isExactMatch = true; - results = results.splice(1); } return { @@ -51,6 +50,9 @@ const search = (state = DEFAULT_STATE, action) => { case 'SEARCH/CHANGE_SELECTED_RESULT': { const { results, isExactMatch } = state; + + if (isExactMatch) return state; + const prevSelectedResult = state.selectedResult; const prevSelectedResultIndex = results.indexOf(prevSelectedResult); let nextSelectedResultIndex = @@ -62,15 +64,11 @@ const search = (state = DEFAULT_STATE, action) => { nextSelectedResultIndex = -1; } - let nextSelectedResult = + const nextSelectedResult = nextSelectedResultIndex === -1 ? null : results[nextSelectedResultIndex]; - if (isExactMatch) { - nextSelectedResult = prevSelectedResult; - } - return { ...state, selectedResult: nextSelectedResult, diff --git a/src/client/react/users.js b/src/client/react/users.js new file mode 100644 index 0000000..01ff093 --- /dev/null +++ b/src/client/react/users.js @@ -0,0 +1,66 @@ +/* global USERS */ + +import { combineReducers, createStore } from 'redux'; + +const getId = ({ type, value }) => `${type}/${value}`; + +const byId = (state = {}, action) => { + switch (action.type) { + case 'USERS/ADD_USER': + return { + ...state, + [action.user.id]: { + ...action.user, + }, + }; + default: + return state; + } +}; + +const allIds = (state = [], action) => { + switch (action.type) { + case 'USERS/ADD_USER': + return [ + ...state, + action.user.id, + ]; + default: + return state; + } +}; + +const allUsers = (state = [], action) => { + switch (action.type) { + case 'USERS/ADD_USER': + return [ + ...state, + { + ...action.user, + }, + ]; + default: + return state; + } +}; + +const store = createStore(combineReducers({ + byId, + allIds, + allUsers, +})); + +USERS.forEach((user) => { + store.dispatch({ + type: 'USERS/ADD_USER', + user: { + type: user.type, + value: user.value, + id: getId(user), + }, + }); +}); + +const users = store.getState(); + +export default users; |