My Code Docs

My Code Docs

  • Docs
  • Projects
  • Components
  • Help

›React

Javascript

  • Promises and Asnyc/Await
  • Local Storage
  • Firebase
  • JS Language Basics
  • JS Array Functions
  • Keyboard and Mouse Input
  • ES6 Cheatsheet
  • ESLint Setup
  • Data Structures
  • Naming Conventions
  • Javascript Resources
  • Javascript Snippets
  • npm Module Creation

Node

  • Node JS Basics
  • Server Config and SSH

NextJS

  • NextJS Basics

JS/React Libs

  • Lodash Library
  • Axios
  • Ramda
  • Moment JS
  • Overmind
  • Redux Forms npm Module
  • React Beautiful DnD
  • Ant Design
  • Zustand State Managment

React

  • React Basics
  • React Hooks
  • React With Classes
  • Reach Router
  • React Router
  • React Components

Redux

  • Redux in React
  • My Redux Pattern
  • Redux with React (First Doc)
  • Simple Redux Package

ReactNative

  • React Native
  • React Navigation V5
  • React Navigation V4

HTMLCSS

  • HTML and CSS Snippets
  • Tailwind CSS

Electron

  • Electron React Boiler
  • Electron Basics
  • Electron Packaging
  • Electron Tooling

React Hooks

  • React Documentation on Hooks
  • Some Useful Hooks

Safe Set

Sometimes you will need to only use a useState setter.

Useful if it is possible that your component will unmount after an async function runs but before it finishes.

useEffect(() => {
  fetch('http://www.splashbase.co/api/v1/images/random?images_only=true')
    .then(response => response.json())
    .then(imageObj => safeSetRandomImage(imageObj.url))
}, [someDependancy]);

const mountedRef = useRef(false);
useEffect(() => {
  mountedRef.current = true;
    return () => (mountedRef.current = false)
  }, [someDependancy]);

const safeSetRandomImage = (...args) => mountedRef.current && setRandomImage(...args)

Handle Dynamic Height Changes in Component

See Analytix Security MainContent.js and SearchFilterBar.js

This is a situation where you have a component like a navbar or searchbar and the elements in it can change their height, based on items selected, etc.

To accomplish this, we will use the useEffect hook to setup a listener for resize events and then another useEffect hook to make sure those changes are reflected in our CSS, here being a styled-component. Oh and don't forget we need some state to store the updated height in, thus we will use useState for this.

The updated height (state) in this case will need to be used by the parent component most likely. It will depend on how you have structured your application. In this example, I have:

<MainContent>
   <SearchBarFilter setSearchBarHeight={setSearchBarHeight} />
  
   <Grid searchBarHeight={searchBarHeight}>{children...}</Grid>
</MainContent>

Grid is a styled-component that will use the height to modify it's CSS

useEffect to add Event Listener

The first useEffect hooks set up the window event listener and is only run on mounting. Also note we are sending a cleanup function.

The event listener will only respond to resizes of your window itself. If you have something like a option box that when filled makes the component height larger, you will need to setup an effect to deal with those changes and update the height when that state changes.

  // Add an event listener that fires every time window is resized
  // Use this so that we can set the 'div' height of SearchFilterBar
  useEffect(() => {
    window.addEventListener('resize', handleResize.current);
    return () => {
      window.removeEventListener('resize', handleResize.current);
    };
  }, []);

handleResize Function

Note that we have a handleResize function that we need to create. I am storing it in a ref, but I don't think that is necessary.

  // Stores height of search bar since it dynamically gets bigger/smaller based on
  // how many companies are being searched for.  Passed up to CSS for appropriate styling
  let handleResize = useRef(() => {
    setSearchBarHeight(document.getElementById('SearchFilterBar').clientHeight);
  });

useEffect To Set The New Height

Whenever the setSearchBarHeight function changes, we run the effect.

useEffect(() => {
  setSearchBarHeight(
   document.getElementById('SearchFilterBar').clientHeight
  );
}, [setSearchBarHeight]);

Style-component using Height

const Grid = styled.div`
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  background: ${baseColors.background};
  margin-top: ${props => `${props.searchBarHeight + 10}px`};
  position: absolute;
  width: 100%;
`;

*Hooks with Context

If you have some state that you will need to be sharing with via context, this seemed like a great way to "package" the state, state setters and context all together.

Main ideas for this came from article by Kent Dodds

The idea is that we have a single file that exports the following:

  • StateProvider - This is the context provider and will be the default export from the file.
  • use...State - This is the hook that will return the state values to you. This keeps your files consuming the context from having to use the useContext hook and also they will not have to import the context
  • use...StateSetters - This is the hook that returns the state setters.

