Testing local state using `evt.target.name` doesn't work - reactjs

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:'',
}

Related

calling function in React SetState gives error that userName is unlabelled why?

import React,{Component} from 'react'
class Formhandler extends Component {
constructor(props) {
super(props)
this.state = {
userName:""
}
}
changer=(event)=>{
this.setState(()=>{
userName : event.target.value
})
}
render()
{
return(
<div>
<label>UserName</label>
<input type="text" value={this.state.userName} onChange={this.changer}/>
</div>
)
}
}
export default Formhandler
You are getting the error because of invalid syntax.
Update changer function
changer = (event) => {
this.setState({ userName: event.target.value });
};
You need to return an object inside the setState function but you are not that's the source of issue(syntax error).
use a function inside setState when your new state value would depend on your previous state value, where the function passed inside the setState will receive previous state as argument
changer = (e) => {
this.setState((prevState) => ({
userName : e.target.value
})
);
}
pass an object to update the state, use this when it doesn't depend on your previous state value.
changer = (e) => {
this.setState({ userName: e.target.value });
};
import React from "react";
class Formhandler extends React.Component {
constructor(props) {
super(props);
this.state = {
userName: "",
};
}
changer(event) {
this.setState(() => ({
userName: event.target.value,
}));
}
render() {
return (
<div>
<label>UserName</label>
<input
type="text"
value={this.state.userName}
onChange={this.changer.bind(this)}
/>
</div>
);
}
}
export default Formhandler;
It will work, compare your version and this

how to change value of an object using reactjs

My objective is to change the value of an object and pass the modified object.
Here is the object:
{
id: '12497wewrf5144',
name: 'ABC',
isVisible: 'false'
}
Here is the code:
class Demo extends Component {
constructor(props) {
super(props)
this.state = {
demo: []
}
}
componentDidMount() {
axios
.get('/api/random')
.then(res => {
this.setState({ demo: res.data})
})
.catch(error => {
console.log(error)
})
}
render() {
return (
<div>
{this.state.demo.map((user)=>
<h1>{user.name}</h1>
<input type="checkbox" value={user.value} />
)}
</div>
)
}
}
export default Demo
I don't know what to write in onchange method for checkbox to only change the value within the object.
Note: value is string isVisible (we need to change value as boolean)
Can anyone help me in this query?
In order to change a certain key of an object you can use the following
this.setState({
...this.state,
demo: {
...this.state.demo,
isVisible: <NEW VALUE>
}
})

Passing the state of a Child component to a Parent?

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.

How am I switching between an uncontrolled/controlled input? React

I have a component that has an input:
state = {
org: {
orgName: ''
}
};
updateInput = field => event => {
this.setState({
org: {
[field]: event.target.value
}
})
}
render() {
let { org } = this.state
return (
<input
value={org.orgName}
onChange={this.updateInput('orgName')}
/>
)
}
I initialize the input value to '', but as soon as I type anything into the input, I get this error:
A component is changing a controlled input of type undefined to be uncontrolled
I thought if I initialized the input, then it would always be controlled, but apparently this is wrong. What is the proper way for this input to always be controlled?
Use the following format:
constructor(){
super()
this.state = {
org: {
orgName: ''
}
};
}
updateInput = (field, event) => {
this.setState({
org: {
[field]: event.target.value
}
})
}
render() {
let { org } = this.state
return (
<input
value={org.orgName}
onChange={(event) => {this.updateInput('orgName', event)}}
/>
)
}
If you use it like this then you get:
place state inside a constructor so you can change it with this.setState.
onChange is being fired only when an event is happening and not automatically fired.
You need to pass the event down through the handler chain so the handler as access to it.
not sure what you meant by updateInput = field => event => {} there is no event in the chain at that point so you cannot access it.
Hope it helps :)

React: Updating one state property removes other states properties in the state

I have two text boxes with id set dynamically as en which can change. Every time the onchange is emittted from input field, the state should update. The problem is, if I type something into one of the inputs, the other input state seems to disappear. For example, If I type test into title text field, the state becomes:
this.state = {
translation: {
en: {
title: 'test'
}
}
};
If I move on to typing into the content text box, it seems to replace the title state. like so,
this.state = {
translation: {
en: {
content: 'content'
}
}
};
They should update the state independently without affecting each other. Here is my intended state
this.state = {
translation: {
en: {
title: 'title-text',
content: 'content-text'
}
}
};
Component
import React from 'react';
export default class Demo extends React.Component
{
constructor(props)
{
super(props);
// default state
this.state = {
translation: {}
};
}
onSubmit(e)
{
e.preventDefault();
console.log(this.state);
}
render()
{
return (
<form onSubmit={(event) => this.onSubmit(event)}>
<input id="en" name="title"
onChange={(event) => this.setState({
translation: {
[event.target.id]: {
title: event.target.value
}
}
})} />
<input id="en" name="content"
onChange={(event) => this.setState({
translation: {
[event.target.id]: {
content: event.target.value
}
}
})} />
<button type="submit">Login</button>
</form>
);
}
}
setState does not deeply merge current state and updates. You should spread your translation state prop.
this.setState({
translation: {
...this.state.translation,
[event.target.id]: {
content: event.target.value
}
}
})
The behaviour is correct.
e.g: var obj = {a:1,b:3};
obj = {a:4};//Obj updated. Now obj.b will be undefined.This is what you worried for.
In your case, you can do something like this. It is not one of the best solution.
onSubmit(e)
{
e.preventDefault();
let state = this.state.translation;
state.en.content = 'content';
this.setState(state);
console.log(this.state);
}
As was mentioned, setState doesn't deeply merge values, however what you really need is to do is this:
this.setState({
translation: {
[event.target.id]: {
...this.state.translation[event.target.id],
content: event.target.value
}
})
And same for title.
You forgot about immutability. Just add to your code ...this.state for import all the properties that have been there before.
this.state = {
...this.state,
..
}

Resources