From 41620ceb096a4c3d94bb83cf9a56077939d89a2c Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Thu, 28 Jun 2018 16:03:46 +0200 Subject: Refactor search --- src/client/react/App.js | 17 ++----- src/client/react/AppRouter.js | 55 ++++++++++++++++++++++ src/client/react/components/container/Search.js | 62 +++++++++---------------- src/client/react/store/actions.js | 2 +- src/client/react/store/reducers.js | 17 +++---- 5 files changed, 90 insertions(+), 63 deletions(-) create mode 100644 src/client/react/AppRouter.js (limited to 'src/client') diff --git a/src/client/react/App.js b/src/client/react/App.js index a5a5cbd..522b746 100644 --- a/src/client/react/App.js +++ b/src/client/react/App.js @@ -1,17 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Router, - Route, - Switch, - Redirect, -} from 'react-router-dom'; - import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; -import Index from './components/page/Index'; -import User from './components/page/User'; +import AppRouter from './AppRouter'; export default class App extends React.Component { static propTypes = { @@ -25,11 +18,7 @@ export default class App extends React.Component { return ( - - - - - + ); diff --git a/src/client/react/AppRouter.js b/src/client/react/AppRouter.js new file mode 100644 index 0000000..ae637f0 --- /dev/null +++ b/src/client/react/AppRouter.js @@ -0,0 +1,55 @@ +import React from 'react'; + +import { + withRouter, + Route, + Switch, + Redirect, +} from 'react-router-dom'; +import { connect } from 'react-redux'; +import { PropTypes } from 'prop-types'; + +import Index from './components/page/Index'; +import User from './components/page/User'; +import { setUser } from './store/actions'; +import { userFromLocation } from './lib/url'; + +class AppRouter extends React.Component { + static propTypes = { + user: PropTypes.string, + resetUserState: PropTypes.func.isRequired, + } + + static defaultProps = { + user: null, + } + + componentDidMount() { + const { user, resetUserState } = this.props; + + resetUserState(user); + } + + render() { + return ( + + + + + + ); + } +} + +const mapStateToProps = (state, { location }) => { + return { + key: location.pathname, + user: userFromLocation(location), + }; +}; + +const mapDispatchToProps = dispatch => ({ + resetUserState: user => dispatch(setUser(user)), +}); + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AppRouter)); diff --git a/src/client/react/components/container/Search.js b/src/client/react/components/container/Search.js index 865a0bc..3e599e7 100644 --- a/src/client/react/components/container/Search.js +++ b/src/client/react/components/container/Search.js @@ -26,7 +26,8 @@ import { withRouter } from 'react-router-dom'; import SearchIcon from 'react-icons/lib/md/search'; -import { makeSetUser, userFromMatch } from '../../lib/url'; +import { userFromMatch } from '../../lib/url'; +import { setUser as setUserAction } from '../../store/actions'; import users from '../../users'; import Menu from './Menu'; @@ -37,15 +38,12 @@ import './Search.scss'; class Search extends React.Component { static propTypes = { - results: PropTypes.arrayOf(PropTypes.string).isRequired, selectedResult: PropTypes.string, searchText: PropTypes.string.isRequired, - - // react-router match: PropTypes.object.isRequired, setUser: PropTypes.func.isRequired, - // redux - dispatch: PropTypes.func.isRequired, + onInputChange: PropTypes.func.isRequired, + changeSelectedResult: PropTypes.func.isRequired, }; static defaultProps = { @@ -64,22 +62,6 @@ class Search extends React.Component { this.onKeyDown = this.onKeyDown.bind(this); } - componentDidMount() { - const { dispatch, match } = this.props; - const urlUser = userFromMatch(match); - - dispatch({ type: 'SEARCH/SET_USER', user: urlUser }); - } - - componentWillReceiveProps(nextProps) { - const { dispatch, match } = this.props; - - if (nextProps.match !== match) { - const urlUser = userFromMatch(nextProps.match); - dispatch({ type: 'SEARCH/SET_USER', user: urlUser }); - } - } - onFocus() { this.setState({ hasFocus: true, @@ -95,40 +77,33 @@ class Search extends React.Component { onKeyDown(event) { const { selectedResult, - results, match, setUser, - dispatch, + changeSelectedResult, } = this.props; const urlUser = userFromMatch(match); - const result = selectedResult || results[0]; switch (event.key) { case 'ArrowUp': event.preventDefault(); - dispatch({ type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: -1 }); + changeSelectedResult(-1); break; case 'ArrowDown': event.preventDefault(); - dispatch({ type: 'SEARCH/CHANGE_SELECTED_RESULT', relativeChange: +1 }); + changeSelectedResult(+1); break; case 'Escape': event.preventDefault(); - dispatch({ type: 'SEARCH/SET_USER', user: urlUser }); + setUser(urlUser); break; case 'Enter': event.preventDefault(); - if (result === urlUser) { - // EDGE CASE: The user is set if the user changes, but it doesn't - // change if the result is already the one we are viewing. - // Therefor, we need to dispatch the SET_USER command manually. - dispatch({ type: 'SEARCH/SET_USER', user: urlUser }); - } else if (result) { - setUser(result); + if (selectedResult) { + setUser(selectedResult); } break; @@ -141,7 +116,7 @@ class Search extends React.Component { const { searchText, match, - dispatch, + onInputChange, } = this.props; const { @@ -166,7 +141,7 @@ class Search extends React.Component { dispatch({ type: 'SEARCH/INPUT_CHANGE', searchText: event.target.value })} + onChange={event => onInputChange(event.target.value)} onKeyDown={this.onKeyDown} value={searchText} placeholder="Zoeken" @@ -189,9 +164,16 @@ const mapStateToProps = state => ({ selectedResult: state.search.selected, }); -const mapDispatchToProps = (dispatch, { history }) => ({ - setUser: makeSetUser(history), - dispatch, +const mapDispatchToProps = dispatch => ({ + setUser: user => dispatch(setUserAction(user)), + onInputChange: searchText => dispatch({ + type: 'SEARCH/INPUT_CHANGE', + searchText, + }), + changeSelectedResult: relativeChange => dispatch({ + type: 'SEARCH/CHANGE_SELECTED_RESULT', + relativeChange, + }), }); export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Search)); diff --git a/src/client/react/store/actions.js b/src/client/react/store/actions.js index 46a38ab..e6aa8f6 100644 --- a/src/client/react/store/actions.js +++ b/src/client/react/store/actions.js @@ -13,7 +13,7 @@ export function setUser(newUser) { // Therefor, we need to dispatch the SET_USER command manually. dispatch({ type: 'SEARCH/SET_USER', user }); } else { - updatePathname(newUser); + updatePathname(newUser || ''); } }; } diff --git a/src/client/react/store/reducers.js b/src/client/react/store/reducers.js index c2ee7e9..3425de0 100644 --- a/src/client/react/store/reducers.js +++ b/src/client/react/store/reducers.js @@ -74,30 +74,31 @@ function reducer(state = DEFAULT_STATE, action) { }; } - case 'SEARCH/INPUT_CHANGE': + case 'SEARCH/INPUT_CHANGE': { + const results = getSearchResults(action.searchText); + return { ...state, search: { - results: getSearchResults(action.searchText), + results, text: action.searchText, - selected: null, + selected: results.length > 0 ? results[0] : null, }, }; + } case 'SEARCH/CHANGE_SELECTED_RESULT': { const prevSelectedResult = state.search.selected; const prevSelectedResultIndex = state.search.results.indexOf(prevSelectedResult); let nextSelectedResultIndex = prevSelectedResultIndex + action.relativeChange; - if (nextSelectedResultIndex < -1) { + if (nextSelectedResultIndex < 0) { nextSelectedResultIndex = state.search.results.length - 1; } else if (nextSelectedResultIndex > state.search.results.length - 1) { - nextSelectedResultIndex = -1; + nextSelectedResultIndex = 0; } - const nextSelectedResult = nextSelectedResultIndex === -1 - ? null - : state.search.results[nextSelectedResultIndex]; + const nextSelectedResult = state.search.results[nextSelectedResultIndex]; return { ...state, -- cgit v1.1