I stored this file in a folder called context with the thought that if you have multiple sets of data that will be shared at different levels of the application, we will have different contexts.

This example is for storing information on viewing/editing a Qlikview Variable, hence all of the naming have Variable in them.

The first piece of the file creates our needed Contexts, one to hold the State, the other to hold the Setters.

Then we build the function that will return our Context Provider.

Note that all our state is created inside this provider function.

// variableStateContext.js

import React, { useState, useContext } from "react";

const VariableStateContext = React.createContext();
const VariableSettersContext = React.createContext();

/**======================================================
 * Provider function
 * This function is also where all the state is created
 */
function VariableStateProvider({ children }) {
  let [viewingId, setViewingIdMain] = useState();
  let [isEditing, setIsEditing] = useState(false);
  let [isDirty, setIsDirty] = useState(false);
  // When setting the viewing ID need to take into account state
  // of being edited
  let setViewingId = newViewingId => {
    if (newViewingId) {
      setIsEditing(false);
    }
    setViewingIdMain(newViewingId);
  };

  return (
    <VariableStateContext.Provider value={{ viewingId, isEditing, isDirty }}>
      <VariableSettersContext.Provider
        value={{ setViewingId, setIsEditing, setIsDirty }}
      >
        {children}
      </VariableSettersContext.Provider>
    </VariableStateContext.Provider>
  );
}

export const useVariableState = () => { ... }
export const useVariableStateSetters = () => { ... }

export default VariableStateProvider;

Now that we have the provider, we will build out our hooks that will get the context for us.

/**======================================================
 * Variable State
 *
 * useVariableState()
 *  Returns and object with the Variable state
 *   { viewingId, isEditing, isDirty }
 *
 * Just a helper hook so that the user doesn't
 * need to import the VariableStateContext and useContext
 */
export const useVariableState = () => {
  const context = useContext(VariableStateContext);
  if (context === undefined) {
    throw new Error(
      "useVariableState must be used within a VariableStateProvider"
    );
  }
  return context;
};

/**======================================================
 * Variable State Setters
 *
 * useVariableStateSetting()
 *  Return an object with the setter functions
 *   { setViewingId, setIsEditing, setIsDirty }
 *
 *
 */
export const variableStateContext = () => {
  const context = useContext(VariableSettersContext);
  if (context === undefined) {
    throw new Error(
      "useVariableStateSetters must be used within a VariableStateProvider"
    );
  }
  return context;
};

Straight forward, read Kent's article for the details.

Lastly, to implement, you will need to first place the provider around the components that need access to the state.

import VariableStateProvider from '../context/variableStateContext';
...
<VariableStateProvider>
  <VariableMain />
</VariableStateProvider>
...

To access the context, just use the hooks!

import { useVariableState, useVariableStateSetters} from '../context/variableStateContext';

function VariableMain() {
  let { viewingId, isEditing, isDirty } = useVariableState;
  let { setViewingId, setIsEditing, setIsDirty } = useVariableStateSetters;
  ...
}

Another Context Option with useReducer

This is another way to use context with the useReducer hook.

It is a function that exports your Context and Provider component. It is a Context factory that you can use to setup as many Contexts needed.

// createDataContext.js
import React, { useReducer } from "react";

export default (reducer, actions, initialState) => {
  const Context = React.createContext();

  const Provider = ({ children }) => {
    const [state, dispatch] = useReducer(reducer, initialState);

    let boundActions = Object.keys(actions).reduce((ba, key) => {
      ba[key] = actions[key](dispatch);
      return ba;
    }, {});
    
    return (
      <Context.Provider value={{ state, ...boundActions }}>
        {children}
      </Context.Provider>
    );
  };

  return { Context, Provider };
};

To use this function you would create your context file. You will need a reducer function, an actions object and initial state.

If you need async actions in your reducer, you will need to update the dispatch function to be able to deal with them.

// BlogContext.js
import createDataContext from "./createDataContext";

const blogReducer = (state, action) => {
  switch (action.type) {
    case "add_blogpost":
      return [...state, { title: `Blog Post #${state.length + 1}` }];
    default:
      return state;
  }
};

const addBlogPost = dispatch => {
  return () => {
    dispatch({ type: "add_blogpost" });
  };
};

export const { Context, Provider } = createDataContext(
  blogReducer,
  { addBlogPost },
  []
);

Lastly, you will need to import your BlogContext into the files that need the data. Also, don't forget to wrap your component tree in the Provider component.

