Search code examples
reactjstypescripttypescript-typings

Is there a way to ensure a member of a type actually exists in an object in TypeScript?


I have an object which will store data. I have a UserPrompt type, nodes and edges objects (from Reactflow library).

I want UserPrompt type's node_id to only accept id's that actually exist in my 'nodes' object already. Is this possible to do in TypeScript?

For example: If there is only a node with id: 5 in my nodes object, UserPrompt will not accept a 3.

Code:

'use client';

import { Edge, Node, NodeProps, useEdges, useNodes } from 'reactflow';
import { Button } from './ui/button';

type UserPrompt = {
  id: number;
  text: string;
  node_id: NodeProps['id'];
};

type SessionObject = {
  nodes: Node<unknown>[];
  edges?: Edge<unknown>[];
  prompts?: UserPrompt[];
};

export default function SaveSessionButton() {
  //TODO: save nodes, edges, user's prompts!
  //TODO: don't save single chat node.
  const nodes = useNodes();
  const edges = useEdges();

  function handleOnClick() {
    const obj: SessionObject = {} as SessionObject;
    obj['nodes'] = nodes;
    obj['edges'] = edges;
    console.log(obj);
  }

  return <Button onClick={handleOnClick}>Save session</Button>;
}


Solution

  • First of all, by default with Typescript you can't do type checks in the runtime. This (or kind of) can be achieved with some 3rd-party libraries, but that is actually not what you need in your case.

    What you need here is a function which will validate the input and allow to proceed or throw an error in case of incorrect input. There are multiple options how to implement it. Let's take a look on the simple example of a method which produces proper objects if node_id is correct:

    const makePrompt = (obj: SessionObject, id: number, text: string, node_id: NodeProps['id']): UserPrompt => {
        const nodes = obj['nodes'];
        const isValid = nodes.some(n => n.id === node_id);
        if (!isValid) { 
            throw new Error('Node does not exist!')
        }
        return { id, text, node_id }
    }
    
    // then somewhere in the code:
    const prompt = makePrompt(session, 123, 'hello', 5);
    

    Also, you don't need to pass the whole session object there and can use just the list of nodes. I've used it in the example to make it more clear.

    Basically, you also may just add the isValid check in the existing code and avoid creating separate function. However, having a dedicated method for validation and object generation is more handy and convenient, especially if there are multiple placed where you need UserPrompt objects.