Rails/React: Refs Must Have Owner - javascript

I am using the react-rails gem. In my component I have two input fields with a ref each. However, the console throws me the following error:
Uncaught Error: addComponentAsRefTo(...): Only a ReactOwner can have
refs. You might be adding a ref to a component that was not created
inside a component's render method, or you have multiple copies of
React loaded
Facebook's documentation does not help.
_new_item.js.jsx
var NewItem = React.createClass({
handleClick() {
var name = this.refs.name.value;
var description = this.refs.description.value;
console.log('The name value is ' + name + 'the description value is ' + description);
},
render() {
return (
<div>
<input ref='name' placeholder='Enter the name of the item' />
<input ref='description' placeholder='Enter a description' />
<button onClick={this.handleClick}>Submit</button>
</div>
);
}
});
_body.js.jsx
var Body = React.createClass({
render() {
return (
<div>
<NewItem />
<AllItems />
</div>
);
}
});
_main.js.jsx
var Main = React.createClass({
render() {
return (
<div>
<Header />
<Body />
</div>
);
}
});
I really don't know where the error is.

You'll better use the function refs instead of the named refs:
So instead of
<input ref='name' />
You can use:
<input ref={node => this.nameElement = node} />
And then instead of:
this.refs.name
Use
this.nameElement
I believe it will fix your problem.

Related

Uncaught TypeError - Getting errors after already defining state variable in React component constructor

