1
\$\begingroup\$

I did a lot of research trying to find a React Native component which could reference its own dimensions in order to scale different elements in its style, but could not find any solid examples. I created a hook which boils a component's dimension down to its simplest, easiest to use components and this is what I cooked up:

import React, { Dispatch, SetStateAction } from "react";
import { LayoutChangeEvent } from "react-native";

/**
 * A function to be invoke when a component's layout changes; passed via the onLayout attribute.
 */
type OnLayoutFunc = (event: LayoutChangeEvent) => void;

/**
 * The dimensions of a component.
 *
 */
export class ComponentDimensions {
  private dims: BasicDimensions;
  private setDims: Dispatch<SetStateAction<BasicDimensions>>;
  private onLayoutFunc: OnLayoutFunc;

  /**
   * Construct a {@link ComponentDimensions} object.
   */
  constructor() {
    const [dims, setDims] = React.useState<BasicDimensions>({ w: 0, h: 0 });
    this.dims = dims;
    this.setDims = setDims;
    this.onLayoutFunc = (event: LayoutChangeEvent): void => {
      const { width, height } = event.nativeEvent.layout;
      this.setDims({ w: width, h: height });
    };
  }

  /**
   * The component's width.
   *
   * @return {number} width in pixels.
   */
  public w(): number {
    return this.dims.w;
  }

  /**
   * The component's height.
   *
   * @return {number} height in pixels.
   */
  public h(): number {
    return this.dims.h;
  }

  /**
   * The function to be passed to a {@link ReactElement ReactElement's} onLayout attribute.
   *
   * @return {OnLayoutFunc} function to be passed as an onLayout attribute.
   */
  public getOnLayoutFunc(): OnLayoutFunc {
    return this.onLayoutFunc;
  }
}

/**
 * React-Native hook to allow a component to reference its own dimensions.
 *
 * @return {ComponentDimensions} the dimensions of the component.
 */
export default function useDimensions(): ComponentDimensions {
  return new ComponentDimensions();
}

/**
 * The layout dimensions of the component.
 */
type BasicDimensions = {
  /**
   * The width of the component in pixels.
   */
  w: number;

  /**
   * The height of the component in pixels.
   */
  h: number;
};

This hook can be used like this:

import React from "react";
import { View } from "react-native";
import { ReactElement } from "react-native/node_modules/@types/react";
import useDimensions, { ComponentDimensions } from "../../hooks/useDimensions";

export default function HookExample(): ReactElement {
  const dims: ComponentDimensions = useDimensions();

  return (
    <View
      onLayout={dims.getOnLayoutFunc()}
      style={{
        width: "100%",
        height: "100%"
      }}
    >
      {/* A red circle. */}
      <View
        style={{
          width: "20%",
          height: dims.w(),
          borderRadius: 150,
          backgroundColor: "red",
        }}
      >
      </View>
      {/* A blue rectangle. */}
      <View
        style={{
          width: "100%",
          height: dims.w() / 4,
          backgroundColor: "blue",
        }}
      >
      </View>
    </View>
  );
}

I'm new to both TypeScript and React-Native, so I'd appreciate some feedback for how I can make this simpler or clean it up. Also, I don't have any intuition for performance in React, so I'd be interested in feedback about that if anyone has any.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

So I think it's natural to reach for a class esp. when coming from an OOP background, but I would encourage you to try and think about if it's necessary in this situation. You are sort of double encapsulating the data by having that hook function and the class.

If we instead just pull the logic into the function itself, it could read like this:

export default function useDimensions(): {
  onLayout: OnLayoutFunc,
  width: number,
  height: number,
 } {
    const [dims, setDims] = React.useState<BasicDimensions>({ w: 0, h: 0 });
    function onLayout(event: LayoutChangeEvent) {
      const { width, height } = event.nativeEvent.layout;
      setDims({ w: width, h: height });
    }

    return {
      onLayout,
      width: dims.w,
      height: dims.h
    }
}

TypeScript is a really great language when you try and think about things in terms of interfaces/types and functions instead of classes. Unless you're really implementing something that deserves to be in a class, and uses more class-only esque features, you can usually achieve far less complex solutions.

In this example we've eliminated half of the public API of your module.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.