Decrement and increment quantity with Reactjs - reactjs

I am newbie to Reactjs and I am making a function to increase and decrease quantity. My code below:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
quantity: 1,
show: true,
max:5,
min:0
};
}
IncrementItem = () => {
if(this.state.quantity > 9) {
}else {
this.setState({
quantity: this.state.quantity + 1
});
}
}
DecreaseItem = () => {
if(this.state.quantity <= 1) {
}else {
this.setState({ quantiy: this.state.quantity - 1 });
}
}
ToggleClick = () => {
this.setState({ show: !this.state.show });
}
render() {
return (
<div>
<button onClick={this.IncrementItem}>+</button>
<input className="inputne" value={this.state.quantity} />
<button onClick={this.DecreaseItem}>-</button>
</div>
);
}
I have problems are:
Browser appears an error :
Warning: Failed prop type: You provided a value prop to a form field without an onChange handler
I cannot change value in input from View.
Please let me know how to solve. Thank for your help.

There are two issues in this code:
There is no onChange handler. Read more on how to handle onChange listeners here
Using value from this.state.quantity to increment and decrement is bad practice because setState function is asynchronous.
You can use functional version of setState to get around this:
this.setState(prevState => {
if(prevState.quantity > 0) {
return {
quantity: prevState.quantity - 1
}
} else {
return null;
}
});
Code Snippet:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
quantity: 1,
show: true,
max: 5,
min: 0
};
}
IncrementItem = () => {
this.setState(prevState => {
if(prevState.quantity < 9) {
return {
quantity: prevState.quantity + 1
}
} else {
return null;
}
});
}
DecreaseItem = () => {
this.setState(prevState => {
if(prevState.quantity > 0) {
return {
quantity: prevState.quantity - 1
}
} else {
return null;
}
});
}
ToggleClick = () => {
this.setState({
show: !this.state.show
});
}
handleChange = (event) => {
this.setState({quantity: event.target.value});
}
render() {
return ( <div>
<button onClick={this.IncrementItem}>+</button>
<input className="inputne" value={this.state.quantity} onChange={this.handleChange}/>
<button onClick = {this.DecreaseItem}>-< /button>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

You need to Update the value, add a onChange on Input field to do that.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
quantity: 1,
show: true,
max:5,
min:0
};
}
IncrementItem = () => {
if(this.state.quantity > 9) {
}else {
this.setState({
quantity: this.state.quantity + 1
});
}
}
DecreaseItem = () => {
if(this.state.quantity <= 1) {
}else {
this.setState({ clicks: this.state.quantity - 1 });
}
}
ToggleClick = () => {
this.setState({ show: !this.state.show });
}
UpdateValue = (e) => {
this.setState({ quantity: e.target.value });
}
render() {
return (
<div>
<button onClick={this.IncrementItem}>+</button>
<input className="inputne" value={this.state.quantity} onChange={this.UpdateValue} />
<button onClick={this.DecreaseItem}>-</button>
</div>
);
}

Try this as well it may help you
class Counters extends Component {
state = {
counters: [
{ id: 1, value: 4, max: 15, min: 0, show: true },
{ id: 2, value: 0 }`enter code here`
]
};
handleIncrement = counter => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
if (counters[index].value < counters[index].max) {
counters[index].value++;
}
this.setState({ counters });
};
handleDecrement = counter => {
const counters = [...this.state.counters];
const index = counters.indexOf(counter);
if (counters[index].value > counters[index].min) {
counters[index].value--;
}
this.setState({ counters });
};
handleDelete = counterId => {
const counters = this.state.counters.filter(c => c.id !== counterId);
this.setState({ counters });
};
render() {
return (
<div>
{this.state.counters.map(counter => (
<Counter
key={counter.id}
onDelete={this.handleDelete}
onIncrement={this.handleIncrement}
onDecrement={this.handleDecrement}
counter={counter}
/>
))}
</div>
);
}
}
export default Counters;
enter code here

Related

Access to api response data

