diff --git a/common/app/App.jsx b/common/app/App.jsx index be56b9687c..b6fcbad01c 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -9,7 +9,9 @@ import { updateNavHeight, updateAppLang, trackEvent, - loadCurrentChallenge + loadCurrentChallenge, + openDropdown, + closeDropdown } from './redux/actions'; import { submitChallenge } from './routes/challenges/redux/actions'; @@ -25,16 +27,20 @@ const mapDispatchToProps = { submitChallenge, updateAppLang, trackEvent, - loadCurrentChallenge + loadCurrentChallenge, + openDropdown, + closeDropdown }; const mapStateToProps = createSelector( userSelector, + state => state.app.isNavDropdownOpen, state => state.app.isSignInAttempted, state => state.app.toast, state => state.challengesApp.toast, ( { user: { username, points, picture } }, + isNavDropdownOpen, isSignInAttempted, toast, ) => ({ @@ -42,6 +48,7 @@ const mapStateToProps = createSelector( points, picture, toast, + isNavDropdownOpen, showLoading: !isSignInAttempted, isSignedIn: !!username }) @@ -62,7 +69,10 @@ const propTypes = { params: PropTypes.object, updateAppLang: PropTypes.func.isRequired, trackEvent: PropTypes.func.isRequired, - loadCurrentChallenge: PropTypes.func.isRequired + loadCurrentChallenge: PropTypes.func.isRequired, + openDropdown: PropTypes.func.isRequired, + closeDropdown: PropTypes.func.isRequired, + isNavDropdownOpen: PropTypes.bool }; // export plain class for testing @@ -102,7 +112,10 @@ export class FreeCodeCamp extends React.Component { picture, updateNavHeight, trackEvent, - loadCurrentChallenge + loadCurrentChallenge, + openDropdown, + closeDropdown, + isNavDropdownOpen } = this.props; const navProps = { username, @@ -110,7 +123,10 @@ export class FreeCodeCamp extends React.Component { picture, updateNavHeight, trackEvent, - loadCurrentChallenge + loadCurrentChallenge, + openDropdown, + closeDropdown, + isNavDropdownOpen }; return ( diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx index 9a953c00a6..2894f1762d 100644 --- a/common/app/components/Nav/Nav.jsx +++ b/common/app/components/Nav/Nav.jsx @@ -31,14 +31,17 @@ function handleNavLinkEvent(content) { } const propTypes = { - points: PropTypes.number, + closeDropdown: PropTypes.func.isRequired, + isNavDropdownOpen: PropTypes.bool, + loadCurrentChallenge: PropTypes.func.isRequired, + openDropdown: PropTypes.func.isRequired, picture: PropTypes.string, - signedIn: PropTypes.bool, - username: PropTypes.string, - updateNavHeight: PropTypes.func, + points: PropTypes.number, showLoading: PropTypes.bool, + signedIn: PropTypes.bool, trackEvent: PropTypes.func.isRequired, - loadCurrentChallenge: PropTypes.func.isRequired + updateNavHeight: PropTypes.func, + username: PropTypes.string }; export default class FCCNav extends React.Component { @@ -80,12 +83,23 @@ export default class FCCNav extends React.Component { renderLink(isNavItem, { isReact, isDropdown, content, link, links, target }) { const Component = isNavItem ? NavItem : MenuItem; + const { + isNavDropdownOpen, + openDropdown, + closeDropdown + } = this.props; + if (isDropdown) { return ( { links.map(this.renderLink.bind(this, false)) } @@ -150,17 +164,6 @@ export default class FCCNav extends React.Component { picture, showLoading } = this.props; - let navLinksCache; - - if (this._navLinksCache) { - navLinksCache = this._navLinksCache; - } else { - // we cache the rendered static links on the instance - // these do not change for the lifetime of the app - navLinksCache = this._navLinksCache = navLinks.map( - this.renderLink.bind(this, true) - ); - } return ( - { navLinksCache } + { + navLinks.map( + this.renderLink.bind(this, true) + ) + } { this.renderSignIn(username, points, picture, showLoading) } @@ -197,5 +204,5 @@ export default class FCCNav extends React.Component { } } -FCCNav.displayName = 'Nav'; +FCCNav.displayName = 'FCCNav'; FCCNav.propTypes = propTypes; diff --git a/common/app/redux/actions.js b/common/app/redux/actions.js index 201b7e3abd..c34b50206a 100644 --- a/common/app/redux/actions.js +++ b/common/app/redux/actions.js @@ -1,6 +1,7 @@ import { Observable } from 'rx'; import { createAction } from 'redux-actions'; import types from './types'; +import noop from 'lodash/noop'; const throwIfUndefined = () => { throw new TypeError('Argument must not be of type `undefined`'); @@ -146,3 +147,6 @@ export const toggleNightMode = createAction( export const updateTheme = createAction(types.updateTheme); // addThemeToBody(theme: /night|default/) => Action export const addThemeToBody = createAction(types.addThemeToBody); + +export const openDropdown = createAction(types.openDropdown, noop); +export const closeDropdown = createAction(types.closeDropdown, noop); diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js index 31b8e94a40..be6bc7c78b 100644 --- a/common/app/redux/reducer.js +++ b/common/app/redux/reducer.js @@ -52,6 +52,14 @@ export default handleActions( [types.delayedRedirect]: (state, { payload }) => ({ ...state, delayedRedirect: payload + }), + [types.openDropdown]: state => ({ + ...state, + isNavDropdownOpen: true + }), + [types.closeDropdown]: state => ({ + ...state, + isNavDropdownOpen: false }) }, initialState diff --git a/common/app/redux/types.js b/common/app/redux/types.js index 8609060c25..1eb0a70635 100644 --- a/common/app/redux/types.js +++ b/common/app/redux/types.js @@ -1,4 +1,4 @@ -import createTypes from '../utils/create-types'; +import { createTypes } from 'redux-create-types'; export default createTypes([ 'analytics', @@ -33,5 +33,9 @@ export default createTypes([ // night mode 'toggleNightMode', 'updateTheme', - 'addThemeToBody' + 'addThemeToBody', + + // nav + 'openDropdown', + 'closeDropdown' ], 'app'); diff --git a/package.json b/package.json index da51c27e73..93847c8294 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "react-youtube": "^7.0.0", "redux": "^3.0.5", "redux-actions": "^0.13.0", + "redux-create-types": "0.0.1", "redux-epic": "^0.1.1", "redux-form": "^5.2.3", "request": "^2.65.0",