How to over-ride onChange prop from another component using cloneElement?

Viewed 20

Currnently, in TextField, I got onChange prop coded like this onChange={(e: React.FormEvent<HTMLInputElement>) => onChangeValue(e.currentTarget.value)}, but I would like to update to onChange={(e: React.FormEvent<HTMLInputElement>) => onChangeValue(e)} in NumberField so I used React.cloneElement to over-ride onChange prop. However, when I check the typeof e from onChangeValue function, I get string type, but I was expecting object since event is synthetic event. In TextField, I get object type for e. So why is in NumberField event is interpreted as string? Am I using cloneElement wrong way? https://codesandbox.io/s/over-ride-prop-type-mjw1sp?file=/src/NumberField.tsx

TextField.tsx

import React from "react";
import "./styles.css";

export interface ITextField {
  onChange?: (value: string) => void;
  value?: string;
}

export const TextField: React.FC<ITextField> = ({ value = "", onChange }) => {
  const onChangeValue = (value: string) => {
    if (typeof onChange === "function") {
      onChange(value);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e: React.FormEvent<HTMLInputElement>) =>
          onChangeValue(e.currentTarget.value)
        }
      />
    </div>
  );
};

NumberField.tsx

import React, { useState } from "react";
import "./styles.css";
import { TextField, ITextField } from "./TextField";

export interface INumberField extends Omit<ITextField, "onChange" | "value"> {
  onChange?: (value: number) => void;
  value: number;
  formatOnType?: boolean;
}

export const NumberField: React.FC<INumberField> = ({ value, onChange }) => {
  const [displayValue, setDisplayValue] = useState("");
  const onChangeValue = (e: React.FormEvent<HTMLInputElement>) => {
    if (typeof onChange === "function") {
      console.log(typeof e); // string, but was expecting object
      onChange(Number(e.currentTarget.value));
    }
  };

  return (
    <>
      {React.cloneElement(<TextField value={displayValue} />, {
        onChange: (e: React.FormEvent<HTMLInputElement>) => onChangeValue(e)
      })}
    </>
  );
};

App.tsx

import React, { useState } from "react";
import { NumberField } from "./NumberField";
import "./styles.css";

export default function App() {
  const [value, setValue] = useState(0);

  return (
    <div>
      <NumberField value={value} onChange={(e) => setValue(e.target.value)} />
    </div>
  );
}
1 Answers

You don't need to clone the component, just create a new for your needs. Here is a quick fix from your codesandbox. App.js will also fail since you expect an object instead of a number.

import React from "react";
import "./styles.css";
import { ITextField } from "./TextField";

export interface INumberField extends Omit<ITextField, "onChange" | "value"> {
  onChange?: (value: number) => void;
  value: number;
}

export const NumberField: React.FC<INumberField> = ({ value, onChange }) => {
  return (
    <TextField
      type="number"
      value={value}
      onChange={(value: string) =>
        onChange && onChange(Number(value))
      }
    />
  );
};

I would also recommend you to try typescript to get some help with type issues in the future. Best of luck

Related