Randare forțată pentru o componentă-copil

Salutare,

Încerc să implementez sign-up cu e-mail. Am un ecran ce conține un TextInput; vreau să folosesc acest ecran în mai multe părți din aplicație. Acest TextInputScreen conține o componentă TextInputComponent. Aici, utilizatorul introduce e-mailul său. Dacă e-mailul este invalid, arunc o eroare și încerc să actualizez mesajul de eroare din ecranul meu TextInputScreen.

Problema cu care mă confrunt acum este că mesajul de eroare de la TextInputComponent nu este actualizat atunci când este aruncată o eroare.

Fluxul este acesta:

  1. Utilizatorul apasă pe „Înregistrare cu e-mail” într-un ecran separat -> openEmailScreen este apelat
  2. Utilizatorul introduce un e-mail și apasă pe tastatură butonul „Done” -> inputReceived este apelat
  3. Dacă e-mailul este invalid -> o eroare este aruncată în inputReceived și mesajul de eroare este afișat în TextInputViewComponent

Actualizarea mesajului de eroare la pasul 3 NU funcționează momentan și nu îmi dau seama cum să o fac să funcționeze.

Acesta este EmailConnector:

export default class EmailConnector {
    static keyboardTypes = {
        email: 'email-address',
        default: 'default',
    };

    static openEmailScreen = async navigation => {
        navigation.navigate('TextInputScreen', {
            placeholder: strings.onboarding.email_flow.email_placeholder,
            keyboardType: this.keyboardTypes.email,
            onKeyboardPressed: () => this.inputReceived(),
            errorMessage: 'placeholder message',
        })
    }
    //method called when the "Done" button from the keyboard is pressed
    static inputReceived = () => {
        try {
            const email = new SignUpUserBuilder().setEmail('testexample.com').build();//used to validate the email
        }
        catch(error) {
            console.log(error);
            ****//HERE I need to figure out a way to change props.errorMessage and force TextInputViewComponent to rerender****
            <TextInputViewComponent errorMessage = 'Invalid email'/>;
            const viewComponent = new TextInputViewComponent();
            viewComponent.forceUpdate();
        }
    } 
}

Acesta este TextInputScreen:

class TextInputScreen extends Component {
    render() {
        return (
            <View style={styles.rootView}>
                <TextInputViewComponent 
                    placeholder={this.props.route.params.placeholder}
                    keyboardType={this.props.route.params.keyboardType}
                    onKeyboardPressed={this.props.route.params.onKeyboardPressed}
                    errorMessage={this.props.route.params.errorMessage}
                />
            </View>
        )
    }
}

export default TextInputScreen;

Și acesta este TextInputViewComponent:

class TextInputViewComponent extends Component {
    constructor(props) {
        super(props);
        this.state = {
            shouldRefreshComponent: false
        }
    }

    refreshComponent = () => {
        this.setState({
            shouldRefreshComponent: true
        })
    }

    render() {
        return (
            <View> 
                <TextInput 
                    placeholder={this.props.placeholder}
                    placeholderTextColor={colors.placeholder}
                    keyboardType={this.props.keyboardType}
                    style={styles.textInput}
                    onSubmitEditing= {() => this.props.onKeyboardPressed()}
                />
                <Text 
                style={{fontSize: 18, color: colors.white}}
                ref={Text => { this._textError = Text}}>
                    {this.props.errorMessage}
                    </Text>
            </View>
        )
    }
}

export default TextInputViewComponent;

Din metoda inputReceived, am încercat să apelez forceUpdate și setState pentru TextInputViewComponent, dar în ambele cazuri primesc mesajul: "Can't call forceUpdate/setState on a component that is not yet mounted"

Cum pot actualiza mesajul de eroare din TextInputViewComponent? Mulțumesc!

Sper ca ai un sos bun pentru spaghetele pe care le-ai facut aici!

In primul rand nu ai cum sa pui in catch o componenta, aia trebuie sa fie intr-un render(), in al doilea rand ai amestecat partea de functionalitate cu prezentarea. Tu trebuie din acel catch sa emiti un eveniment sau sa faci un dispatch sau sa apelezi un callback, ceva programatic care modifica starea aplicatiei.

Am înțeles. Îmi poți da un exemplu, te rog? dispatch nu e o soluție pentru mine, din păcate, nu folosesc react-redux.

M-am jucat foarte putin cu react dar la prima vedere pare ca acea componenta din catch nu exista in DOM.
O solutie e sa adaugi in DOM si faci componenta aceea hidden apoi sa ii schimbi vizibilitatea cu ajutorul unui prop ori cu un event atunci cand crezi tu ca e nevoie.

Ai abuzat de react cum n-am văzut de ceva timp. :eyes:

Pur și simplu setezi un state de bază în constructor. După in componenta iei this.state.errorMessage.

Pui this.state.errorMessage && <Componenta> în render.

Când errorMessage e undefined sau null sau gol nu o să se randeze. Când faci setState la errorMessage o să se randeze cu noul text.

Expui cu o funcție setState -ul din părinte la copii in props.

Încearcă să scrii cod declarativ in loc de cod imperativ cu react, poti scrie in 3 rânduri tot ce ai scris mai sus.

Înțeleg ce zici. Problema mea este că eu știu în EmailConnector dacă trebuie să afișez un mesaj de eroare sau nu. Cum transmit din EmailConnector mesajul de eroare în TextInputScreen?

Soluția a fost să expun o metodă în EmailConnector - isValidEmail - și să apelez această metodă in TextInputScreen. În funcție de răspunsul din isValidEmail, fac un setState pe errorMessage în TextInputScreen și-l trimit ca props către TextInputViewComponent.

Nu vad cum este EmailConnector folosit, in care din componente? De ce este o clasa / component?

Pentru că am vrut să separ logica de business (ex. validare email) de UI.

Pe React e util sa te familiarizezi cu conceptul de side-effects.

Poti folosi React hooks?

Modelul din functional programming devine foarte util in situatia ta: f(x) -> y pentru orice x ca input garantezi ca va fi mereu y ca output (side effect free).

// f(x) -> y
// hasEmailError(value) -> result

function hasEmailError(value) {
  if (EMAIL_REGEX_HERE.test(value)) return null

  return "The email entered is invalid"
}

Business logic-ul trebuie spart in functii care returneaza un rezultat (side-effect free).

Apoi le folosesti in componente de UI. View / UI prin natura lui are side effects datorita evenimentelor care se intampla asincron (onClick, setState, navigation.navigate , etc).

class TextInputField extends Component {
  constructor(props) {
    super(props);
    // set initial state
    this.state = { value: "", error: null };
  }

  onChange: (value) => { 
    // validate on user input
    this.onValidate(value) // <- side-effect
    // update state with new value
    this.setState({ value }) // <- side-effect
  }

  onValidate: (value) => {
    const error = hasEmailError(value) // <- no side-effect

    // update state, hasEmailError return null or the error message  
    this.setState({ error }) // <- side-effect (sets state & triggers re-render)
  }

  render() {
    return (
       <View>
         <Input value={this.state.value} onChange={this.onChange} />

        {!this.state.error ? null : (
          <Text>{this.state.error}</Text>
        )}
       </View>
    )
  }

E util sa prefixezi toate functiile care au side-effect cu “on”:

  • “on event, do this” -> onEventDoThis
  • “on input change” -> onChange
  • “on change will trigger validation” -> onValidate
    in idee ca ele la randul lor emit alte evenimente.

Are sens?

4 Likes

Da, are sens. Mersi mult, o să țin cont. :slight_smile: