Why is this.refs undefined in the code below?
class NewItem extends React.Component {
handleClick() {
console.log(this.refs) //prints out undefined
const name = this.refs.name.value;
const description = this.refs.description.value;
$.ajax({
url: 'api/v1/items',
type: 'POST',
data: {
item: {
name: name,
description: description
}
},
success: (item) => {
this.props.handleSubmit(item);
}
});
}
render() {
return (
<div>
<input ref='name' placeholder='Enter the name of the item' />
<input ref='description' placeholder='Enter the description of the item' />
<button onClick={this.handleClick}>Submit</button>
</div>
)
}
}
The method is not bound to this when used as a function:
<button onClick={this.handleClick.bind(this)}>Submit</button>
or
<button onClick={event => this.handleClick(event)}>Submit</button>
or bind it in constructor:
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
You need to bind this to your handleClick() function, like this:
<button onClick={this.handleClick.bind(this)}>Submit</button>
or through the constructor, like this:
constructor(props) {
...
this.handleClick = this.handleClick.bind(this);
}
Though you should avoid using string literals in refs. This approach is deprecated.
Legacy API: String Refs
If you worked with React before, you might be
familiar with an older API where the ref attribute is a string, like
"textInput", and the DOM node is accessed as this.refs.textInput. We
advise against it because string refs have some issues, are considered
legacy, and are likely to be removed in one of the future releases. If
you're currently using this.refs.textInput to access refs, we
recommend the callback pattern instead.
Instead do:
constructor(props) {
this.nameInputRef;
this.descriptionInputRef;
}
...
<input ref={(el) => {this.nameInputRef = el;} placeholder='Enter the name of the item' />
<input ref={(el) => {this.descriptionInputRef = el;} placeholder='Enter the description of the item' />
You havens bind the functions. it should be done like this
class NewItem extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.refs) //prints out undefined
const name = this.refs.name.value;
const description = this.refs.description.value;
$.ajax({
url: 'api/v1/items',
type: 'POST',
data: {
item: {
name: name,
description: description
}
},
success: (item) => {
this.props.handleSubmit(item);
}
});
}
render() {
return (
<div>
<input ref='name' placeholder='Enter the name of the item' />
<input ref='description' placeholder='Enter the description of the item' />
<button onClick={this.handleClick}>Submit</button>
</div>
)
}
}
Try using the ES6 features, The arrow functions to avoid this binding issue.
like this.
handleClick =()=> {
console.log(this.refs) //prints out undefined
const name = this.refs.name.value;
const description = this.refs.description.value;
$.ajax({
url: 'api/v1/items',
type: 'POST',
data: {
item: {
name: name,
description: description
}
},
success: (item) => {
this.props.handleSubmit(item);
}
});
}
Related
This is my code for a modal form I'm using and I wanted to use my function to console.log, but I'm not sure how to change the state in this.set.state? Any help is appreciated since Im very new to react. The variable st is just the string it takes in each time
interface State {
name: string;
email: string;
amt: string;
}
interface Props {
isOpen: boolean;
handleClose: () => void;
handleSendRequest: (values: State) => void;
}
export default class FormDialog extends React.Component<Props, State> {
state = { name: "", email: "", amt: "" };
onChange = ((st: string) => (event: any) => {
const newObject: any = {};
newObject[st] = event.target.value;
this.setState({ State: newObject[st] }); //this part im struggling with
}).bind(this);
render() {
const { isOpen, handleClose, handleSendRequest } = this.props;
return (
<Dialog
open={isOpen}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Send Money Request</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
id="standard-read-only-input"
name="name"
label="Contact Name"
defaultValue="John"
onChange={this.onChange("name")}
/>
CodeSandbox example
You can cast newObject as seen below. This answer was derived from this Stack Overflow answer.
onChange = (st: string) => (event: any) => {
const newObject = { [st]: event.target.value } as Pick<State, keyof State>;
this.setState(newObject);
};
EDIT:
In response to: [H]ow it come it doesn't work for default values in the text field? If i take the default tag off and enter a new value it works tho
This is because defaultValue for your input is hard coded. Your component doesn't know about the value you defined in your input. You would have to move the default value into your state and provide the value to the <TextField /> component.
export default class FormDialog extends React.Component<Props, State> {
state = {
name: "John", // Define the default value here
email: "",
amt: "",
};
onChange = (st: string) => (event: any) => {
const newObject = { [st]: event.target.value } as Pick<State, keyof State>;
this.setState(newObject);
};
render() {
return (
<TextField
defaultValue={this.state.name} // Pass the default value as a prop here
onChange={this.onChange("name")}
/>
);
}
}
Here is a simple example in javascript, which will log the state after it has been changed.
class Hello extends React.Component {
constructor(props) {
super(props)
this.state = {
name: ""
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
name: event.target.value
}, () => console.log(this.state));
}
render() {
return <div > < input type = "text"
id = "name"
onChange = {
this.handleChange
}
/></div > ;
}
}
ReactDOM.render( <
Hello / > ,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I followed a tutorial to make an Asp.Net Core MVC app with a ReactJs front end (https://reactjs.net/tutorials/aspnetcore.html). I've been adding additional functionality to the project after completing the tutorial to see what else I can do with it.
My <AddColourForm> component assembles a <Colour> object and posts it off via an XmlHttpRequest to my API controller which in turn persists it to local storage. The submitUrl for the controller is passed in through the props. This works.
I've since tried to add the <SoftDeleteColour> component to each colourNode rendered in the <ColourList> which I intend to behave in more-or-less the same manner as the <AddColourForm> component. Each colourNode rendered in the <ColourList> has it's own delete button and I want the <SoftDeleteColour> component to take the colour.id from the selected colour and pass it to the softDelete action on the API controller so that can be handled in turn (it'll find the colour by id and append a DateDeleted to it, the API will then ignore any colours where DateDeleted != null) and the <SoftDeleteColour> component can then call loadColoursFromServer() to bring back the refreshed list from the storage. I want <SoftDeleteColour> to receive the softDeleteUrl from props in the same way that the add form does.
When I run the project in debug the softDeleteUrl is coming in as undefined and when I inspect the props in the browser it doesn't contain the softDeleteUrl. Also the "colour" is undefined so I feel like my <SoftDeleteColour> component isn't receiving the props or state. I'm new to React and struggling conceptually with props/state binding a little bit so I suspect this is the source of my problem.
How can I pass the softDeleteUrl and the properties of the colour from the <ColourList> that I am selecting for deletion to the <SoftDeleteColour> component? Do I need to call something like <SoftDeleteColour HandleDeletion=this.HandleDeletion.bind(this) /> or something?
class ColourDisplay extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] };
this.handleColourSubmit = this.handleColourSubmit.bind(this);
}
loadColoursFromServer() {
const xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ data: data });
};
xhr.send();
}
handleColourSubmit(colour) {
const data = new FormData();
data.append('name', colour.name);
data.append('brand', colour.brand);
data.append('expiry', colour.expiry);
data.append('serialNumber', colour.serialNumber);
const xhr = new XMLHttpRequest();
xhr.open('post', this.props.submitUrl, true);
xhr.onload = () => this.loadColoursFromServer();
xhr.send(data);
}
componentDidMount() {
this.loadColoursFromServer();
}
render() {
return (
<div className="colourDisplay">
<h1>Colours</h1>
<ColourList data={this.state.data}/>
<AddColourForm onColourSubmit={this.handleColourSubmit}/>
</div>
);
}
}
class ColourList extends React.Component {
render() {
const colourNodes = this.props.data.map(colour => (
<Colour name={colour.name} key={colour.id}>
<div>Brand: {colour.brand}</div>
<div>Exp: {colour.expiry}</div>
<div>Serial #: {colour.serialNumber}</div>
<div>Date Added: {colour.dateAdded}</div>
<SoftDeleteColour />
</Colour>
));
return <div className="colourList">{colourNodes}</div>;
}
}
class SoftDeleteColour extends React.Component {
constructor(props) {
super(props)
this.state = {
colour: this.props.colour
};
}
HandleDeletion(colour) {
var xhr = new XMLHttpRequest();
var url = this.props.softDeleteUrl + colour.id;
xhr.open('DELETE', url, true);
xhr.onreadystatechange = () => {
if (xhr.status == 204) {
this.loadColoursFromServer();
}
}
xhr.send();
}
render() {
return (
<button onClick={() => { this.HandleDeletion(this.state.colour); }}>Delete</button>
)
}
}
class AddColourForm extends React.Component {
constructor(props) {
super(props);
this.state = { name: '', brand: '', expiry: '', serialNumber: '' };
this.handleNameChange = this.handleNameChange.bind(this);
this.handleBrandChange = this.handleBrandChange.bind(this);
this.handleExpiryChange = this.handleExpiryChange.bind(this);
this.handleSerialNumberChange = this.handleSerialNumberChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleNameChange(e) {
this.setState({ name: e.target.value });
}
handleBrandChange(e) {
this.setState({ brand: e.target.value });
}
handleExpiryChange(e) {
this.setState({ expiry: e.target.value });
}
handleSerialNumberChange(e) {
this.setState({ serialNumber: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
const name = this.state.name.trim();
const brand = this.state.brand.trim();
const expiry = this.state.expiry.trim();
const serialNumber = this.state.serialNumber.trim();
if (!name || !brand || !expiry || !serialNumber) {
return;
}
this.props.onColourSubmit({
name: name,
brand: brand,
expiry: expiry,
serialNumber: serialNumber
})
this.setState({
name: '',
brand: '',
expiry: '',
serialNumber: ''
});
}
render() {
return (
<form className="addColourForm" onSubmit={this.handleSubmit}>
<h2>Add a colour to your list</h2>
<div>
<input
type="text"
placeholder="Colour"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
<div>
<input
type="text"
placeholder="Brand"
value={this.state.brand}
onChange={this.handleBrandChange}
/>
</div>
<div>
<input
type="text"
placeholder="Expiry MM/YY"
value={this.state.expiry}
onChange={this.handleExpiryChange}
/>
</div>
<div>
<input
type="text"
placeholder="Serial #"
value={this.state.serialNumber}
onChange={this.handleSerialNumberChange}
/>
</div>
<input type="submit" value="Post" />
</form>
);
}
}
class Colour extends React.Component {
render() {
return (
<div className="colour">
<h2 className="colourName">{this.props.name}</h2>
{this.props.children}
</div>
);
}
}
ReactDOM.render(
<ColourDisplay
url="/colours"
submitUrl="/colours/new"
softDeleteUrl="/colours/softDelete"
/>,
document.getElementById('content')
);
I think I know what I need to do to make my searchLocationChange function work, but I'm not sure quite how to do it. Please forgive the indentation, was grappling a fair bit with StackOverflow's WYSIWYG!
Here's my Parent component setup:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
forecasts: [],
location: {
city: '',
country: '',
},
selectedDate: 0,
searchText: '',
};
this.handleForecastSelect = this.handleForecastSelect.bind(this);
this.searchLocationChange = this.searchLocationChange.bind(this);
}
}
With this specific function I want to make work:
searchLocationChange() {
console.log(this.state.searchText);
Axios.get('https://mcr-codes-weather.herokuapp.com/forecast', {
params: {
city: this.state.searchText,
},
})
.then((response) => {
this.setState({
forecasts: response.data.forecasts,
location: {
city: response.data.location.city,
country: response.data.location.country,
}
});
});
}
And in my Child component, the logic is:
class SearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const enteredText = event.target.value;
this.setState({
searchText: enteredText
});
}
render() {
return (
<span className="search-form">
<div className="search-form__input"><input type="text" value={this.state.searchText} onChange={this.handleInputChange} /></div>
<div className="search-form__submit"><button onClick={this.props.searchLocationChange}>Search</button></div>
</span>
);
}
}
I realise I'm trying to update the Parent component with searchText from the state of the Child component, but I can't figure out what I need to change to make this work. I have a sneaking suspicion I'm 99% of the way there, and it's only a few more lines I need, but I could be way off?
You're already passing down searchLocationChange from your parent.
in parent component:
searchLocationChange(searchedText) {
console.log(searchText);
Axios.get('https://mcr-codes-weather.herokuapp.com/forecast', {
params: {
city: searchText,
},
})
.then((response) => {
this.setState({
forecasts: response.data.forecasts,
location: {
city: response.data.location.city,
country: response.data.location.country,
},
});
});
}
in child:
render() {
const { searchText } = this.state;
return (
<span className="search-form">
<div className="search-form__input"><input type="text" value={this.state.searchText} onChange={this.handleInputChange} /></div>
<div className="search-form__submit"><button onClick={()=>{this.props.searchLocationChange(searchText)}}>Search</button></div>
</span>
);
}
You should call the function like that this.props.searchLocationChange(this.state.searchText)
You can do something like below
<div className="search-form__submit"><button onClick={() => {this.props.searchLocationChange(this.state.searchText)}}>Search</button></div>
and function definition should be
searchLocationChange(searchText) {
You are mixing controlled and uncontrolled. Either do controlled or uncontrolled. So take the search Text from parent only. Above solutiion is one way of doing this . Another way is to pass searchTxt from parent to child.
<SearchForm
searchTxt={this.state.searchTxt}
handleInputChange={this.handleInputChange}
searchLocationChange={this. searchLocationChange}
/>
Move your handleInputChange in parent:
handleInputChange = (event) => {
const enteredText = event.target.value;
this.setState({ searchText: enteredText });
}
Then change your child component respective line to
<div className="search-form__input"><input type="text" value={this.props.searchText} onChange={this.props.handleInputChange} /></div>
Now when you try the above code it should work. Now you are keeping your searchTxt in the parent component. your SearchForm component is Completely controlled now.
When running enzyme specs, I have this spec that I can't seem to pass using evt.target.name in my event handler. The spec looks like this:
describe('<CampusInput /> component', () => {
let renderedCampusInput
let campusInputInstance
beforeEach(() => {
renderedCampusInput = shallow(<CampusInput />)
campusInputInstance = renderedCampusInput.instance()
})
it.only('handleChange should update the local state', () => {
renderedCampusInput.find('input').simulate('change', {
target: { value: 'Another Campus Name' }
})
expect(campusInputInstance.state.name).to.equal('Another Campus Name')
})
})
My component looks like this:
export default class CampusInput extends Component {
constructor(props) {
super(props)
this.state = {
name: ''
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(evt) {
// this line doesn't pass the spec :(
this.setState({ [evt.target.name]: evt.target.value })
// this passes the spec
this.setState({ name: evt.target.value })
}
render() {
return (
<div>
<input
name="name"
type="text"
onChange={this.handleChange}
value={this.state.name}
/>
</div>
)
}
}
I can only get it to pass if I hard-code the key in my setState(). How can I re-write the specs so that it passes if I use setState({[evt.target.name]: evt.target.vale})?
First of all you are expecting name of the input to equal it's value , the second thing is you didn't add the name key to the event object .
In order to solve this we need to add the name key to the event in your test:
renderedCampusInput.find('input').simulate('change', {
target: { name:'name',value: 'Another Campus Name' }
})
and change the expect method to equal 'name'
expect(campusInputInstance.state.name).to.equal('name')
also you can add expect value of the input
expect(campusInputInstance.state.value).to.equal('Another Campus Name')
but don't forget to add it to the component state
this.state = {
name: '',
value:'',
}
Hi i'm having problems with Flow and React.
I'm getting these errors and I want to get rid of them. What am I missing I have search everywhere. I understand that the props are missing but I can't seem to find where to define them.
Flow: property className. Property not found in props of React element form
Flow: property onSubmit. Property not found in props of React element form
export default class LoginForm extends React.Component {
_submitForm: Function;
constructor(props?: {}) {
super(props);
this.state = {
form: {
email: '',
password: '',
},
errors: {
_form: "",
email: "",
password: ""
}
};
this._submitForm = this._submitForm.bind(this);
}
_handleValues(param: string, value?: string) {
let obj = this.state;
obj['form'][param] = value;
this.setState(obj);
}
_submitForm(event: Event) {
this._clearErrors(event);
let form = this.state.form;
AxiosQueue
.post({
url: LINK.AUTHENTICATE,
data: form
})
.then(({data}) => {
if (!data.success) {
return;
}
})
.catch((response) => {
console.error(response);
});
}
render() {
const {errors, form} = this.state;
const user = UserStore.getUser();
const formText = FORM_TEXT[user.language || "en_AU"];
return (
<form className="form-inline" onSubmit={this._submitForm}>
{errors._form}
<InputEmail id="email" error={errors.email} value={form.email} callback={this._handleValues}/>
<InputPassword id="password" error={errors.password} value={form.password}
callback={this._handleValues}/>
<button type="submit" className="btn btn-default">{formText.LOGIN}</button>
</form>
);
}
}
There is conflict with your variable name form in Syntex const {errors, form} = this.state; and form component. Solution is to give some other name in this.state. Like
this.state = {
formValidation:{
//Validation properties
}
}
And consume so that will remove conflict
const {formValidation} = this.state