Search code examples
javascriptreactjs

How to call methods through references in React19? I can't pass methods by reference like it's done in the book


I'm reading a book about React, but it's out of date and it's from React16. The book is Progressive Web Apps. I managed to update most of the things to React19, but I can't pass methods by reference like it's done in the book. From what I've seen, React removed 'refs' and is using useRef or createRef, but I'm not able to do it according to the book.

This is the reference by refs through react16

import React from 'react';
import './App.css';
import Header from './components/Header/Header.tsx';
import NovoUsuario from './components/NovoUsuário/NovoUsuario.tsx';
import Toast from './components/Toast/Toast.tsx'

function App() {
  return (
    <div>
      <Header />
      <NovoUsuario erro={msg => this.refs.toast.erro(msg)} />
      <Toast />
    </div>
  );
}

export default App;

pass method by reference = this.refs.toast.erro(msg) i pass ref in toast but in this is undefined, refs is not found. from what i saw refs was deprecated and replaced by react hooks after 16 onwards like useRef but I couldn't use useRef Context and others

The erro() component comes from the NovoUsuario class which is inside the validate(e) function.

import React from 'react'
import Label from '../Label/Label.tsx'
import Input from '../Input/Input.tsx'
import GenderSelector from '../GenderSelector/GenderSelector.tsx'
import Usuario from '../../models/Usuario.ts'
import Button from '../Button/Button.tsx'

class NovoUsuario extends React.Component <any, any> {
    constructor(props){
        super(props)
    
        this.state = {
            usuario: new Usuario(),
            validacao: {
                nomeInvalido: false,
                generoInvalido: false
            },
            primeiraVisaoCompleta: false
        }
    }

    validar(e) {
        e.preventDefault()
        let usuario = this.state.usuario
        let validacao = this.state.validacao
        validacao.nomeInvalido = ! usuario.validarNome()
        validacao.generoInvalido = ! usuario.validarGenero()
    
        let mensagem = ''
        let primeiraVisaoCompleta = false
        if(validacao.nomeInvalido && validacao.generoInvalido) {
            mensagem = 'Os campos nome e gênero estão inválidos!'
        } else if (validacao.nomeInvalido) {
            mensagem = 'Seu nome está inválido!'
        } else if (validacao.generoInvalido) {
            mensagem = 'Selecione seu gênero!'
        } else {
            primeiraVisaoCompleta = true
        }
        if (!primeiraVisaoCompleta) {
            this.props.erro(mensagem)
        }

        this.setState({
            validacao: validacao,
            primeiraVisaoCompleta: primeiraVisaoCompleta
        })
    }

    atualizarNome(e) {
        let usuario = this.state.usuario
        usuario.nome = e.target.value
        this.setState({
            usuario: usuario
        })
    }

    atualizarGenero(e, genero) {
        e.preventDefault()
        let usuario = this.state.usuario
        usuario.genero = genero
        this.setState({
            usuario: usuario
        })
    }

    renderizarNome() {
        return(
            <section>
                <Label
                    for="nome"
                    texto="Quem é você?"
                    valorInvalido={this.state.validacao.nomeInvalido}
                />
                <Input
                    id="nome"
                    placeholder="Digite seu nome"
                    maxLength="40"
                    readOnly={this.state.primeiraVisaoCompleta}
                    valorInvalido={this.state.validacao.nomeInvalido}
                    defaultValue={this.state.usuario.nome}
                    onChange={this.atualizarNome.bind(this)}
                />
            </section>
        )
    }

    renderizarGenero() {
        if (this.state.primeiraVisaoCompleta) {
            return null
        } else {
            return(
                <section>
                    <Label
                        texto="Seu gênero."
                        valorInvalido={this.state.validacao.generoInvalido}
                />
                <GenderSelector
                    valorInvalido={this.state.validacao.generoInvalido}
                    genero={this.state.usuario.genero}
                    atualizarGenero={this.atualizarGenero.bind(this)}
                />
            </section>
        )
    }
}

renderizarBotoes() {
    if (this.state.primeiraVisaoCompleta) {
        return(
            <section>
                <Button
                    texto="Voltar"
                    onClick={e => {
                        e.preventDefault()
                        this.setState({
                            primeiraVisaoCompleta: false
                        })
                    }}
                />
                <Button
                    principal
                    texto="Salvar"
                />
            </section>
            )
        } else {
            return(
                <section>
                    <Button
                        principal
                        texto="Próximo"
                        onClick={this.validar.bind(this)}
                    />
                </section>
            )
        }
    }

    render() {
        return(
            <div className="center">
                <form className="pure-form pure-form-stacked">
                    {this.renderizarNome()}
                    {this.renderizarGenero()}
                    {this.renderizarBotoes()}
                </form>
            </div>
        )
    }
}

export default NovoUsuario

I tried using ref useRef useContext and it didn't work, what would be the easiest way to access the function by reference in another component?

this is the component Toast

import React, { createContext } from "react"
import { ToastContainer, toast } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'

class Toast extends React.Component <any, any> {
    sucesso = (msg) => {
        toast.success(msg)
    }

    info = (msg) => {
        toast.info(msg)
    }

    erro = (msg) => {
        toast.error(msg)
    }

    render() {
        return(
            <ToastContainer
                position="bottom-center"
                autoClose={5000}
                hideProgressBar={true}
                closeOnClick
                pauseOnHover
            />
        )
    }
}

export default Toast

the logic that operates the props goes from NovoUsuario > app > toast where erro, sucesso and info will be responsible for using react-toastify, but the error message is sent by NovoUsuario in the validate(e) method, when I run the application everything works, except if the fields are empty "Cannot find refs in ..."


Solution

  • React function components are "instance-less", so this is simply undefined.

    The App component just needs to create a React ref to pass to the Toast component such that its ref value is available within App to pass down to other components or to be used in callbacks.

    Example:

    function App() {
      // (1) Create ref object
      const toastRef = React.useRef(null);
    
      return (
        <div>
          <Header />
    
          {/* (3) use ref in callback function, access current ref value */}
          <NovoUsuario erro={msg => toastRef.current?.erro(msg)} />
    
          {/* (2) pass ref */}
          <Toast ref={toastRef} />
        </div>
      );
    }