• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Property 'validate' does not exist TypeScript

Cloud VPS
0 głosów
844 wizyt
pytanie zadane 10 lipca 2020 w JavaScript przez poldeeek Mądrala (5,980 p.)

Napisałem 3 stopniowy formularz w react-final-form. Przy próbie dodania walidacji, wyrzuca mi błąd :
 

Property 'validate' does not exist on type 'string | number | {} | ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<...>)> | ReactNodeArray | ReactPortal'.
  Property 'validate' does not exist on type 'string'.ts(2339)

Dopiero zaczynam się uczyć TypeScript i nie mogę dojść, gdzie jest błąd w kodzie. Formularz jest podzielony na 3 części - WizardPage (komponent funkcyjny wyświetlający children), dla którego stworzyłem typ, ponieważ w momencie tworzenia przekazuje w propsach funkcję validate, która sprawdza mi podane dane. Błąd wyrzuca przy próbie połączenia tego z głównym formularzem ( linia 52-53 ) i nie potrafię dojść o co chodzi... 

wizard.tsx

import React, { useState } from "react";
import { Form } from "react-final-form";
import styles from "./register.module.scss";
import { Values } from "./register";

type Wizard = {
  onSubmit: (values: Values) => void;
};

type WizardPage = {
  validate: (
    values: Values
  ) => {
    error1: String;
    error2: String;
    error3?: String;
    error4?: String;
  };
};

export const WizardPage: React.FC<WizardPage> = ({ validate, children }) => (
  <div className={styles.wizard}>{children}</div>
);

// 3-steps form
const Wizard: React.FC<Wizard> = ({ onSubmit, children }) => {
  const [page, setPage] = useState(0);
  const [values, setValues] = useState<Values | undefined>(undefined);
  const activePage = React.Children.toArray(children)[page];
  const isLastPage = page === React.Children.count(children) - 1;

  // next page
  const next = (values: Values) => {
    setPage(Math.min(page + 1, React.Children.count(children)));
    setValues(values);
  };

  // previous page
  const previous = () => {
    setPage(Math.max(page - 1, 0));
  };

  const handleSubmit = (values: Values) => {
    const isLastPage = page === React.Children.count(children) - 1;
    if (isLastPage) {
      return onSubmit(values);
    } else {
      next(values);
    }
  };

  const validate = (values: Values) => {
    return activePage.validate(values);
  };

  return (
    <Form onSubmit={handleSubmit} validate={validate}>
      {({ handleSubmit, submitting, values }) => {
        return (
          <form className={styles.moblieForm} onSubmit={handleSubmit}>
            {activePage}
            <div
              className={styles.buttons}
              style={page === 0 ? { justifyContent: "flex-end" } : {}}
            >
              {page > 0 && (
                <button type="button" onClick={previous}>
                  Powrót
                </button>
              )}
              {!isLastPage && <button type="submit">Dalej</button>}
              {isLastPage && (
                <button type="submit" disabled={submitting}>
                  Zakończ
                </button>
              )}
            </div>
          </form>
        );
      }}
    </Form>
  );
};

export default Wizard;

register.tsx
 

import React from "react";
import { Field } from "react-final-form";
import Wizard, { WizardPage } from "./wizard";
import TextInput from "../../../../components/form/textInput";

import styles from "./register.module.scss";

export type Values = {
  name: String;
  surname: String;
  email: String;
  password: String;
  city: String;
  birthDay: Number;
  birthMonth: Number;
  birthYear: Number;
};

