How to access to input errors dynamically

Viewed 759

What I have

I created a function that accept an argument which will be used to access to specific formik error dynamically, this is achieved by using bracket notation instead of dot notation like this:

import {useFormikContext} from 'formik';

function TextField(name: string): JSX.Element {
  const {errors} = useFormikContext();
  console.log(errors[name]); // ts error is thown here
  
  // other function/component code
}

The problem

The code works as expected, I can get the error passing the name(error name) as string, but I'm having problem with ts types, ts is thrown the following error:

errors[name]: TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'FormikErrors<unknown>'. No index signature with a parameter of type 'string' was found on type 'FormikErrors<unknown>'.

What I want

I could see the types implemented by formik but I could not figure out how to implement this function that accepts the error name as function argument and use it to access the errors without ts complaining.

Update 1

Current problem: I'm trying to obtain error dynamically with bracket notation. In this case how to use FormData?

import {getIn, useFormikContext} from 'formik';
import React from 'react';
import {Input as UIKInput} from '@ui-kitten/components';

import {InputProps as UIKInputProps} from '@ui-kitten/components/ui/input/input.component';

export type TextFieldProps = UIKInputProps & {
  name: string;
};

export default function TextField(props: TextFieldProps): JSX.Element {
  const {name, ...inputProps} = props;
  const {setFieldTouched, handleChange, errors, touched} = useFormikContext();

  return (
    <UIKInput
      status={errors[name] && touched[name] ? 'danger' : 'basic'}
      caption={
        errors[name] && touched[name] ? errors[name] : ''
      }
      onBlur={() => setFieldTouched(name)}
      onChangeText={handleChange(name)}
      {...inputProps}
    />
  );
}

Update 2

Using getIn to obatin a specific error or touched and avoid type error.

import {getIn, useFormikContext} from 'formik';
import React from 'react';
import {Input as UIKInput} from '@ui-kitten/components';

import {InputProps as UIKInputProps} from '@ui-kitten/components/ui/input/input.component';

export type TextFieldProps = UIKInputProps & {
  name: string;
};

export default function TextField(props: TextFieldProps): JSX.Element {
  const {name, ...inputProps} = props;
  const {setFieldTouched, handleChange, errors, touched} = useFormikContext();

  return (
    <UIKInput
      status={getIn(errors, name) && getIn(touched, name) ? 'danger' : 'basic'}
      caption={
        getIn(errors, name) && getIn(touched, name) ? getIn(errors, name) : ''
      }
      onBlur={() => setFieldTouched(name)}
      onChangeText={handleChange(name)}
      {...inputProps}
    />
  );
}
1 Answers

The error is because name is a string, but the errors object will only have specific strings as keys, not all strings. This means you need to make the type system aware of what keys exist and stick to using just those.

If you mouse over useFormikContext() you should see the following type popup:

useFormikContext<unknown>(): FormikContextType<unknown>

The <unknown> there mean that it's expecting a type that is the shape of the form data. If you provide that, then everything should work as you expect.

Let's say you had form data like this:

interface FormData {
  firstName: string
  lastName: string
}

Now you can define your component props to accept a value that is a key of that form data:

interface Props {
  formKey: keyof FormData
}

Lastly, pass FormData as the type argument to useFormikContext():

const { errors } = useFormikContext<FormData>();
console.log(errors[formKey]); // works fine

This has the added benefit of type safety if you pass in a bad key. A type error will be raised.

// Good
const testUsage = <TextField formKey='firstName' />

// Error: Type '"keyDoesNotExist"' is not assignable to type 'keyof FormData'.
const badUsage = <TextField formKey='keyDoesNotExist' /> 

Working example on Typescript Playground

Related