Repository: freeCodeCamp/freeCodeCamp
File: client/src/components/profile/components/portfolio.tsx (TypeScript)

Code before:
  const {
    t,
    portfolio: initialPortfolio = [],
    setIsEditing,
    updatePortfolio
  } = props;
  const [portfolio, setPortfolio] = useState([...initialPortfolio]);
  const [unsavedItemId, setUnsavedItemId] = useState<string | null>(null);
  const [isImageValid, setIsImageValid] = useState<ProfileValidation>({
    state: 'success',
    message: ''
  });
  const validationImageRef = useRef<HTMLImageElement>(new Image());

  const validateImageLoad = async (
    image: string
  ): Promise<ProfileValidation> => {
    return new Promise(resolve => {
      validationImageRef.current.src = encodeURI(image);
      validationImageRef.current.onload = () => {
        resolve({ state: 'success', message: '' });
      };
      validationImageRef.current.onerror = () => {
        resolve({ state: 'error', message: t('validation.url-not-image') });
      };
    });
  };

  const createOnChangeHandler =
    (id: string, key: 'description' | 'image' | 'title' | 'url') =>
    (e: React.ChangeEvent<HTMLInputElement>) => {
      e.preventDefault();
      const userInput = e.target.value.slice();
      setPortfolio(prevPortfolio => {
        const mutablePortfolio = [...prevPortfolio];
        const index = findIndex(prevPortfolio, p => p.id === id);
        mutablePortfolio[index] = {
          ...mutablePortfolio[index],
          [key]: userInput
        };
        if (key === 'image' && userInput) {
          void validateImageLoad(userInput).then(imageValidation => {
            setIsImageValid(imageValidation);
          });
        } else if (key === 'image' && !userInput) {
          setIsImageValid({ state: 'success', message: '' });
        }
        return mutablePortfolio;
      });
    };


Review comment (suggestion):
I'm not convinced a ref is necessary here. I think the way you handled it in https://github.com/freeCodeCamp/freeCodeCamp/pull/58568 is much nicer. You can even reuse the code, if you like, and follow the same pattern here as in that PR.

```ts
void checkIfValidImage(value).then(isValid => {
  // set whatever state you want to here e.g.
  if (isValid) {
    setIsImageValid({ state: 'success', message: '' })
  } else {
    setIsImageValid({ state: 'error', message: t('validation.url-not-image') })
  }
});
```

That way the separation of concerns is really clean. 

Code after:
  const {
    t,
    portfolio: initialPortfolio = [],
    setIsEditing,
    updatePortfolio
  } = props;
  const [portfolio, setPortfolio] = useState(initialPortfolio);
  const [unsavedItemId, setUnsavedItemId] = useState<string | null>(null);
  const [imageValidation, setImageValid] = useState<ProfileValidation>({
    state: 'success',
    message: ''
  });

  const checkIfValidImage = (url: string): Promise<ProfileValidation> => {
    const img = new Image();

    return new Promise(resolve => {
      img.onerror = () =>
        resolve({ state: 'error', message: t('validation.url-not-image') });
      img.onload = () => resolve({ state: 'success', message: '' });
      img.src = url;
    });
  };

  const createOnChangeHandler =
    (id: string, key: 'description' | 'image' | 'title' | 'url') =>
    (e: React.ChangeEvent<HTMLInputElement>) => {
      e.preventDefault();
      const userInput = e.target.value.slice();
      setPortfolio(prevPortfolio => {
        const mutablePortfolio = [...prevPortfolio];
        const index = findIndex(prevPortfolio, p => p.id === id);
        mutablePortfolio[index] = {
          ...mutablePortfolio[index],
          [key]: userInput
        };
        if (key === 'image' && userInput) {
          void checkIfValidImage(userInput).then(imageValidation => {
            setImageValid(imageValidation);
          });
        } else if (key === 'image' && !userInput) {
          setImageValid({ state: 'success', message: '' });
        }
        return mutablePortfolio;
      });
    };

  const updateItem = (
    id: string,
    updatedPortfolio?: PortfolioProjectData[]
  ) => {