I am getting the error: Uncaught TypeError: Cannot read properties of undefined (reading 'state') even though state is defined in the constructor of my React component. I get the error at the line where I set the value of the <input> to {this.state.deckName}
export class DeckForm extends React.Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`)
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<this.DeckList />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
DeckName() {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
DeckList() {
let format = 'EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3'
return (
<div className='form-group'>
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className='form-control'
rows='15'
required
>
{format}
</textarea>
</div>
);
}
}
Use below code it's working for me
https://codesandbox.io/s/weathered-water-jm1ydv?file=/src/App.js
DeckName() {
return (
<div className="form-group mb-3">
<input
value={this?.state.deckName} /* ERROR HERE */
onChange={this?.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
}
DeckList() {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this?.state.deckList}
onChange={this?.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
}
You can use es6 function to return components which exist outside of parent rather than using method,change only this part of code:
1.instead of DeckName(){...}use DeckName =()=>{....}
2.instead of DeckList(){...}use DeckList =()=>{....}
Full modified code:
import React, { Component } from "react";
export class DeckForm extends Component {
constructor(props) {
super(props);
this.state = { deckName: "", deckList: "" };
// Bind our event handler methods to this class
this.handleDeckNameChange = this.handleDeckNameChange.bind(this);
this.handleDeckListChange = this.handleDeckListChange.bind(this);
this.handleSubmission = this.handleSubmission.bind(this);
}
// Event handler method to update the state of the deckName each time a user types into the input form element
handleDeckNameChange(event) {
let typed = event.target.value;
this.setState({ deckName: typed });
}
// Event handler method to update the state of the deckList each time a user types into the textarea from element]
handleDeckListChange(event) {
let typed = event.target.value;
console.log(typed);
this.setState({ deckList: typed });
}
// Event handler method to handle validation of deckName and deckList
handleSubmission(event) {
console.log(`${this.state.deckName}`);
console.log(`${this.state.deckList}`);
}
render() {
return (
<form className="was-validated">
<this.DeckName />
<this.DeckList />
<button type="submit" className="btn-lg btn-warning mt-3">
Create
</button>
</form>
);
}
DeckName = () => {
return (
<div className="form-group mb-3">
<input
value={this.state.deckName}
onChange={this.handleDeckNameChange}
type="text"
placeholder="Deck name"
className="form-control"
required
/>
</div>
);
};
DeckList = () => {
let format =
"EXACT CARD NAME 1\nPot of Greed 3\nChange of Heart 3\nGraceful Charity 3";
return (
<div className="form-group">
<textarea
value={this.state.deckList}
onChange={this.handleDeckListChange}
className="form-control"
rows="15"
required
>
{format}
</textarea>
</div>
);
};
}
Live Demo:
https://codesandbox.io/s/brave-hill-l0eknx?file=/src/DeckForm.js:0-2091
Another way to solve the issue is defining DeckName() method using arrow function. Here's a code snippet I tried with react 17.0.2 which worked perfectly fine for me.
It's always recommended to use arrow function to define methods in class based components, since arrow function inherit "this" from the block its called from, so you also don't have to do .bind(this) whenever you call methods.
JSX
import React, { Component } from 'react'
class Test extends Component {
constructor(props) {
super(props);
this.state = {
deckName: '',
deckList: ''
};
}
DeckName = () => {
return (
<div className='form-group mb-3'>
<input
value={this.state.deckName} /* ERROR HERE */
onChange={this.handleDeckNameChange}
type='text'
placeholder='Deck name'
className='form-control'
required
/>
</div>
);
}
render() {
return (
<form className='was-validated'>
<this.DeckName />
<button type='submit' className='btn-lg btn-warning mt-3'>Create</button>
</form>
);
}
}
export default Test

React onClick function within render() is undefined

With the react component below I am getting the error:
Uncaught (in promise) TypeError: Cannot read property 'oncontactClick' of undefined
class Contacts extends React.Component{
constructor(props, contact){
super(props);
this.state ={ contacts: [] };
this.oncontactClick = this.oncontactClick.bind(this);
}
oncontactClick(){
console.log("contact clicked");
}
render(){
var contacts = this.state.contacts;
contacts = contacts.map(function(contact, index){
return(
<div key={index} className="contactcard" onClick={this.oncontactClick}>
<div className="name">{contact.name}</div>
</div>
)
});
return(
<div id="addcontainer">
<form id="addinput">
<input type="text" ref="name" placeholder="name" required/>
</form>
<ul>{contacts}</ul>
</div>
);
}
};
However, if I place the onClick in the form "addinput", it works fine. Why is that the case and how can I get it to work within the contactcard? I would like to avoid using jQuery.
Array.map takes two arguments :
First one is the iteratee.
Second one is what defines this in the iteratee.
This should works :
contacts = contacts.map(function(contact, index){
return(
<div key={index} className="contactcard" onClick={this.oncontactClick}>
<div className="name">{contact.name}</div>
</div>
)
}, this);

React.js passing => in props

Follow up on this question but deserved a separate thread Trying to convert React.CreateClass to extends React.Component.
I'm wondering how I can make use of => while calling the component but without passing in the exact input name, that should get filled up by the component internally:
Component:
var FormFields = React.createClass({
render: function() {
const upwd = this.props.unamepwd;
return(
<form>
Username: <input value={upwd.username}
onChange={this.props.handleChange('username')} /><br />
Password: <input type="password" value={upwd.password}
onChange={this.props.handleChange('password')} />
<button onClick={this.props.updateChanges}>Go!</button>
</form>
);
}
});
While in the parent render method I would like to call it something like:
<FormFields unamepwd={this.state}
handleChange={() => self.handleChange()} updateChanges={self.updateToServer} />
The following would work but only for the username field:
<FormFields unamepwd={this.state}
handleChange={() => self.handleChange('username')} updateChanges={self.updateToServer} />
Just pass an argument to the function.
<FormFields unamepwd={this.state}
handleChange={(fieldName) => self.handleChange(fieldName)} updateChanges={self.updateToServer} />
and call it like:
this.props.handleChange('password')
You can use e.target in the function handling onChange event to get a reference to the input that has triggered the event. So you only pass a reference to the handleChange function arround, no need for {x => handleChange('name') } or { handleChange.bind('name') } or whatever.
var FormFields = React.createClass({
render: function() {
const upwd = this.props.unamepwd;
return(
<form>
Username: <input value={upwd.username}
onChange={this.props.handleChange} name="username" /><br />
Password: <input type="password" value={upwd.password}
onChange={this.props.handleChange} name="password" />
<button onClick={this.props.updateChanges}>Go!</button>
</form>
);
}
});
var Parent = React.createClass({
handleChange(e){
console.log( e.target.name );
},
render(){
return <FormFields handleChange={ this.handleChange } />
}
});
http://jsfiddle.net/mg5cbepk/

How to access the html component from a composite component in react?

Consider this component
var React = require("react");
var Input = React.createClass({
render:function(){
return (<input {...this.props}/>)
}
})
module.exports = Input;
This is another component which uses the first component
var React = require('react');
var Button = require('./Button.react');
var Input = require('./Input.react');
var Form = React.createClass({
render:function(){
return( <div>
<Input ref = {(input) => this._user=input} placeholder='Username'/>
<Input ref = {(input) => this._pass=input} type="password" placeholder='Password'/>
<Button onClick={this.onPress}>Login</Button>
</div> )
},
onPress:function(){
console.log(this._user.value);
}
});
module.exports = Form;
I want to access the value property of the <input /> of the first component. I understand that only the component constructor is available in the callback function provided for ref attribute.
So is there any way I can access the html component within the second component ?
I apologise if this question is duplicate , I am new to react and unfamiliar with the terminology.
You can use refs
<Input ref = {(input) => this._pass=input} type="password" placeholder='Password'/>
and then
this._pass.yourValueFunction()
However, I suspect this is not what you want to do. You're trying to get the value from <Input />, but that should be done with onChange callback from <Input /> that's setting the value in its parent.
Something like...
var Input = React.createClass({
render() {
return <input {...this.props} onChange={this.props.handleChange} />
}
})
and in your parent you need to define handleChange callback, for example
handleChange(e) {
this.setState({ inputValue: e.target.value })
// or possibly this.inputValue = inputValue
}
and pass it to the <Input />
<Input handleChange={handleChange} ... />
Then the <Input /> value will always be accessible: this.state.inputValue

How does one query a field value in ReactJS?

I have code that is at present accessing a React component directly, and getting a warning saying "You probably don't want to do this." The code is:
var description = document.getElementById('description').value;
console.log(description);
new_child.setState({
description: description
});
The component it's trying to access is:
var that = this;
return (
<table>
<tbody>
{that.state.children}
</tbody>
<tfoot>
<td>
<textarea className="description"
placeholder=" Your next task..."
onChange={that.onChange}
name="description"
id="description"></textarea><br />
<button onClick={that.handleClick}
id="save-todo">Save</button>
</td>
</tfoot>
</table>
);
What is an idiomatic way to replace the code I have here with a "thinking in React" replacement?
You should be using the refs attribute.
So lets say you have a render method that looks like this:
render: function () {
<MyTextBox ref="myText" />
<div>Some other element</div>
}
Now lets say the rendered MyTextBox element has a expode() method. You could call it using:
this.refs.myText.explode()
Essentially, refs has a property myText on it because during render, you provided a ref name to it by writing ref="myText"
You can find more info here
I'd call myself a complete novice here but looking at this code
https://github.com/abdullin/gtd/blob/master/web/components/TaskComposer.jsx
You should be able to bind to the textareas value:
<textarea id="description"
value={that.state.text} ...
and then you can pull out the value in your click handler like this:
var description = this.state.text;
Let me know if this works :)
UPDATE
Just looked at the React home-page (https://facebook.github.io/react/) and the 3rd example An Application seems to follow this pattern as well
var TodoList = React.createClass({
render: function() {
var createItem = function(itemText, index) {
return <li key={index + itemText}>{itemText}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
});
var TodoApp = React.createClass({
getInitialState: function() {
return {items: [], text: ''};
},
onChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var nextItems = this.state.items.concat([this.state.text]);
var nextText = '';
this.setState({items: nextItems, text: nextText});
},
render: function() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items} />
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 1)}</button>
</form>
</div>
);
}
});
React.render(<TodoApp />, mountNode);
So to answer you're question I think the React way to do stuff is to bind to this.state.

Categories