How to memoize React elements rendered recursively when updating state object

I’m rendering elements that reflects the structure of an object, so each value is an input, each key is a label and the render function is recursive. When changing the value of an input I update the object with the new value and re-render. The problem is that each re-render is rendering all the elements and it is noticeable in the UI (in delay). I want to memoize the elements so it re-render only the changed input but I am having trouble doing so since I am using a spread operator in order to force the re-render after updating the object.

Let’s take an object for example:

{
  "id": "12345",
  "details": {
    "name": "jake",
  },
  "attachments": [
    {"file": "spodjgposg.file"},
    {"file": "sdgidfhoew.file"},
  ],
}

And the component to render the elements base on the object:

// all imports

const MemoObjectRendererInput = React.memo(ObjectRendererInput);

type Data = Record<any, any>;

type Entry = string | boolean | unknown[] | Data;

type Props = {
  onDataChange: (data: Data) => void;
  data: Data;
};

export const ObjectRenderer: FC<Props> = props => {
  const {data, onDataChange} = props;

  const onValueChange = (value: string, path: string) => {
    const _data = {...data};
    updateByPath(_data, path, value); // the function to update the object
    onDataChange(_data);
  };

  const render = (entry: Entry, label = '', level = -1, parent = 'root') => {
    if (typeof entry === 'string' || typeof entry === 'boolean') {
      const path = parent.replace('root.', '');
      return (
        <MemoObjectRendererInput
          onValueChange={v => onValueChange(v, path)}
          label={label}
          value={entry}
        />
      );
    }

    if (Array.isArray(entry)) {
      return (
        <div>
          <ObjectRendererLabel />
          {entry.map((item, index) => {
            return render(item, index.toString(), level + 1);
          })}
        </div>
      );
    }

    if (typeof entry === 'object') {
      return (
        <div>
          <ObjectRendererLabel />
          {isExpanded && Object.entries(entry).map(([key, value]) => {
            return render(value, key, level + 1, [parent, key].join('.'));
          })}
        </div>
      );
    }
  };

  return (
    <div>
      {render(data)}
    </div>
  );
};

This is the updateByPath function:

const updateByPath = (object: Record<any, any>, path: string, value: string | boolean) => {
  const _path = path.split('.');

  while (_path.length > 1) {
    const key = _path.shift();
    if (key) object = object[key];
  }

  const key = _path.shift();
  if (key) object[key] = value;
};

I tried memoize ObjectRendererInput also tried to use useCallback on the render function but when changing a value on an input all the elements are re-rendered.

How can I solve this? What is the best implementation to achieve this?