My code pass in a search term and the promise api call returns one record and the data format is as below:
` json api data
0:
{
id:"aff3b4fa-bdc0-47d1-947f-0163ff5bea06"
keyword: somekeyword
URL:"mypage.html"
}
I need to retrieve the URL value, so I try to get URL by using response.data[0].URL. But I receive the error "Unhandled Rejection (TypeError): response.data[0] is undefined". How do I get the URL value? Thanks.
` autocomplete.js
export class Autocomplete extends Component {
state = {
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: "",
suggestions: [],
results: [],
URL: "",
};
componentDidMount() {
this.GetPrograms();
const { userInput } = this.state;
//this.runSearch();
}
GetPrograms = () => {
axios
.get("https://mydomain/GetPrograms/")
.then((response) => {
this.setState({ suggestions: response.data });
});
};
runSearch = async () => {
const response = await axios.get(
"https://mydomain/api/get",
{
params: {
searchTerm: this.state.userInput,
},
}
);
let results = response.data;
console.log("response", results);
this.setState({ results: results, URL: response.data[0].URL });
window.location.href =
"https://mydomain/" + this.state.URL;
};
onChange = (e) => {
const { suggestions } = this.state; //this.props;
const userInput = e.currentTarget.value;
const filteredSuggestions = suggestions.filter(
(suggestion) =>
suggestion.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({
activeSuggestion: 0,
filteredSuggestions,
showSuggestions: true,
userInput: e.currentTarget.value,
});
};
onClick = (e) => {
this.setState({
activeSuggestion: 0,
filteredSuggestions: [],
showSuggestions: false,
userInput: e.currentTarget.innerText,
});
this.onSearch();
console.log(
"child component clicked and value=" + e.currentTarget.innerText
);
};
onKeyDown = (e) => {
const { activeSuggestion, filteredSuggestions } = this.state;
if (e.keyCode === 13) {
this.setState({
activeSuggestion: 0,
showSuggestions: false,
userInput: filteredSuggestions[activeSuggestion],
});
} else if (e.keyCode === 38) {
if (activeSuggestion === 0) {
return;
}
this.setState({ activeSuggestion: activeSuggestion - 1 });
} else if (e.keyCode === 40) {
if (activeSuggestion - 1 === filteredSuggestions.length) {
return;
}
this.setState({ activeSuggestion: activeSuggestion + 1 });
}
//this.setState({ searchTerm: e.currentTarget.value });
console.log("userinput:" + this.state.userInput);
};
render() {
const {
onChange,
onClick,
onKeyDown,
onKeyPress,
state: {
activeSuggestion,
filteredSuggestions,
showSuggestions,
userInput,
},
} = this;
let suggestionsListComponent;
if (showSuggestions && userInput) {
if (filteredSuggestions.length) {
suggestionsListComponent = (
<ul class="suggestions">
{filteredSuggestions.map((suggestion, index) => {
let className;
if (index === activeSuggestion) {
className = "";
}
return (
<li key={suggestion} onClick={onClick}>
{suggestion}
</li>
);
})}
</ul>
);
} else {
suggestionsListComponent = (
<div class="no-suggestions">
<em>No suggestions</em>
</div>
);
}
}
return (
<div>
<input
id="search-box"
placeholder="Search..."
type="search"
onChange={onChange}
onKeyDown={onKeyDown}
value={userInput}
/>
{suggestionsListComponent}
</div>
);
}
}
export default Autocomplete;
`
The api call returns 0 record that causes the error.

Edit list item in place (React)

based on simple To-Do app, I'd like to understand how I can modify text of list item in place. List item contains 2 divs, first one holds todo's text and second one contains icons (delete, edit). I tried to conditionally render either first div or input inside the li, file ListItem.js, but that didn't work for me.
App.js
class App extends React.Component {
state = {
items: [],
currentValue: '',
clearValue: false
};
submitFormHandler = event => {
event.preventDefault();
if (this.state.currentValue === '') return;
const updatedItems = [...this.state.items];
if (
updatedItems.filter(udtItem => udtItem.value === this.state.currentValue)
.length === 0
) {
updatedItems.push({
id: uuidv4(),
value: this.state.currentValue,
completed: false
});
}
this.setState({ items: updatedItems, clearValue: true });
localStorage.setItem('todos', JSON.stringify(updatedItems));
};
changeInputHandler = event => {
this.setState({
currentValue: event.target.value,
clearValue: false
});
};
deleteItem = id => {
const updatedItems = [...this.state.items].filter(item => item.id !== id);
this.setState({ items: updatedItems });
localStorage.setItem('todos', JSON.stringify(updatedItems));
};
editItem = (event, id) => {
event.stopPropagation();
//do something here
};
deleteAll = () => {
this.setState({ items: [] });
localStorage.removeItem('todos');
};
componentDidMount() {
let todos = localStorage.getItem('todos');
if (todos) {
this.setState({ items: JSON.parse(todos) });
}
}
render() {
const itemList = this.state.items.map(item => (
<ListItem
key={item.id}
data={item}
deleted={this.deleteItem}
edited={this.editItem}
></ListItem>
));
return (
<div className="App">
<img src={trashIcon} alt="Delete" onClick={this.deleteAll} />
<header className="header">To-Do List</header>
<div className="items">
<ul>{itemList}</ul>
</div>
<form onSubmit={this.submitFormHandler}>
<Input
val={this.state.currentValue}
changed={e => this.changeInputHandler(e)}
clear={this.state.clearValue}
/>
</form>
</div>
);
}
}
export default App;
ListItem.js
class ListItem extends Component {
state = {
crossCheck: false,
hidden: true
};
toggleCrossCheck = () => {
const storageItems = JSON.parse(localStorage.getItem('todos'));
storageItems.forEach(item => {
if (item.id === this.props.data.id) {
item.completed = !item.completed;
this.setState({ crossCheck: item.completed });
}
});
localStorage.setItem('todos', JSON.stringify(storageItems));
};
componentDidMount() {
this.setState({ crossCheck: this.props.data.completed });
}
render() {
let classList = 'icon-container';
if (!this.state.hidden) classList = 'icon-container open';
return (
<li
className={this.state.crossCheck ? 'item cross-check' : 'item'}
onClick={this.toggleCrossCheck}
onMouseEnter={() => this.setState({ hidden: false })}
onMouseLeave={() => this.setState({ hidden: true })}
>
<div className="item-text">{this.props.data.value}</div>
<div className={classList}>
<Icon
iconType={trashIcon}
altText="Delete"
clicked={() => this.props.deleted(this.props.data.id)}
></Icon>
<Icon
iconType={editIcon}
altText="Edit"
clicked={event => this.props.edited(event, this.props.data.id)}
></Icon>
</div>
</li>
);
}
}
export default ListItem;

Action not updating reducer when invoked

I cannot get my reducer to update. I can step into the action when I fire this.completeQuiz(id) in my debugger but my state doesn't get updated. Any ideas?
import {
submitAnswer,
resetQuiz,
nextQuestion,
completeQuiz
} from "../actions/videos";
class TestYourselfScreen extends React.Component {
constructor(props) {
super(props);
this.onCapture = this.onCapture.bind(this);
}
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
render() {
.....
return (
{questionsFiltered.length > 0 && !completed && (
<View
style={{
flex: 1
}}
>
....
<Button
title={lastQuestion ? "Finish" : "Next"}
buttonStyle={[styles.button]}
disabled={
!results.correctAnswers.includes(current) &&
!results.incorrectAnswers.includes(current)
? true
: false
}
onPress={() =>
lastQuestion ? this.completeQuiz(id) : this.next(id, current)
}
/>
</View>
)}
{completed === true && (
<View
style={{
flex: 1
}}
>
<ViewShot ref="viewShot" options={{ format: "jpg", quality: 0.9 }}>
...
</View>
)}
</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
videos: state.tcApp.videos
};
};
const mapDispatchToProps = dispatch => ({
submitAnswer: data => dispatch(submitAnswer(data)),
resetQuiz: id => dispatch(resetQuiz(id)),
nextQuestion: data => dispatch(nextQuestion(data)),
completeQuiz: data => dispatch(completeQuiz(data))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TestYourselfScreen);
Action:
export const completeQuiz = data => ({
type: "COMPLETE",
data
});
Reducer:
import { trimText } from "../helpers";
export function tcApp(
state = { videos: [], search: { videos: [], term: "" } },
action
) {
switch (action.type) {
....
case "COMPLETE": {
const { completed, totalScore, id } = action.data;
return {
videos: state.videos.map(video =>
video.id === id
? {
...video,
results: {
totalScore
},
completed: true
}
: video
),
search: { term: "", videos: [] }
};
}
default:
return state;
}
}
I think your action is available through props do it as this
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
this.props.completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
because we mapDispatchToProps
Hope it helps

Reactjs Search after onChange

I have implemented a little search functionality in my reactjs application.
The Problem is, that my "searchHandler" function is triggered after every single letter the user enters in the textfield... So e.g. for the term "Lorem" my function is fetching 5 times from my api :(
How can I solve this problem?
Here is my code:
const { scaleDown } = transitions;
function searchingFor(term){
return function(x){
return x.title.toLowerCase().includes(term.toLowerCase()) ||
x.body.toLowerCase().includes(term.toLowerCase());
}
}
class ViewAll extends React.Component{
constructor(props){
super(props);
this.state = {
term: '',
mounted: true,
tracks: [],
hasMoreItems: true,
page: 2,
}
this.searchHandler = this.searchHandler.bind(this);
this.focus = this.focus.bind(this);
this.keyPress = this.keyPress.bind(this);
}
loadContent() {
var requestUrl = this.props.url;
fetch(requestUrl + this.state.page + '&_limit=3').then((response)=>{
return response.json();
}) .then((tracks)=>{
this.setState({ tracks: this.state.tracks.concat(tracks)});
this.setState({page: this.state.page + 1});
if(this.state.page === 6){
this.setState({hasMoreItems: false})
}
}).catch((err)=>{
console.log("There has been an error");
});
}
componentDidMount() {
window.scrollTo(0, 0);
var requestUrl = this.props.url;
fetch(requestUrl + '1&_limit=3')
.then((response)=>{
return response.json();
}) .then((data)=>{
this.setState({tracks : data});
})
.catch((err)=>{
console.log("There has been an error");
});
//this.focus();
}
searchHandler(event){
this.setState({term: event.target.value});
var requestUrl = 'https://questdb.herokuapp.com/all?q='
fetch(requestUrl + this.state.term).then((response)=>{
return response.json();
}) .then((tracks)=>{
this.setState({ tracks: this.state.tracks.concat(tracks)});
}).catch((err)=>{
console.log("There has been an error");
});
}
focus() {
this.textInput.focus();
}
keyPress(e){
if(e.keyCode == 13){
console.log('value', e.target.value);
// put the login here
}
}
render() {
const {term, data, tracks} = this.state;
const loader = <div className="loader2"> </div>;
var items = [];
const imageUrl = require(`../assets/Book.jpg`)
tracks.filter(searchingFor(term)).map(function(title, i)
{
items.push(
<div>
<MuiThemeProvider>
<Paper style={{ borderRadius: "2em",
background: '#ffffff'
}} zDepth={1} >
<ItemViewAll
key={title.id}
/>
</Paper>
</MuiThemeProvider>
</div>
);
}, this);
return (
<div>
<Fade in={true} timeout={1000}>
<div >
<MuiThemeProvider>
<TextField hintText='Bot suchen...'
type="Text"
onChange={this.searchHandler}
value={term}
underlineFocusStyle={{borderColor: '#B00020', borderWidth: 3}}
underlineStyle={{borderColor: '#B00020', borderWidth: 1.5, top: '45px'}}
hintStyle={{fontSize: '8.1vw', fontFamily: 'Anton', color: 'rgba(255,255,255,0.9)'}}
inputStyle={{fontSize: '8.1vw', fontFamily: 'Anton', color: '#ffffff'}}
ref={(input) => { this.textInput = input; }}
style={{caretColor: '#ffffff', width: '90%', maginLeft: 'auto', marginRight: 'auto', marginTop: '12%' }}
InputLabelProps={{ shrink: true }}
/>
</MuiThemeProvider>
</div>
</Fade>
<InfiniteScroll
pageStart={1}
loadMore={this.loadContent.bind(this)}
hasMore={this.state.hasMoreItems}
initialLoad={true}
>
{items}
</InfiniteScroll>
</div>
)
}
}
export default ViewAll;
Here you can check out the Website with the broken search function. As you can see the items are shown double or even triple... After the textfield is emptied, the search results should be removed and only the normal fetched ones should be shown.
https://www.genko.de (use the mobile version in chrome)
Thank you :)
Use lodash debounce. It is used for this exact use case
https://stackoverflow.com/questions/48046061/using-lodash-debounce-in-react-to-prevent-requesting-data-as-long-as-the-user-is
Sample:
import React, {Component} from 'react'
import { debounce } from 'lodash'
class TableSearch extends Component {
//********************************************/
constructor(props){
super(props)
this.state = {
value: props.value
}
this.changeSearch = debounce(this.props.changeSearch, 250)
}
//********************************************/
handleChange = (e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}
//********************************************/
render() {
return (
<input
onChange = {this.handleChange}
value = {this.props.value}
/>
)
}
//********************************************/
}
If you don't need full lodash package you can write it yourself:
function debounce(f, ms) {
let timer = null;
return function (...args) {
const onComplete = () => {
f.apply(this, args);
timer = null;
}
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(onComplete, ms);
};
}
The first arg (f) is your function which should not be performed more often than
second arg (ms) - amount of ms ). So in your case you can write your handler in next way:
handleChange = debounce((e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}, 1000) // second arg (1000) is your amount of ms

jest test case for input change and button in reactjs

I am very new to jest so i don't know how to proceed with jest. I have write test case using jest for input on change and button click for the below component. But it failed. issue with 'Method “props” is only meant to be run on a single node. 0 found instead.' please help me
code:
import React from 'react';
import { Col } from 'react-bootstrap';
class FITB extends React.Component {
constructor(props) {
super(props);
this.String = String;
this.defaultTextSize = 8;
this.state = {
inputList: [],
splitList: [],
count: 0,
isValid: false
};
this.onInputChange = this.onInputChange.bind(this);
this.onClickSend = this.onClickSend.bind(this);
this.checkInputValidations = this.checkInputValidations.bind(this);
}
componentDidMount() {
this.initialize();
}
onInputChange(index) {
return (event) => {
const inputList = this.state.inputList;
let isValid = true;
inputList[index] = event.target.value;
if (!this.isValidInput(inputList[index])) {
isValid = false;
}
this.setState({
inputList,
isValid
});
// console.log('onInputChange fib state', this.state);
};
}
onClickSend() {
const {
splitList,
inputList,
count
} = this.state;
// console.log('onClickSend fib before state', this.state);
const onReply = this.props.onReply;
let fullText = '';
splitList.map((text, index) => {
fullText += text;
if ((index < count - 1) && inputList[index]) {
fullText += inputList[index];
}
return true;
});
if (onReply) {
onReply(fullText);
}
// console.log('onClickSend fib after state', this.state);
}
isValidInput(text) {
const regex = /^[\u0020-\u007e]*$/;
const replaceChar160RegExp = new RegExp(this.String.fromCharCode(160), 'g');
return regex.test(text.replace(replaceChar160RegExp, ' '));
}
initialize() {
let text = '';
this.props.messages.map((element) => {
if (element.type && (typeof element.type === 'string') && (element.type === 'FILL_IN_THE_BLANK')) {
text = element.message;
}
// console.log('inside fib', text);
return text;
});
const splitList = text.split(/_+/g);
this.setState({
splitList,
count: splitList.length
});
// console.log('init fib state', this.state);
}
checkInputValidations() {
const {
inputList,
count,
isValid
} = this.state;
let i;
let flag = false;
for (i = 0; i < count - 1; i += 1) {
if (!inputList[i] || inputList[i].trim() === '') {
flag = true;
}
}
// console.log('checkInputValidations', this.state);
return flag || !isValid;
}
render() {
const {
splitList,
count,
inputList
} = this.state;
// console.log('reder fitb', this.state);
return (
<Col lg={12} className="rply-block">
<Col lg={11} className="textarea-block">
<div className="fitb-wrap">
{ splitList && splitList.map((item, index) => (
<span>
<span className="fitb-text">{item}</span>
{ (index < count - 1) && (
<input
className="fitb-input"
type="text"
maxLength="40"
size={(inputList[index] && inputList[index].length > this.defaultTextSize && inputList[index].length) || this.defaultTextSize}
value={inputList[index]}
onChange={this.onInputChange(index)}
autoFocus={index === 0}
aria-describedby={count > 1 ? `Missing word ${index + 1} of ${count - 1}` : 'Missing word'}
aria-label="Fill in missing words"
/>
)}
</span>
))}
</div>
</Col>
<Col lg={1} className="">
<button
className="btn-info-dm"
role="button"
tabIndex="0"
onClick={this.onClickSend}
disabled={this.checkInputValidations()}
>Send</button>
</Col>
</Col>
);
}
}
FITB.propTypes = {
onReply: React.PropTypes.isRequired,
messages: React.PropTypes.array
};
export default FITB;
test files for the input and button click
import React from 'react';
import FITB from '../components/dialogManager/fitb';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
describe('FITB', () => {
let component;
const mockFn = jest.fn();
beforeEach(() => {
component = shallow(<FITB onReply={mockFn} />);
});
test('Should initialize the FITB content', () => {
expect(component.find('.rply-block')).toHaveLength(1);
});
test('Should have been called send', () => {
component.find('.btn-info-dm').simulate('click');
expect(mockFn).toHaveBeenCalled();
});
test('Should render the text box', () => {
expect(component.state().inputList).toEqual([]);
expect(component.state().isValid).toEqual(false);
// expect(component.find('input.fitb-input')).toBeDefined();
console.log('state== ', component.state());
console.log('input == ', component.find('.fitb-input'));
component.find('.fitb-input').simulate('change', { target: { value: 'Qualitative data includes detailed interviews, direct _____, and historical records.' } });
console.log('fitb-input onchange== ', component.find('.fitb-input'));
expect(component.state().inputList).toEqual('Qualitative data includes detailed interviews, direct _____, and historical records.');
expect(component.state().isValid).toEqual(true);
});
// test('Should check the input label', () => {
// const expectedFitbTextLabel = 'Fill in missing words';
// const fitbTextList = [];
// console.log('span fitb txt== ', component.find('.fitb-text').instance().label);
// component.find('.fitb-text').map((elem) => {
// fitbTextList.push(elem.text().trim());
// });
// expect(component.find('.fitb-text').instance().label).toEqual(expectedFitbTextLabel);
// });
test('Should render the fitbText ', () => {
const expectedFitbText = 'Qualitative data includes detailed interviews, direct _____, and historical records.';
const fitbTextList = [];
console.log('span fitb txt== ', component.find('.fitb-text').text());
// fitbTextList.push(expectedFitbText.split(/_+/g));
// console.log('fitbTextList= ', fitbTextList);
component.find('.fitb-text').map((elem) => {
fitbTextList.push(elem.text().trim());
});
expect(fitbTextList).toEqual(expectedFitbText);
});
test('Should check the fitbText ', () => {
const expectedFitbText = 'Qualitative data includes detailed interviews, direct _____, and historical records.';
const fitbTextList = [];
fitbTextList.push(expectedFitbText.split(/_+/g));
expect(component.state().inputList).toEqual([]);
console.log('input list init== ', component.state().inputList);
component.find('input.fitb-input').simulate('change', { target: { value: 'test' } });
console.log('input list== ', component.state().inputList);
// component.find('input').instance().onInputChange(0);
// expect(component.state().inputList).toEqual('test');
});
});
for the another component on button click to send the details
import React from 'react';
class ReplyButtons extends React.Component {
constructor(props) {
super(props);
this.replyBtnWrap = '';
this.replyBtnList = '';
this.state = {
list: [],
isLeftArrowEnabled: false,
isRightArrowEnabled: false
};
this.onClickLeftArrow = this.onClickLeftArrow.bind(this);
this.onClickRightArrow = this.onClickRightArrow.bind(this);
this.onClickReplyBtn = this.onClickReplyBtn.bind(this);
}
componentDidMount() {
this.initializeList();
}
componentDidUpdate() {
this.checkElementOffset();
}
onClickRightArrow() {
const wrapElement = this.replyBtnWrap;
const listElement = this.replyBtnList;
const wrapWidth = wrapElement.offsetWidth;
const listWidth = listElement.offsetWidth;
let listLeft = wrapElement.scrollLeft;
let listOverflowWidth = 0;
listLeft += 400;
listOverflowWidth = listWidth - listLeft;
if (listOverflowWidth < 0) {
listLeft = listWidth - wrapWidth;
}
wrapElement.scrollLeft = listLeft;
this.checkElementOffset();
}
onClickLeftArrow() {
const wrapElement = this.replyBtnWrap;
let listLeft = wrapElement.scrollLeft;
listLeft -= 400;
if (listLeft < 0) {
listLeft = 0;
}
wrapElement.scrollLeft = listLeft;
this.checkElementOffset();
}
onClickReplyBtn(item) {
return () => {
const onReply = this.props.onReply;
if (onReply) {
onReply(item);
}
};
}
checkElementOffset() {
const wrapElement = this.replyBtnWrap;
const listElement = this.replyBtnList;
const wrapWidth = wrapElement.offsetWidth;
const listWidth = listElement.offsetWidth;
const listLeft = wrapElement.scrollLeft;
let listOverflowWidth = 0;
let isLeftArrowEnabled = false;
let isRightArrowEnabled = false;
if (listLeft > 0) {
isLeftArrowEnabled = true;
}
listOverflowWidth = listWidth - listLeft - wrapWidth;
if (listOverflowWidth > 0) {
isRightArrowEnabled = true;
}
if (this.state.isLeftArrowEnabled !== isLeftArrowEnabled || this.state.isRightArrowEnabled !== isRightArrowEnabled) {
this.setState({
isLeftArrowEnabled,
isRightArrowEnabled
});
}
}
initializeList() {
// this.setState({
// list: [{
// type: 'MENU_ITEM',
// text: 'what is quantitative research?',
// return_value: 'what is quantitative research?'
// }, {
// type: 'MENU_ITEM',
// text: 'what is mixed method research?',
// return_value: 'what is mixed method research?'
// }, {
// type: 'MENU_ITEM',
// text: 'what is qualitative research?',
// return_value: 'what is qualitative research?'
// }, {
// type: 'MENU_ITEM',
// text: 'I had a different question',
// return_value: 'I had a different question'
// }, {
// type: 'MENU_ITEM',
// text: 'That was actually my answer',
// return_value: 'That was actually my answer'
// }]
// });
const replyButtonText = [];
// console.log('reply btns props = ', this.props);
if (this.props.messages) {
this.props.messages.map((element) => {
if (element.type && (typeof element.type === 'string') && (element.type === 'MENU_ITEM')) {
replyButtonText.push(element);
}
return this.setState({ list: replyButtonText });
});
}
}
render() {
const btnList = this.state.list;
const {
isLeftArrowEnabled,
isRightArrowEnabled
} = this.state;
return (
<div className="r-wrap">
{ isLeftArrowEnabled && (
<button className="r-btn-left-arrow" onClick={this.onClickLeftArrow} role="button" tabIndex="0">
<i className="glyphicon glyphicon-menu-left" />
</button>
)}
<div className="r-btn-wrap" ref={(e) => { this.replyBtnWrap = e; }}>
<div className="r-btn-list" ref={(e) => { this.replyBtnList = e; }}>
{
btnList && btnList.map(btnItem => <button
className="r-btn"
role="button"
tabIndex="0"
onClick={this.onClickReplyBtn(btnItem)}
title={btnItem.text}
>{btnItem.text}</button>)
}
</div>
</div>
{ isRightArrowEnabled && (
<button className="r-btn-right-arrow" onClick={this.onClickRightArrow} role="button" tabIndex="0">
<i className="glyphicon glyphicon-menu-right" />
</button>
)}
</div>
);
}
}
ReplyButtons.propTypes = {
onReply: React.PropTypes.isRequired,
messages: React.PropTypes.array
};
export default ReplyButtons;
test file:
import React from 'react';
import ReplyButtons from '../components/dialogManager/replybuttons';
import { shallow } from 'enzyme';
import renderer from 'react-test-renderer';
const mockOutputObj = [{
type: 'MENU_ITEM',
text: 'what is quantitative research?',
return_value: 'what is quantitative research?'
}, {
type: 'MENU_ITEM',
text: 'what is mixed method research?',
return_value: 'what is mixed method research?'
}];
describe('ReplyButtons', () => {
let component;
const mockFn = jest.fn();
beforeEach(() => {
component = shallow(<ReplyButtons onReply={mockFn} />);
});
test('Should initialize the ReplyButtons content', () => {
expect(component.find('.r-wrap')).toHaveLength(1);
});
test('Should check the ReplyButtons click', () => {
console.log('r-btn==> ', component.find('button.r-btn'));
component.find('.r-btn').simulate('click');
expect(mockFn).toHaveBeenCalled();
});
});
Please help me.
I think the error happens because the selector in the last test case
component.find('button.r-btn')
refers to buttons that are build by iterating on this.state.list in the ReplyButtons component
AND you do not provide the prop 'messages' that would trigger a feeding to the list
try instanciating ReplyButtons with some messages like
beforeEach(() => {
component = shallow(<ReplyButtons onReply={mockFn} messages={['1','2']}/>);
});
and it should be ok (btw not having the error stack is pretty unconfortable here, there might be other issues)

Resources