1

I am working on a task to practice react programming, this is the task - Change Username

Here is the explanation:

This application should allow the user to update their username by inputting a custom value and clicking the button.

The Username component is finished and should not be changed, but the App component is missing parts. Finish the App component so that the Username component displays the inputted text when the button is clicked.

The App component should use the React.useRef Hook to pass the input to the Username component for the input element and for the Username component.

For example, if the user inputs a new username of "John Doe" and clicks the button, the div element with id root should look like this:

<div><button>Change Username</button><input type="text"><h1>John Doe</h1></div>

This is the code given:

class Username extends React.Component {
  state = { value: "" };

  changeValue(value) {
    this.setState({ value });
  }

  render() {
    const { value } = this.state;
    return <h1>{value}</h1>;
  }
}

function App() {
  function clickHandler() {}

  return (
    <div>
      <button onClick={clickHandler}>Change Username</button>
      <input type="text" />
      <Username />
    </div>
  );
}

document.body.innerHTML = "<div id='root'></div>";
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

I tried a lot to understand how to solve this, but I am not able to fix this, how to solve this problem?

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
learner
  • 6,062
  • 14
  • 79
  • 139

4 Answers4

6

Just found it out. Seems like you need to use refs for everything. No state or anything allowed! Please note that you should not do it like that in a real world app :)

function App() {
  const ref = React.useRef()
  const inputRef = React.useRef()

  function clickHandler() {
    ref.current.changeValue(inputRef.current.value)
  }


  return (
    <div>
      <button onClick={clickHandler}>Change Username</button>
      <input type="text" ref={inputRef}  />
      <Username ref={ref} />
    </div>
  );
}
Tobias Lins
  • 2,543
  • 19
  • 22
  • 2
    It works but it's **horrendous!** :O Please everyone, never do this in a real-world app. – Emile Bergeron Nov 26 '19 at 19:03
  • 1
    It's an absolute nightmare :D +1 – kind user Nov 26 '19 at 19:04
  • @EmileBergeron, How we should do this in real world, using the shared states I assume, or context – Javascript Coder May 12 '20 at 07:00
  • 1
    @JavascriptCoder in OP's simulated use-case, this answer is valid. But the use-case itself on this skill testing website is an anti-pattern. Using a `ref` to access a child node's instance method is a brittle hack. It assumes the implementation of the child component and wouldn't work if the child was using hooks. Usually, a React component will offer a useful API, like a `value` prop on the `Username` component. – Emile Bergeron May 12 '20 at 15:36
  • I think the description is not totally clear about the requirements. I tried to solve this question in several different ways, all of them works perfectly, but the app don't validate my answer as correct. The only way to achieve the positive result is using ref for the input instead of state and then on the `clickHandler` refer the value of inputValue and use it to update state ref `ref.current.setState({value: inputRef.current.value});` – Joel Apr 04 '22 at 00:22
0

This works

import React from 'react'
import ReactDOM from 'react-dom'

class Username extends React.Component {
  constructor(props){
    super(props);
    this.state = {value: ""};
  }

  changeValue(value) {
    this.setState({value: value});
  }

  render() {
    const value = this.state.value;
    return <h1>{value}</h1>;
  }
}

class App extends React.Component {
    constructor(props) {
        super(props);
        this.userNameRef = React.createRef();
    }

  clickHandler() {
    var name = document.getElementById('name_input').value;
    this.userNameRef.current.setState({value: name});
  }

  render() {
    return (
      <div>
        <button onClick={this.clickHandler.bind(this)}>Change Username</button>
        <input id="name_input" type="text" />
        <Username ref={this.userNameRef} />
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

Make sure you understand the code and don't be like me, forgetting the bind method :-)

JMRC
  • 1,473
  • 1
  • 17
  • 36
0

In the real world you would probably do something like this: -

import React, { useRef } from "react";
import ReactDOM from "react-dom";

class Username extends React.Component {
  state = { value: "" };

  changeValue(value) {
    this.setState({ value });
  }

  render() {
    const { value } = this.state;
    return <h1>{value}</h1>;
  }
}

function App() {
  const myRef = useRef();
  function clickHandler() {
    document.querySelector("h1").innerHTML = myRef.current.value;
  }

  return (
    <div>
      <button onClick={clickHandler}>Change Username</button>
      <input type="text" ref={myRef} />
      <Username />
    </div>
  );
}

document.body.innerHTML = "<div id='root'></div>";
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

document.querySelector("input").value = "John Doe";
document.querySelector("button").click();
setTimeout(() => console.log(document.getElementById("root").innerHTML));
N Djel Okoye
  • 950
  • 12
  • 10
-1

This component structure probably isn't your best bet. Typically you want to have a Class component at the top with functional components on the bottom, and call those functional components within the Class component.

So rendering <button> within App is just making things hard for you. In App you should just be rendering <Username /> and have <Username /> holding your logic:

class Username extends Component {
constructor(props){
this.state = { usernameValue: ''};
this.onInputChange = this.onInputChange.bind(this);
this.changeUsername = this.changeUsername.bind(this);
}

onInputChange(event) {
this.setState({usernameValue: event.target.value});
}

changeUsername() {
//Update username in the DB
db.record = this.state.usernameValue
}

render(){

return (
<div>//Wrapper div
<input onChange={this.onInputChange} value={this.state.usernameValue} />
<button onClick={this.changeUsername}>Change Username</button>
</div>
);

}

}

function App(){
return(
<Username />
);
}

I did this a different way than what you were trying as you were trying to update the username by clicking the button, which you can do, but it would be better to update the state as you input the username, then use the button click as a form submission or something along those lines.

A good resource for this is here

logos_164
  • 736
  • 1
  • 13
  • 31