Skip to content

Instantly share code, notes, and snippets.

@shadyvb
Last active August 2, 2019 09:21
Show Gist options
  • Select an option

  • Save shadyvb/c6c2b892377c87dde74816fa48e4dbd0 to your computer and use it in GitHub Desktop.

Select an option

Save shadyvb/c6c2b892377c87dde74816fa48e4dbd0 to your computer and use it in GitHub Desktop.

Revisions

  1. shadyvb revised this gist Aug 2, 2019. 1 changed file with 37 additions and 28 deletions.
    65 changes: 37 additions & 28 deletions reusable-effects.diff
    Original file line number Diff line number Diff line change
    @@ -1,33 +1,36 @@
    diff --git a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js
    index fcbe02b7..48310c2c 100644
    index fcbe02b7..bf12822a 100644
    --- a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js
    +++ b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js
    @@ -1,6 +1,7 @@
    -import PropTypes from 'prop-types';
    -import React from 'react';
    -import { __ } from '@wordpress/i18n';
    +import PropTypes from 'prop-types';
    +import React, { useEffect, useRef } from 'react';
    +import { __ } from '@wordpress/i18n';
    +import { OnKeyboardActions } from '../effects';
    +import PropTypes from 'prop-types';
    +import React, { useEffect, useRef, useState } from 'react';
    +import { __ } from '@wordpress/i18n';
    +import { onClickedOutside, onKeyboardActions } from '../effects';

    /**
    * Component to render the search form and related UI in the site header.
    @@ -11,6 +12,13 @@ import { __ } from '@wordpress/i18n';
    @@ -11,6 +12,16 @@ import { __ } from '@wordpress/i18n';
    * @constructor
    */
    const HeaderSearch = ( { keyword, onChange } ) => {
    + const nodeRef = useRef();
    + const actions = {
    + 'ArrowUp': () => console.log( 'test up' ),
    + 'ArrowDown': () => console.log( 'test down' ),
    + };
    + useEffect( OnKeyboardActions( nodeRef, actions ), [] );
    + const actions = [
    + [ 'ArrowUp', () => console.log( 'test up' ) ],
    + [ 'ArrowDown', () => console.log( 'test down' ) ],
    + ];
    + useEffect( onKeyboardActions( nodeRef, actions ), [] );
    +
    + const [ isOptionsVisible, setIsOptionsVisible ] = useState( false );
    + useEffect( onClickedOutside( [ nodeRef ], setIsOptionsVisible ), [] );
    +
    return (
    <form
    id="form-search-input-inline"
    @@ -30,6 +38,7 @@ const HeaderSearch = ( { keyword, onChange } ) => {
    @@ -30,6 +41,7 @@ const HeaderSearch = ( { keyword, onChange } ) => {
    placeholder={ __( 'Search', 'siemens' ) }
    required
    type="search"
    @@ -36,31 +39,31 @@ index fcbe02b7..48310c2c 100644
    />
    <button className="header__searchSubmit" aria-label={ __( 'Search', 'siemens' ) } type="submit" />
    diff --git a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js
    index e69de29b..bdf824ba 100644
    index e69de29b..9b5877cb 100644
    --- a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js
    +++ b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js
    @@ -0,0 +1,60 @@
    @@ -0,0 +1,66 @@
    +import { useEffect } from 'react';
    +
    +/**
    + * React effect: Handle clicking outside of an element
    + *
    + * Usage:
    + * `useEffect( effectHideOnBlur( [ ...nodeRefs ], setIsVisible ), [] );`
    + * `useEffect( onClickedOutside( nodeRefs, setIsVisible ), [] );`
    + * Where
    + * - `nodeRefs` are refs for elements that do not steal focus off the popover
    + * - `setIsVisible` is the callback to set the state, coming from `useState`
    + *
    + * @param {Array<Node>} nodes Array of nodes to exclude
    + * @param {Function} setState Callback to execute
    + * @param {Array<React.Ref>} nodeRefs Array of nodes to exclude
    + * @param {Function} setState Callback to execute
    + *
    + * @returns {function(): function(): *}
    + */
    +export const OnClickedOutside = ( nodes, setState ) => {
    +export const onClickedOutside = ( nodeRefs, setState ) => {
    + // Document click handler
    + const onClick = e => {
    + // See if the click target is within one of the passed refs
    + if ( nodes.filter( node => node && node.contains( e.target ) ).length === 0 ) {
    + if ( nodeRefs.filter( ref => ref.current && ref.current.contains( e.target ) ).length === 0 ) {
    + // If not, set the state to false
    + setState( false );
    + }
    @@ -77,26 +80,32 @@ index e69de29b..bdf824ba 100644
    +/**
    + * React effect: Execute callbacks on keyboard keys pressed
    + *
    + * @param {Object<{current:String}>} nodeRef
    + * @param {Array<Object{key:<String|Function>,callback:<Function>}>} actionMap
    + * Usage:
    + * `useEffect( onKeyboardActions( nodeRef, actionMap ), [] );`
    + * Where
    + * - `nodeRef` is a ref for the tracked element
    + * - `actionMap` is an array of objects, each with a `key` and a `callback`
    + *
    + * @param {Object<{current:String}>} nodeRef
    + * @param {Array<Array<String|Function>>} actionMap
    + *
    + * @returns {function(): function(): *}
    + */
    +export const OnKeyboardActions = ( nodeRef, actionMap ) => {
    +export const onKeyboardActions = ( nodeRef, actionMap ) => {
    + /**
    + * Keyboard press event handler
    + * @param {KeyboardEvent} e
    + */
    + const onPress = e => actionMap
    + .filter( action => (
    + ( typeof action.key === 'string' && e.key === action.key )
    + .filter( ( [ key ] ) => (
    + ( typeof key === 'string' && e.key === key )
    + ||
    + ( typeof action.key === 'function' && action.key( e ) )
    + ( typeof key === 'function' && key( e ) )
    + ) )
    + .map( action => action.callback() );
    + .map( ( [ , callback ] ) => callback() );
    +
    + return () => {
    + nodeRef && nodeRef.current.addEventListener( 'keypress', onPress );
    + return () => nodeRef.current && nodeRef.current.removeEventListener( 'keypress', onPress );
    + nodeRef.current && nodeRef.current.addEventListener( 'keydown', onPress );
    + return () => nodeRef.current && nodeRef.current.removeEventListener( 'keydown', onPress );
    + }
    +};
  2. shadyvb created this gist Aug 2, 2019.
    102 changes: 102 additions & 0 deletions reusable-effects.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,102 @@
    diff --git a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js
    index fcbe02b7..48310c2c 100644
    --- a/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js
    +++ b/content/themes/siemens-ingenuity/verbose/js/app/components/header-search.js
    @@ -1,6 +1,7 @@
    -import PropTypes from 'prop-types';
    -import React from 'react';
    -import { __ } from '@wordpress/i18n';
    +import PropTypes from 'prop-types';
    +import React, { useEffect, useRef } from 'react';
    +import { __ } from '@wordpress/i18n';
    +import { OnKeyboardActions } from '../effects';

    /**
    * Component to render the search form and related UI in the site header.
    @@ -11,6 +12,13 @@ import { __ } from '@wordpress/i18n';
    * @constructor
    */
    const HeaderSearch = ( { keyword, onChange } ) => {
    + const nodeRef = useRef();
    + const actions = {
    + 'ArrowUp': () => console.log( 'test up' ),
    + 'ArrowDown': () => console.log( 'test down' ),
    + };
    + useEffect( OnKeyboardActions( nodeRef, actions ), [] );
    +
    return (
    <form
    id="form-search-input-inline"
    @@ -30,6 +38,7 @@ const HeaderSearch = ( { keyword, onChange } ) => {
    placeholder={ __( 'Search', 'siemens' ) }
    required
    type="search"
    + ref={ nodeRef }
    value={ keyword }
    />
    <button className="header__searchSubmit" aria-label={ __( 'Search', 'siemens' ) } type="submit" />
    diff --git a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js
    index e69de29b..bdf824ba 100644
    --- a/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js
    +++ b/content/themes/siemens-ingenuity/verbose/js/app/effects/index.js
    @@ -0,0 +1,60 @@
    +import { useEffect } from 'react';
    +
    +/**
    + * React effect: Handle clicking outside of an element
    + *
    + * Usage:
    + * `useEffect( effectHideOnBlur( [ ...nodeRefs ], setIsVisible ), [] );`
    + * Where
    + * - `nodeRefs` are refs for elements that do not steal focus off the popover
    + * - `setIsVisible` is the callback to set the state, coming from `useState`
    + *
    + * @param {Array<Node>} nodes Array of nodes to exclude
    + * @param {Function} setState Callback to execute
    + *
    + * @returns {function(): function(): *}
    + */
    +export const OnClickedOutside = ( nodes, setState ) => {
    + // Document click handler
    + const onClick = e => {
    + // See if the click target is within one of the passed refs
    + if ( nodes.filter( node => node && node.contains( e.target ) ).length === 0 ) {
    + // If not, set the state to false
    + setState( false );
    + }
    + };
    +
    + // Return a function ( to execute on componentDidMount )
    + // .. that returns a cleaning function ( to execute on componentWillUnmount )
    + return () => {
    + document.addEventListener( 'mousedown', onClick );
    + return () => document.removeEventListener( 'mousedown', onClick );
    + }
    +};
    +
    +/**
    + * React effect: Execute callbacks on keyboard keys pressed
    + *
    + * @param {Object<{current:String}>} nodeRef
    + * @param {Array<Object{key:<String|Function>,callback:<Function>}>} actionMap
    + *
    + * @returns {function(): function(): *}
    + */
    +export const OnKeyboardActions = ( nodeRef, actionMap ) => {
    + /**
    + * Keyboard press event handler
    + * @param {KeyboardEvent} e
    + */
    + const onPress = e => actionMap
    + .filter( action => (
    + ( typeof action.key === 'string' && e.key === action.key )
    + ||
    + ( typeof action.key === 'function' && action.key( e ) )
    + ) )
    + .map( action => action.callback() );
    +
    + return () => {
    + nodeRef && nodeRef.current.addEventListener( 'keypress', onPress );
    + return () => nodeRef.current && nodeRef.current.removeEventListener( 'keypress', onPress );
    + }
    +};