const Register: React.FC = () => {
  const onSubmit = (values: Values) => {
    console.log(values);
  };

  return (
    <Wizard onSubmit={onSubmit}>
      <WizardPage
        validate={(values: Values) => {
          const errors = {
            error1: "",
            error2: "",
          };
          if (!values.name) {
            errors.error1 = "Required";
          }
          if (!values.surname) {
            errors.error2 = "Required";
          }
          return errors;
        }}
      >
        <Field<string>
          type="text"
          name="name"
          component={TextInput}
          placeholder="Imię"
        />
        <Field<string>
          type="text"
          name="surname"
          component={TextInput}
          placeholder="Nazwisko"
        />
      </WizardPage>
      <WizardPage
        validate={(values: Values) => {
          const errors = {
            error1: "",
            error2: "",
          };
          if (!values.email) {
            errors.error1 = "Required";
          }
          if (!values.password) {
            errors.error2 = "Required";
          }
          return errors;
        }}
      >
        <Field<string>
          type="email"
          name="email"
          component={TextInput}
          placeholder="E-mail"
        />
        <Field<string>
          type="password"
          name="password"
          component={TextInput}
          placeholder="Hasło"
        />
      </WizardPage>
      <WizardPage
        validate={(values: Values) => {
          const errors = {
            error1: "",
            error2: "",
            error3: "",
            error4: "",
          };
          if (!values.city) {
            errors.error1 = "Required";
          }
          if (!values.birthDay) {
            errors.error2 = "Required";
          }
          if (!values.birthMonth) {
            errors.error3 = "Required";
          }
          if (!values.birthYear) {
            errors.error4 = "Required";
          }
          return errors;
        }}
      >
        <Field<string>
          type="text"
          name="city"
          component={TextInput}
          placeholder="Miasto"
        />
        <label>Data urodzenia:</label>
        <div className={styles.birthdayInputs}>
          <Field<string>
            type="text"
            name="birthDay"
            component={TextInput}
            placeholder="Dzień"
            style={{ marginRight: "1rem" }}
          />
          <Field<string>
            type="text"
            name="birthMonth"
            component={TextInput}
            placeholder="Miesiąc"
            style={{ marginRight: "1rem" }}
          />
          <Field<string>
            type="text"
            name="birthYear"
            component={TextInput}
            placeholder="Rok"
          />
        </div>
      </WizardPage>
    </Wizard>
  );
};

export default Register;

 

komentarz 10 lipca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Błąd rzuca tutaj (53 linia)?

return activePage.validate(values);

Jakiego typu jest activePage?

Jeśli dobrze patrzę w typingsach react-final-form, to metoda validate występuje na typie UseFieldConfig<FieldValue>, więc może powinieneś zrzutować activePage na ten typ?

komentarz 10 lipca 2020 przez poldeeek Mądrala (5,980 p.)

Dokładnie podkreśla się '.validate'. Nie za bardzo wiem jak zrzutować activePage...

Jeśli dobrze rozumiem

  const activePage = React.Children.toArray(children)[page];

activePage, w takim przypadku będzie typem jaki przekażę w children, a tam są 3 razy <WizardPage>, który zdefiniowałem tak:
 

type WizardPage = {
  validate: (
    values: Values
  ) => {
    error1: String;
    error2: String;
    error3?: String;
    error4?: String;
  };
};

export const WizardPage: React.FC<WizardPage> = ({ validate, children }) => (
  <div className={styles.wizard}>{children}</div>
);

Rozumiem, że chodzi Ci o to abym zdefiniował activePage w momencie jego tworzenia, jednak nie wiem szczerze mówiąc jak to zrobić. Próbowałem po prostu zrobić to tak:
 

  const activePage: UseFieldConfig<FieldValue> = React.Children.toArray(
    children
  )[page];

jednak wtedy dostaję taki błąd :
 

Cannot find name 'UseFieldConfig'.ts(2304)

 

komentarz 10 lipca 2020 przez ScriptyChris Mędrzec (190,190 p.)

A pokaż jeszcze (łącznie z prototypem) co jest w środku tego activePage.

komentarz 10 lipca 2020 przez poldeeek Mądrala (5,980 p.)

Mam 2 komponenty: Wizard i WizardPage.
WizardPage tworzy się w taki sposób:

export const WizardPage: React.FC<WizardPage> = ({ validate, children }) => (
  <div className={styles.wizard}>{children}</div>
);

I działa to w ten spsób, że tworząc Formularz piszę:

<Wizard onSubmit={onSubmit}>
      <WizardPage
        validate={(values: Values) => {
          const errors = {
            error1: "",
            error2: "",
          };
          if (!values.name) {
            errors.error1 = "Required";
          }
          if (!values.surname) {
            errors.error2 = "Required";
          }
          return errors;
        }}
      >
        <Field<string>
          type="text"
          name="name"
          component={TextInput}
          placeholder="Imię"
        />
        <Field<string>
          type="text"
          name="surname"
          component={TextInput}
          placeholder="Nazwisko"
        />
      </WizardPage>
</Wizard>

Czyli przy tworzeniu WizardPage dostaję do niego funkcję validate i children, gdzie children to jest po prostu kod zawarty między <WizardPage></WizardPage>.
Takich <WizardPage></WizardPage> w <Wizard></Wizard>, mam 3 i jako, że one są wewnątrz <Wiazrd> są one traktowane jako jego dzieci. Dzięki czemu dostaję się do nich przez 
 

  const activePage = React.Children.toArray(children)[page];

