Hooks; what are they, and why use them

Hooks allow pulling out part of a function component and re-using it anywhere. They can use other hooks as well, so you can build simple, re-usable hooks. Built in hooks such as useState, useMemo, useEffect allow function components to use state, post-mount, and memoizing functionality just like class components. What makes the function component/hooks setup better than the same functionality in class components however is that this code can be pulled out of the function components entirely into hooks and re-used in any other function component.

This contrast with class components

There is no way to pull core state/update functionality out of class components that I am aware of. For me, this gave a significant advantage to using function components and hooks.

I switched over and really liked it better

I can see why some people would be annoyed or not interested in learning hooks. I was not sure about it at first. When I dove into it and rewrote some existing React code I had with hooks however, I did not come back to writing new class components at all. It meshed well with my general approach to programming, which is functional when possible: JS, R, Bash FP, but it also just made for better components. Many of my components now only have minimal code inside, because it is all broken out into hooks.

For more hook examples, check out my libraries

I have several react libraries that make extensive use of hooks, so if you want to see more examples in real use, take a look at those:
Interactive Histogram
Selection Form
Geographic Mapping

I am going to focus on these three built in Hooks

I use these built in hooks most often, so I will focus on them.
useState(),
useEffect(),
useMemo(),

Custom hooks really are what makes hooks worth it

Being able to use any of the built in hooks and any custom hooks you make means the structures you can build with hooks is anything you would otherwise have put fully in a fcn component.

The useState Hook -

useState sets up persistent state variables.

import React, {useState } from 'react';  
// Returns the current value (count) and a setter function (setCount)
const [count, setCount] = useState(0);  

Change in useState variable will trigger renders

When the setter function is used to change the value, any component using the state variable will re-render.
Allowing children to modify state is easy, pass the setter function
If you need a child component to be able to change the state variable, just pass the setter function.

Example building custom hook around useState

I set up custom hooks that useState (or useRef) to track pointer values, but do nothing on their own. They can then be used by a component to allow it to track mouse movement by calling the setter functions they provide.

// Keeps track of whether mouse button is pressed down
const useMouseStatus = () => {
  const [ismousedown, setmousedown] = useState(false)
  return ({ ismousedown, setmousedown })
}

// updates the location of mouse when mousemove called
const useMouseLocation = () => {
  const x = useRef(0)
  const y = useRef(0)
  const mousemove = R.curry((xin, yin) => {
    x.current = xin 
    y.current = yin
  })
  return ({x: x.current, y: y.current, mousemove}) 
}

// Store the last location where mouse button pressed down.
const useMouseDownLocation = () => {
  const [startx, setstartx] = useState(0)
  const [starty, setstarty] = useState(0)
  const mousedown = (x, y) => {
    setstartx( x )
    setstarty( y )
  }
  return ({startx, starty, mousedown})
}

Then use the custom hooks in a component

The hooks break out all the state/tracking functionality, and all the
component needs to do is set up listeners and call the functions from
the hooks at the appropriate times.

const SelectBase = (props) => {
  const { ismousedown, setmousedown} = useMouseStatus()
  const { startx, starty, mousedown} = useMouseDownLocation()
  const { x, y, mousemove } = useMouseLocation()

  return(
   <div  
      onClick={(e) => {
        let [x, y] = getEventXY(e)
        }
      }
      onMouseMove={(e) => {
        if(! ismousedown){ return }
        const [x, y] = getEventXY(e)
        mousemove(x, y)
        }
      }
      onMouseDown={(e) => {
        setmousedown(true)
        const [x, y] = getEventXY(e)
        mousemove(x, y)
        mousedown(x, y)
        }
      }
      onMouseUp={(e) => {
        setmousedown(false) 
        }
      }
   >

   </div>
  )
}

UseMemo() hook returns memoized data -

useMemo() saves function output between renders when the dependency array has not changed.
Takes a callback and dependency array -
The callback determines what value useMemo calculates/stores. The dependency array determines when to call the function to recalculate the value.

Example of a useMemo hook -

This example will recalculate a feature set only when the input topology changes.

const exampleMemo = input => {
  const features = useMemo(() =>
      topojson.feature(input.topology, input.topology.objects[input.topopath]).features, 
   [input.topology]);
}

Can useMemo for rendering elements -

If a render takes a long time, only render when a meaningful content change has occurred. Components should generally do this on their own, but there are instances I have wanted to control it directly.

  return(
   <svg >
      { useMemo(() => {
        return (
	  <GeoMap 
            data={props.data}
            limits={props.limits}
          />)
	 )
      },[props.data, props.limits])}
    </svg>
   )

Putting usememo to use with a custom hook

The example memoizes several pieces of map projection data that require a significant amount of computation. The hook takes care of determining whether to redo the calculations or not based on the inputs, and returns the data either way.

const useGeoMemo = input => {
  const features = useMemo(() =>
      topojson.feature(input.topology, input.topology.objects[input.topopath]).features, 
   [input.topopath]);
  const projection = useMemo(() => input.projection(input.scaling), 
  [ input.scaling ]);
  const geopath = useMemo(() => geoPath(projection), [input.scaling]);
  return { features, projection, geopath };
};

Here is another case meant for creating feature paths to plot. With these split out, the component that need to create geographic coordinate and features just pass all this off to the hook.

const useFeatureMemo = input => {
  const featkey = input.feature.properties[input.featurekey]
  const path = useMemo(() => input.geopath(input.feature), [ featkey ]);
  const bounds = useMemo(() => input.geopath.bounds(input.feature), [ featkey ]);
  return { path, bounds };
};

Pull the custom memoizing hooks into the component

The computations and memoizing the results are handled in the hooks, and the components just take care of what to do with the results.

const GeoSvg = props => {
  const { features, geopath } = useGeoMemo(props);
  const featurekey = props.featurekey ? props.featurekey : "GEO_ID";

  return (
    <svg >
         { features.map(feature => (
           <GeoFeature
            key={`${feature.properties[featurekey]}`}
            feature={feature}
            {...props, geopath}
          />
         ));
        }
    </svg>
  );
};

const GeoFeature = props => {
  const { path, bounds } = useFeatureMemo(props);
  return (
    <path
      d={path}
    />
  );
};

useEffect() runs something after the component mounted

The use of this hook is to run code after a component mounts. It can be used to conduct operations that will take some time as to not delay the initial mounting.

The example is a custom hook for loading data

The specific use here is to load data sets that could be large. The point is that useEffect will execute after the component mounts and return its result whenever it finishes so as not to delay initial mounting. In this case useState is also used to store the result, and just the state value is returned from the custom hook.

const useLoaddata = (dataget, topotype) => {
  const [data, setdata] = useState(undefined);
  useEffect(() => {
    const rawdata = {};
    let datagetter = async () => {
        rawdata[topotype] = await dataget();
        setdata(topojson.topology(rawdata));
      };
    } 
    datagetter();
  }, []);

  return data;
};