Im tryng a simple test to verify if the user can enter text on a text area, but for some reason the simulate method doesnt work..
CommentBox component
class CommentBox extends Component {
state = { comment: "" };
handleChange = event => {
this.setState({ comment: event.value });
};
handleSubmit = e => {
e.preventDefault();
this.setState({ comment: "" });
};
render() {
return (
<form>
<h4>
Add a CommentBox
<textarea onChange={this.handleChange} value={this.state.comment} />
<div>
<button onSubmit={this.handleSubmit}>Submit Comment</button>
</div>
</h4>
</form>
);
}
}
The test
export default CommentBox;
let wrapped;
beforeEach(() => {
wrapped = mount(<CommentBox />);
});
afterEach(() => {
wrapped.unmount();
});
it('has a text area and a button', () => {
expect(wrapped.find('textarea').length).toEqual(1);
expect(wrapped.find('button').length).toEqual(1);
});
it('has a text area that users can type in' ,() => {
const textarea = wrapped.find('textarea');
textarea.simulate('change', {
target: { ,
value: 'testing' }
});
wrapped.update();
expect(textarea.prop('value')).toEqual('testing');
});
error :
● has a text area that users can type in
expect(received).toEqual(expected) // deep equality
Expected: "testing"
Received: ""
Issue #1
handleChange = event => {
this.setState({ comment: event.value });
};
Actually value is event.target.value. But I believe better make destructuring right in arguments:
handleChange = ({ target: { value }}) => {
this.setState({ comment: value });
};
Issue#2:
after rerendering you have to run .find('textarea') again instead of relying on previous value; also you don't ever need wrapped.update()
So
it('has a text area that users can type in' ,() => {
wrapped.find('textarea').simulate('change', {
target: { value: 'testing' }
});
expect(wrapped.find('textarea').props().value).toEqual('testing');
});
works fine.
Related
I’m making a dynamic form in reactjs. But I have an annoying problem. I would only like to send the fields of the form that are filled out.
To do this, I use this piece of code that allows me to download all datas that are not filled.
Globally, my code for the sending part is
async handleSubmit(event) {
this.setState({ loading: true });
setTimeout(() => {
this.setState({ loading: false });
}, 2000);
event.preventDefault();
const {
name_contact='', name_contact1='', endfr='', endfr_1='',
} = this.state;
Object.keys(this.state).forEach(key => {
if (!this.state[key]) delete this.state[key];
})
await axios.post(
' MY_endpoint API',
{
name: `${name_contact},${name_contact_1} `,end: `${endfr},${endfr_1});
}
On this piece of code, I delete datas from this.state that are null to remove variables with nothing in them.
But my problem is that I don’t know how to handle the fact that a field is empty in axios.get.
I would like to be able to remove from get.axios the variables already deleted by
Object.keys(this.state).forEach(key => {
if (!this.state[key]) delete this.state[key];
})
For example if I do not fill in the name_contact_1 field, I would like axios.get to be
await axios.post(
' MY_endpoint API',
{
name: `${name_contact}`,end: `${endfr},${endfr_1});
}
or if i'm not puting nothing in endfr
await axios.post(
' MY_endpoint API',
{
name: `${name_contact},${name_contact_1} `,end: `${endfr_1});
}
So my question is : Does anyone have any idea how I can handle this? (It may not be possible with my code structure)
I’m not sure if I’m being clear enough, but if I’m not, tell me and I’ll make sure I change it.
PS: my full code
export default class FormPage extends Component {
constructor(props) {
super(props);
this.state = initialState;
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
showMessage = (bool) => {
setTimeout(() => {
this.setState({
showMessage: true
});
}, 2000);
if (this.state.Showing) return;
this.setState({ Show: true, Showing: true });
setTimeout(() => {
this.setState({ Show: false, Showing: false });
}, 2000);
}
showMessageFalse = (bool) => {
this.setState({
showMessage: false
});
this.setState(initialState);
}
handleChange(event) {
const InputValue = event.target.value;
const stateField = event.target.name;
this.setState({
[stateField]: InputValue,
});
console.log(this.state);
}
async handleSubmit(event) {
this.setState({ loading: true });
setTimeout(() => {
this.setState({ loading: false });
}, 2000);
event.preventDefault();
const {
name_contact='',
} = this.state;
Object.keys(this.state).forEach(key => {
if (!this.state[key]) delete this.state[key];
})
await axios.post(
' MY_endpoint API',
{
name: `${name_contact}`);
}
render() {
const { loading } = this.state;
return (
<div>
<ExpansionPanel title="Contacts" expandedTitle="Contacts" titleIcon="done_all" ><div>
<Container>
<div id="normal"><label id='title2'>Detail du contact</label></div><br/>
<Row align="center">
<Col id= "color" sm={3}> <label> Name: </label></Col> <Col id= "color" sm={3}><Input placeholder="Nom complet" type="string" name="name_contact" value={this.state.name_contact} onChange={this.handleChange}/><br /> </Col>
</Row>
</Container>
</div>
</ExpansionPanel>
<form onSubmit={this.handleSubmit}>
<br /><br /><div id="deb"><Button type="submit" value="Show" onClick={this.showMessageFalse.bind(null, true)} > Update </Button></div>
</form>
</div>
);
}
}
First of all, you need to understand that when you assign the value const {name_contact=''} = this.state; And then you later do delete this.state['name_contact'], like you've done above, you haven't actually changed the value of the name_contact variable. You've changed the value inside the state, but not the value itself.
Secondly, javascript cannot magically create your name string like that for you, with a comma only if there are 2 values. You'll have to write a function to do that.
Something like var name = [name_contact, name_contact1].filter(v =>v).join(','); The .filter function gets rid of the empty values from the array, and the .join turns it into a string with commas between values
So you'll end up with something more like this:
async handleSubmit(event) {
this.setState({ loading: true });
setTimeout(() => {
this.setState({ loading: false });
}, 2000);
event.preventDefault();
const {
name_contact='', name_contact1='', endfr='', endfr_1='',
} = this.state;
const name = [name_contact, name_contact1].filter(v =>v).join(',');
const end = [name_contact, name_contact1].filter(v =>v).join(',');
await axios.post(
' MY_endpoint API',
{
name, end
})
}
I started to learn React and I try to make a To do list. I stuck at editing items from the list. This is how I see this. To edit single item I click on the "edit" button, input appears in the place of the item, I submit changes by hiting the "enter" button.
My problem is
my function for submit button don't work (handleEditingDone - in the code).
when I click on the "edit" button TypeError appears
Cannot read property 'task' of undefined
(handleEditing in the code)
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: [
// task: '',
// id: '',
//completed: false,
// all of our to-dos
],
todo: '',
// an empty string to hold an individual to-do
filtertodos: []
}
}
inputChangeHandler = event => {
this.setState({[event.target.name]: event.target.value})
}
addTask = event => {
event.preventDefault();
let newTask = {
task: this.state.todo,
id: Date.now(),
completed: false,
editing: false
};
this.setState({
todos: [...this.state.todos, newTask],
todo: ''
})
}
handleEditing = (id) => {
const todos = this.state.todos.map(todo => {
if (todo.id === id) {
todo.editing = !todo.editing
}
return todo
});
this.setState({
todos : todos,
todo: {
changedText: this.todo.task,
}
})
}
handleEditingDone = event => {
if (event.keyCode === 13) {
this.setState(prevState => ({
todo: { // object that we want to update
editing: false, // update the value of specific key
}
}));
}
}
handleEditingChange = event => {
let _changedText = event.target.value;
this.setState({
todo: {changedText: _changedText}
});
}
componentDidMount() {
this.setState({
todo: {changedText: this.todo.task}
})
}
Todo.js
const Todo = props => {
return (
<div style={{display: "flex"}}>
{props.todo.editing ? (
<input onKeyDown={event => props.handleEditingDone} onChange={event => props.handleEditingChange}
type="text" value={props.changedText}/> )
:
(<p key={props.todo.id}
onClick={event => {
props.toggleComplete(props.todo.id)
}} >{props.todo.changedText}{props.todo.completed && ' Completed'}</p>)
}
<button onClick={event => props.handleEditing(props.todo.id)}>EDIT</button>
<button onClick={event => {
props.onDelete(props.todo.id)
}}>x
</button>
</div>
)
}
The problem is the following:
this.todo.task
There is no this.todo.
Try
this.state.todo
I made TextInput component, here is code:
export const TextInput = (props: ITextInputProps): TReactElement => {
const {
errorMessage,
hasError,
...restProps
} = props;
return (
<div>
<input
{ ...restProps }
type="text"
className={ mergeClassNames([
textInputStyles["text-input"],
hasError ? textInputStyles["text-input--error"] : "",
]) }
/>
{
hasError &&
<p className={ textInputStyles["text-input__error-message"] }>{ errorMessage }</p>
}
</div>
);
};
Now I wont test that onChange work correctly, I do it like this:
test("TextInput: should change value", () => {
let actualInputValue;
const textInputProps = {
onChange: (event: ChangeEvent<HTMLInputElement>): void => {
actualInputValue = event.currentTarget.value;
},
};
const textInputWrapper = shallow(<TextInput { ...textInputProps } />);
textInputWrapper.find(".text-input")
.simulate("change", {
currentTarget: {
value: "Hello, world!",
},
});
expect(actualInputValue)
.toBe("Hello, world!");
});
I feel that actualInputValue and onChange handler is excess because I can get value directly from .text-input
I tried read value like this (but got undefined):
test("TextInput: should change value", () => {
const textInputWrapper = shallow(<TextInput />);
textInputWrapper.find(".text-input")
.simulate("change", {
currentTarget: {
value: "Hello, world!",
},
});
expect(textInputWrapper.find(".text-input").props().value)
.toBe("Hello, world!");
});
Then I tried update textInputWrapper like this (but got undefined):
test("TextInput: should change value", () => {
const textInputWrapper = shallow(<TextInput />);
textInputWrapper.find(".text-input")
.simulate("change", {
currentTarget: {
value: "Hello, world!",
},
});
textInputWrapper.update();
expect(textInputWrapper.find(".text-input").props().value)
.toBe("Hello, world!");
});
Then I also tried use done callback (but got undefined):
test("TextInput: should change value", (done: () => void) => {
const textInputWrapper = shallow(<TextInput />);
textInputWrapper.find(".text-input")
.simulate("change", {
currentTarget: {
value: "Hello, world!",
},
});
textInputWrapper.update();
expect(textInputWrapper.find(".text-input").props().value)
.toBe("Hello, world!");
done();
});
I also used mount instead shallow and got same results...
Then I used actualInputValue and onChange handler :(
It's my questions: how to get actual value from textInputWrapper.find(".text-input") ?
Thank you so much!!!
I think the main issue you're having is within your expect statement (below)
expect(textInputWrapper.find(".text-input").props().value)
.toBe("Hello, world!");
You're running .props() on an array instead of a single node, assuming you know there will be only one ".text-input" replace it with the below.
expect(textInputWrapper.find(".text-input").at(0).props().value)
.toBe("Hello, world!");
You could also use .prop("value") instead of .props().value although that's more personal preference.
Also you don't need to use the done callback for this test, that's only for async functions such as MockApi calls
When I click submit on the button, only 1 value is submitted to the object instead of both. I have worked out that the state is updated correctly until the submit button is pressed and only 1 value is submitted into the obj.
I have used the functions as below:
onChange = (event) => {
this.setState({ term: {term1: event.target.value }});
}
onChange2 = (event) => {
this.setState({ term: {term2: event.target.value }});
}
onSubmit = (event) => {
event.preventDefault();
let obj = {
result1: this.state.term.term1,
result2: this.state.term.term2,
};
{console.log('obj', obj)}
this.setState({
term: {
term1: '',
term2: ''
},
items: [...this.state.items, obj]
});
}
I have used the render as below:
render() {
return (
<div>
<form onSubmit={this.onSubmit}>
<input value={this.state.term.term1} onChange={this.onChange} />
<input value={this.state.term.term2} onChange={this.onChange2} />
<button>Submit</button>
</form>
<List items={this.state.items} />
</div>
);
}
My state structure is as follows:
this.state = {
term: {
term1: '',
term2: ''
},
items: []
};
Any help would be great! Thank you!
It's because you're mutating the term state. You need to update the state like:
onChange = (event) => {
this.setState({ term: {...this.state.term, term1: event.target.value }});
}
Do the similar approach with onChange2. And then you'll get the merged term: term1 and term2.
You may even use updater callback like this:
onChange = (event) => {
this.setState((prevState) => ({
term: {
...prevState.term,
term1: event.target.value
}
}))
}
You may even just define single handler for both changes as commented by Henok:
onChange = (event, termNum) => {
this.setState((prevState) => ({
term: {
...prevState.term,
[termNum]: event.target.value
}
}))
}
And pass the termNum with responding term1, or term2 in your onChange:
onChange={this.onChange(term1)}
// there are sorts of methods to pass the parameter.
You may check my another post for further details about using parameters.
Try below change
onChange = (event) => {
this.setState(prevState => ({
term: {
...prevState.term,
term1: event.target.value
}
}))
}
onChange2 = (event) => {
this.setState(prevState => ({
term: {
...prevState.term,
term2: event.target.value
}
}))
}
I believe your state is overwriting itself each time your onChange functions are called. If you use the spread operator you'll keep your current values of state without over writing it.
onChange2 = (event) => {
this.setState({
term: {
...this.state.term,
term2: event.target.value
}
})
}
Do the same for the other onChange even you have as well.
Try like this
onSubmit = (value1, value2) = ev => {
ev.preventDefault();
console.log(value1,value2)
}
and call like this
<form onSubmit={this.onSubmit(this.state.term.term1,this.state.term.term2)}>
I'm not sure what I'm doing wrong, but I have an input field for entering a search term and trying to filter results based on the search term. The problem is that the first value being passed is an empty string and input is offset by 1 item for each keypress after that. For example, if I type 'sea', it would update the search term to be ' se'. Then, when I try to delete the value, it is offset the other direction, so deleting ' se' ends with 's', which can't be deleted.
(Here's a link to the app in progress: https://vibrant-yonath-715bf2.netlify.com/allpokemon. The full search functionality isn't working quite yet. I'm pretty new at this.)
import React, { Component } from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props) {
super(props);
this.state = {
pokemonList: [],
searchTerm: '',
fetched: false,
loading: false
};
this.updateResults = this.updateResults.bind(this);
}
componentWillMount() {
this.setState({
loading: true
});
fetch('https://pokeapi.co/api/v2/pokemon?limit=151')
.then(res => res.json())
.then(response => {
this.setState({
pokemonList: response.results,
loading: true,
fetched: true
});
});
}
handleSearchTermChange = (
event: SyntheticKeyboardEvent & { target: HTMLInputElement }
) => {
this.setState({ searchTerm: event.target.value });
this.updateResults();
};
updateResults() {
const filteredList = this.state.pokemonList.filter(
pokemon =>
pokemon.name.toUpperCase().indexOf(this.state.searchTerm.toUpperCase()) >= 0
);
console.log(this.state.searchTerm);
this.setState({
pokemonList: filteredList
});
}
render() {
const { fetched, loading, pokemonList } = this.state;
let content;
if (fetched) {
content = (
<div className="flex-grid">
{pokemonList.map((pokemon, index) => (
<Pokemon key={pokemon.name} id={index + 1} pokemon={pokemon} />
))}
</div>
);
} else if (loading && !fetched) {
content = <p> Loading ...</p>;
} else {
content = <div />;
}
return (
<div>
<input
onChange={this.handleSearchTermChange}
value={this.state.searchTerm}
type="text"
placeholder="Search"
/>
{content}
</div>
);
}
}
export default PokemonList;
setState is asynchronous, so your this.state.searchTerm is not updated when you call updateResults. You could e.g. filter the array in render instead.
Example
class App extends Component {
state = {
pokemonList: [
{ name: "pikachu" },
{ name: "bulbasaur" },
{ name: "squirtle" }
],
searchTerm: ""
};
changeSearchTerm = event => {
this.setState({ searchTerm: event.target.value });
};
render() {
const { pokemonList, searchTerm } = this.state;
const filteredList = pokemonList.filter(pokemon =>
pokemon.name.toUpperCase().includes(searchTerm.toUpperCase())
);
return (
<div>
<input value={searchTerm} onChange={this.changeSearchTerm} />
{filteredList.map(pokemon => <div>{pokemon.name}</div>)}
</div>
);
}
}
I think the problem is that you call this.updateResults();
and then calling this.setState({ searchTerm: event.target.value }); instead of using the callback function for setState.
For example:
this.setState({ searchTerm: event.target.value }, () => this.updateResults());
Hope I got it right.
Update:
Also I see many problems in your code, for example, why you update the list with a filtered list? you don't need to do that:
this.setState({
pokemonList: filteredList
});
Instead of updating the results in the state, you simply need to render the filtered list... meaning your state stay with the original list, also your filterd value, just in the render you pass the filtered list..