Make Reducer Handle Thunks

I originally coded this to handle multiple reducers, however, I think that having separate contexts for each reducer may be better. Here is the multiple reducer implementation:

/**
 * thunk dispatch
 * @param {array} dispatchArr - Array of dispatch functions returned from useReducer calls
 * @param {any} store - main store that holds all data from above mentioned useReducer calls
 *
 * @returns {function} - returns a function that accepts an action.
 *    The action can be a standard redux style action {type: '', payload: ''}
 *    or a function, i.e. thunk
 *    If it is a function/thunk/async func then we will call it and pass the "fullDispatch" function
 *    along with the store.
 */
function useThunkDispatch(dispatchArr, store) {
  return action => {
    let fullDispatch = action => {
      dispatchArr.forEach(dispatchFunc => {
        dispatchFunc(action);
      });
    };
    if (typeof action === 'function') {
      action(fullDispatch, store);
    } else {
      fullDispatch(action);
    }
  };
}

Here is the implementation without the multiple reducers

/**
 * thunk dispatch
 * @param {function} dispatch -  dispatch function returned from useReducer call
 * @param {any} store - store that holds the data(state)
 *
 * @returns {function} - returns a function that accepts an action.
 *    The action can be a standard redux style action {type: '', payload: ''}
 *    or a function, i.e. thunk
 *    If it is a function/thunk/async func then we will call it and pass the "fullDispatch" function
 *    along with the store.
 */
function useThunkDispatch(dispatch, store) {
  return action => {
    let fullDispatch = action => {
      dispatch(action);
    };
    if (typeof action === 'function') {
      action(fullDispatch, store);
    } else {
      fullDispatch(action);
    }
  };
}

When using the above, you may want to wrap the dispatch in a useRef so that it doesn't get recreated each time.

Search Input Box Hook

Originally implemented as a Render Prop component, this is the hook implementation.

To see the Render Prop implementation, go to Search Input Box Component

The idea with this hooks is that you will pass it the searchArray that will be used to match with typed in input. The hook returns the following:

{
  searchingIsOn, // boolean letting us know if the search function is active
  spreadProps // props that need to be spread on the input control
}

spreadProps is itself an object the contains

spreadProps: {
  ref,
  value,
  onKeyDown,
  onChange
}

Since the updated value is always being returned in spreadProps, you can pull it out early on and use it as needed:

import React from "react";
import useSearchInput from "./useSearchInput";

const authors = [
  { author: "mark" },
  { author: "lori" },
  { author: "Haley" },
  { author: "Hunter" }
];