, ponieważ z tego co rozumiem są one trakowane jako tablica jeśli jest ich kilka, a page w tym przypadku oznacza numer strony formularza, na której obecnie jesteśmy.

 

I do tej pory wszystko działa. Problem jest teraz kiedy dodałem funkcję validate w <WizardPage></WizaradPage>.
Jako, że ten formularz jako ogólna całość znajduje się w <Wizard>, to tutaj muszę zdefiniować funkcję validate, która wywoła funkcję o takiej samej nazwie w tym przypadku:
 

  const validate = (values: Values) => {
    return activePage.validate(values);
  };

activePage - powinno być stroną która jest akutalnie, a validate jest to funkcja przekazana w propsach tutaj:
 

<WizardPage
        validate={(values: Values) => {
          const errors = {
            error1: "",
            error2: "",
          };
          if (!values.email) {
            errors.error1 = "Required";
          }
          if (!values.password) {
            errors.error2 = "Required";
          }
          return errors;
        }}
      >
       [...]
      </WizardPage>

Wytłumaczyłem to najprościej jak potrafię, a przynajmniej jak ja to rozumiem...
I właśnie błąd pojawia się przy próbie dostania do funkcji validate, która znajduje się w <WizardPage>

komentarz 10 lipca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Póki co wycisz błędy TSa dla tej problematycznej linijki. Czy ten kod się wykonuje w przeglądarce, czy JS rzuca błędem?

Możesz pokazać wynik z console.log('activePage: ', activePage) umieszczonego pod stworzeniem tej zmiennej?

komentarz 10 lipca 2020 przez poldeeek Mądrala (5,980 p.)

Po samym wyciszeniu typescript'u dostaje błąd, który mówi, że validate to nie jest funkcja.
 

TypeError: activePage.validate is not a function


i wypisanie w konsoli activePage:

komentarz 10 lipca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Wychodzi na to, że metoda validate nie jest bezpośrednio w obiekcie activePage, tylko w activePage.props.validate - więc coś chyba źle rozpakowałeś/przekazałeś. Albo po prostu wywołaj ta metodę przez activePage.props.validate (o ile tak można wołać metody komponentów w React).

Błąd typowania powinno się dać naprawić przez modyfikację type WizardPage:

type WizardPage = {
  props: { // dodanie obiektu props
    validate: (
      values: Values
    ) => {
      error1: String;
      error2: String;
      error3?: String;
      error4?: String;
    }
  }
};

Ale nie wiem, czy to prawidłowy zapis dla typowania propsów w React.

komentarz 10 lipca 2020 przez poldeeek Mądrala (5,980 p.)

Tworzenie komponentu z props'ami wygląda tak :
 

export const WizardPage: React.FC<WizardPage> = (props) => (
  <div className={styles.wizard}>{prop.schildren}</div>
);

Ja używając zapisu :
 

export const WizardPage: React.FC<WizardPage> = ({ validate, children }) => (
  <div className={styles.wizard}>{children}</div>
);

Robię destrukturzację propsów na validate i children.

Ale nawet jeśli mimo wszystko próbuję się odwołać przez activePage.props.validate, dostaję `Property 'props' does not exist on type`.

komentarz 10 lipca 2020 przez poldeeek Mądrala (5,980 p.)
Nie wiem, zgłupiałem już przez tego TypeScript. Ale czysto teoretycznie w zwykłym JavaScript powinienem móc się odnieść do tej funkcji, skoro widnieje ona nawet w konsoli jako element activePage i do tego jak byk jest pokazane, że jest to funkcja ?
komentarz 10 lipca 2020 przez ScriptyChris Mędrzec (190,190 p.)

Jeśli przy "wyciszonym" TS działa wywołanie activePage.props.validate, to pobaw się rzutowaniem typów przed wywołaniem tej metody, albo daj odpowiedni typ zmiennej activePage w momencie jej tworzenia.

Zaloguj lub zarejestruj się, aby odpowiedzieć na to pytanie.

Podobne pytania

0 głosów
1 odpowiedź 1,947 wizyt
pytanie zadane 3 września 2020 w JavaScript przez poldeeek Mądrala (5,980 p.)
+1 głos
0 odpowiedzi 1,430 wizyt
+1 głos
2 odpowiedzi 610 wizyt

93,483 zapytań

142,417 odpowiedzi

322,763 komentarzy

62,895 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Kursy INF.02 i INF.03
...