Tag: typescript

  • Remember last value in React functional components


    Sometimes it can be very useful to save previous values of props (or state) in a React component.
    Perhaps this is easier to be imagined in a class component, but in a functional component, less so.

    However, we can use the useRef() React hook to achieve that quite efficiently.

    Less talk, more code (typescript)…

    const someFunctionalComponent = (p: {index: number}) => {
      // Keep the last positive value around.
      const lastPositiveIndexRef = React.useRef<number>(Math.max(p.index, 0));
      if (p.index >= 0) {
        lastPositiveIndexRef.current = p.index;
      }
    
      // Here, the ref will always have the last positive input.
      const positiveIndex = p.index >= 0 ? p.index : lastPositiveIndexRef.current;
      return <span>I'm only allowing positive numbers to be shown: {positiveIndex}</span>
    }

    Here’s a working sample (JSFiddle).

    I found the following comment from the React documentation, quite illuminating:
    However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

    Enjoy!

  • Using enum values as strictly typed object keys


    In TypeScript, it’s often useful to define interfaces or complex (structured)
    types whose properties (or keys) may only be values of a previously defined
    enum type.

    Here’s a good example: an object declaring a set of buttons for a modal dialog.

    Instead of this…

    type DialogButtons = {
      yes?: boolean,
      no?: boolean,
      cancel?: boolean
    }
    
    interface IDialog {
      buttons: DialogButtons
    }
    
    const dialog: IDialog = {
      buttons: {
        yes: true,
        no: false
      }
    }
    
    console.log("yes" in dialog.buttons) // true
    console.log(dialog.buttons.yes) // true
    
    console.log("no" in dialog.buttons) // true
    console.log(dialog.buttons.no) // false
    
    console.log("cancel" in dialog.buttons) // false
    console.log(dialog.buttons.cancel) // undefined

    This works, but once we try to use these values in other contexts, the approach becomes difficult to use. What if we want to pass which button was pressed to a callback function?

    It would be cumbersome. Therefore…

    …we can use enum values.

    enum DialogButton {
      YES = "yes",
      NO = "no",
      CANCEL = "cancel"
    }
    
    interface IDialog {
      buttons: { [B in DialogButton]?: boolean },
      callback: (button: DialogButton) => void
    }
    
    const dialog: IDialog = {
      buttons: {
        [DialogButton.YES]: true,
        [DialogButton.NO]: false
      },
      callback(button) {
        console.log(button)
      }
    }
    
    console.log(DialogButton.YES in dialog.buttons) // true
    console.log(DialogButton.NO in dialog.buttons) // true
    console.log(DialogButton.CANCEL in dialog.buttons) // false
    
    console.log(dialog.buttons[DialogButton.YES]) // true
    console.log(dialog.buttons[DialogButton.NO]) // false
    console.log(dialog.buttons[DialogButton.CANCEL]) // undefined
    
    dialog.callback(DialogButton.YES) // yes
    dialog.callback(DialogButton.NO) // no
    dialog.callback(DialogButton.CANCEL) // cancel

    This uses the a TypeScript feature called Mapped Types
    and allows us to play around with the actual button values with a lot more flexibility, for example we could
    define an array of buttons.

    function renderButtons(buttons: [DialogButton]): [HTMLElement] {
      // ...
    }

    Code gist: https://gist.github.com/vpalos/0aab903ef607d6da31229b957d91d888.

    Enjoy!