function App() {
  let [author, setAuthor] = React.useState("");
  let {searchingIsOn, spreadProps} = useSearchInput({
    searchArray: authors.map(author => author.author)
  });
  let { value } = spreadProps;
  
  return (
    <div className="App">
      <input 
      style={searchingIsOn ? { backgroundColor: 'white'} : {backgroundColor: 'yellow'}}
      {...spreadProps} placeholder="Use an Author" />
    </div>
  );

Code for useSearchInput hook

import { useState, useRef, useEffect } from "react";
/**
 * parameters: { searchArray: [] }
 * Uses the passed searchArray and display like google predictive search
 * Escape key press turns off searching, press again, turns back on
 * the "searchingIsOn" value returned from useSearchInput() lets you know if
 * searching is on or off
 *
 * @param {Object} - { searchArray: [] }
 * @example
 *   let {searchingIsOn, spreadProps} = useSearchInput({
 *     searchArray: arrayToSearch
 *   });
 *  // get the value so you can use it after it is input
 *   let { value } = spreadProps;
 *  ....
 *  <input {...spreadProps}
 *    style={searchingIsOn ? { backgroundColor: 'white'} : {backgroundColor: 'yellow'}}
 *    placeholder="Input a Value"
 *  />
 */
const useSearchInput = ({ searchArray }) => {
  const [inputValue, setInputValue] = useState("");
  const [backspace, setBackspace] = useState(false);
  const [escKey, setEscKey] = useState(false);
  const [startPos, setStartPos] = useState(0);
  const [endPos, setEndPos] = useState(0);
  const inputEl = useRef();

  const onKeyDown = e => {
    const keyPressed = e.key;
    //Check keyPressed and set selection
    switch (keyPressed) {
      case "ArrowRight":
        setStartPos(inputEl.current.selectionStart + 1);
        setEndPos(inputEl.current.selectionStart + 1);
        break;
      case "ArrowLeft":
        setStartPos(inputEl.current.selectionStart - 1);
        setEndPos(inputEl.current.selectionStart - 1);
        break;
      case "Backspace":
        if (startPos !== endPos) {
          setStartPos(inputEl.current.selectionStart);
          setEndPos(inputEl.current.selectionStart);
        } else {
          setStartPos(inputEl.current.selectionStart - 1);
          setEndPos(inputEl.current.selectionStart - 1);
        }
        setBackspace(true);
        break;
      case "Delete":
        setStartPos(inputEl.current.selectionStart);
        setEndPos(inputEl.current.selectionStart);
        setBackspace(true);
        break;
      case "Escape":
        setStartPos(inputEl.current.selectionStart);
        setEndPos(inputEl.current.selectionStart);
        setEscKey(!escKey);
        break;
      default:
        break;
    }
  };
  const onInputChange = e => {
    //Get input value
    //CHECK FOR this.state.backspace and if true, set state to target.value passed
    // and set backspace to false
    const inputValue = e.target.value;
    if (backspace || escKey) {
      setInputValue(inputValue);
      setBackspace(false);
      return;
    }
    //Setup match expression
    const matchExpr = inputValue.length > 0 ? "^" + inputValue : /.^/;
    //Create RegExp Object
    const expr = new RegExp(matchExpr, "ig");
    //Try and Find a match in array of service inputValues
    const foundItem = searchArray.find(desc => desc.match(expr));
    // console.log(`foundItem ${foundItem}`);
    //If not found, return inputValue, else return found item and set selection range
    const finalValue = foundItem || inputValue;

    setStartPos(inputValue.length);
    setEndPos(finalValue.length);
    // console.log(`startpos: ${startPos} -- endpos: ${endPos} -- foundItem: ${foundItem}`)
    setInputValue(finalValue);
  };
  useEffect(() => {
    if (startPos !== endPos) {
      inputEl.current.setSelectionRange(startPos, endPos);
    }
  });
  return {
    searchingIsOn: !escKey,
    spreadProps: {
      ref: inputEl,
      value: inputValue,
      onKeyDown: onKeyDown,
      onChange: onInputChange
    }
  };
};

export default useSearchInput;

UseOnKeyPress Hook

const useOnKeyPress = (targetKey, onKeyDown, onKeyUp, isDebugging = false) => {
  const [isKeyDown, setIsKeyDown] = useState(false);
  const onKeyDownLocal = useCallback(e => {
    if (isDebugging)
      console.log(
        "key down",
        e.key,
        e.key != targetKey ? "- isn't triggered" : "- is triggered"
      );
    if (e.key != targetKey) return;
    setIsKeyDown(true);

    if (typeof onKeyDown != "function") return;

    onKeyDown(e);
  });

  const onKeyUpLocal = useCallback(e => {
    if (isDebugging)
      console.log(
        "key up",
        e.key,
        e.key != targetKey ? "- isn't triggered" : "- is triggered"
      );

    if (e.key != targetKey) return;

    setIsKeyDown(false);

    if (typeof onKeyUp != "function") return;
    onKeyUp(e);
  });

  useEffect(() => {
    addEventListener("keydown", onKeyDownLocal);
    addEventListener("keyup", onKeyUpLocal);

    return () => {
      removeEventListener("keydown", onKeyDownLocal);
      removeEventListener("keyup", onKeyUpLocal);
    };
  }, []);

  return isKeyDown;
};

Publish Hook to NPM

The create-react-hook module sets up a project so that you can deploy your react hook.

$ npx create-react-hook
? Package Name: @yournpmusename/useMyNewHook // scopes hook to your user
? Package Description: a description for the hook
? Authors GitHub Handle: markmccoid
? Github Repo Path: markmccoid\useMyNewHookd
...

Add your hook code to the index.js in the src directory. Egghead course

npm version (docs)

You can use the command line tool npm version ... to bump your version. It will also set a git tag for the version.

← React BasicsReact With Classes →
  • Safe Set
  • Handle Dynamic Height Changes in Component
    • useEffect to add Event Listener
    • handleResize Function
    • useEffect To Set The New Height
    • Style-component using Height
  • *Hooks with Context
  • Another Context Option with useReducer
    • Make Reducer Handle Thunks
  • Search Input Box Hook
    • Code for useSearchInput hook
  • UseOnKeyPress Hook
  • Publish Hook to NPM
    • npm version (docs)
My Code Docs
Docs
Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
Community
User ShowcaseStack OverflowProject ChatTwitter
More
BlogGitHubStar
Facebook Open Source
Copyright © 2020 McCoidCo