unit testing a react component with mocha - javascript

I'm working through a TodoMVC example for the Redux ecosystem. I've completed working code for the example and am now working through the creation of tests for each of the elements of the application.
For actions and reducers, the testing is very straightforward, but for the components, writing tests has proven somewhat more challenging.
My general component architecture looks like this:
Home.js
\-App.js
\-TodoList.js
\-TodoItem.js
\-TodoInput.js
Writing the unit tests for TodoInput.js has been relatively straightforward:
TodoInput.js:
handleChange(e) {
this.setState({ text: e.target.value });
}
...
render() {
return (
<input type="text" autoFocus='true'
className={classnames({
edit: this.props.editing,
'new-todo': this.props.newTodo
})}
value={this.state.text}
placeholder={this.props.placeholder}
onKeyDown={this.handleKeyDown.bind(this)}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleChange.bind(this)}>
</input>
);
}
TodoInput-test.js:
const mockedTodo = {
text: 'abc123',
complete: false
};
it(`should update text from user input`, () => {
const component = TestUtils.renderIntoDocument(
<TodoInput
text = {mockedTodo.text}
editing = {false}
onSave = {_.noop}
/>
);
const inputComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(React.findDOMNode(inputComponent).value).toBe(mockedTodo.text);
TestUtils.Simulate.change(React.findDOMNode(inputComponent), {target: {value: "newValue"}});
expect(React.findDOMNode(inputComponent).value).toBe("newValue");
React.unmountComponentAtNode(React.findDOMNode(component));
});
But for TodoItem.js, testing has been a little trickier.
The render code branches based on whether or not an editing flag has been set on the item:
TodoItem.js:
import React, { Component, PropTypes } from 'react';
import TodoInput from './TodoInput';
import classnames from 'classnames';
export default class TodoItem extends Component {
static propTypes = {
todo: PropTypes.object.isRequired,
editTodo: PropTypes.func.isRequired,
markTodoAsComplete: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired
}
constructor(props, context) {
super(props, context);
this.state = {
editing: false
};
}
handleDoubleClick() {
this.setState({ editing: true });
}
handleSave(id, text) {
if (text.length === 0) {
this.props.deleteTodo(id);
} else {
this.props.editTodo(id, text);
}
this.setState({ editing: false });
}
render() {
const {todo, markTodoAsComplete, deleteTodo} = this.props;
let element;
if (this.state.editing) {
element = (
<TodoInput text={todo.text}
editing={this.state.editing}
onSave={(text) => this.handleSave(todo.id, text)} />
);
} else {
element = (
<div className='view'>
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
{todo.text}
</label>
<input className='markComplete'
type='checkbox'
checked={todo.complete}
onChange={() => markTodoAsComplete(todo)} />
<button className='destroy'
onClick={() => deleteTodo(todo)} />
</div>
);
}
return (
<li className={classnames({
completed: todo.complete,
editing: this.state.editing
})}>
{element}
</li>
)
}
}
I'm a little stumped on how to go about writing a test that, for instance, would verify that a double-click on the component had successfully set the state to editing: true.
Typically, I have my tests divided into two parts, "rendering" and "events", i.e. for TodoItem-test.js:
import React, { addons } from 'react/addons';
import _ from 'lodash';
import expect from 'expect';
const { TestUtils } = addons;
import TodoItem from '../TodoItem';
describe('TodoItem', () => {
const mockedTodo = {
text: 'abc123',
complete: false
};
describe('rendering', () => {
let component;
before(() => {
component = TestUtils.renderIntoDocument(
<TodoItem
todo={mockedTodo}
editTodo={_.noop}
markTodoAsComplete={_.noop}
deleteTodo={_.noop}
/>
);
});
afterEach(() => {
React.unmountComponentAtNode(React.findDOMNode(component));
});
it('should render the element', () => {
const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
expect(liComponent).toExist();
});
it('should render text in label', () => {
const labelComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'label');
expect(labelComponent).toExist();
expect(React.findDOMNode(labelComponent).textContent).toEqual('abc123');
});
});
describe('events', () => {
...
});
but in this case, I want to see if double-clicking on the component leads to the following:
the component state should now have an editing flag associated with it
the element should have changed, and TodoItem.js should now render a <TodoInput/> component instead.
What is the most efficient way to structure a test against this expected behavior? I am thinking that I should do two things:
First, test to see if a double-click on the component adds the expected "editing: true" flag. I am not sure how to do this. If I set up a test as follows:
describe('events', () => {
let component;
let deleteTodoCallback = sinon.stub();
beforeEach(() => {
component = TestUtils.renderIntoDocument(
<TodoItem
todo={mockedTodo}
editTodo={_.noop}
markTodoAsComplete={_.noop}
deleteTodo={deleteTodoCallback}
/>
);
});
afterEach(() => {
React.unmountComponentAtNode(React.findDOMNode(component));
});
it(`should change the editing state to be true if a user double-clicks
on the todo`, () => {
const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
// expect the editing flag to be false
TestUtils.Simulate.doubleClick(React.findDOMNode(liComponent));
// expect the editing flag to be true
});
});
how do I go about testing to ensure that the editing flag has been set? liComponent.props.editing returns undefined.
Second, have a context("if the component is editing mode") that tests to make sure that the following has been rendered correctly:
<li className={classnames({
completed: todo.complete,
editing: this.state.editing
})}>
<TodoInput text={todo.text}
editing={this.state.editing}
onSave={(text) => this.handleSave(todo.id, text)} />
</li>
I'm also not sure how I would go about testing this rigorously as well.

liComponent.props is undenfined because liComponent is a DOM element, not a react component. That's the case because you're fetching it with findRenderedDOMComponentWithTag. You actually already have access to the React component you're trying to test against.
it('should render the element', () => {
const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
// `component` is from your `before` function
expect(component.state.editing).toBe(false);
// Make sure you're simulating on a DOM element
TestUtils.Simulate.doubleClick(liComponent);
expect(component.state.editing).toBe(true);
});
You can then use scryRenderedComponentsWithType to check whether a TodoInput is rendered.

Related

Why does Enzyme's `find()` incorrectly return elements that don't exist?

Below I have an example React component that behaves as follows -
When the showDiv state is false (initial state), it renders nothing.
When the showDiv state is updated to true (maybe by some external component, redux, etc..), it renders a <div> with a single radio input.
I've also included Jest tests:
import { expect } from 'chai';
import { mount } from 'enzyme';
import PropTypes from 'prop-types';
import React from 'react';
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { showDiv: false };
}
render() {
const { showDiv } = this.state;
if (!showDiv) { return null; }
return (
<div className='my-component'>
<input type="radio" value="yes" id="foo" checked={true} />
</div>
);
}
}
describe('<MyComponent />', () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<MyComponent />);
});
it('should render the div', () => {
// This INCORRECTLY passes
expect(wrapper.find('#foo').at(0)).to.not.be.null;
expect(wrapper.find('#foo').at(0)).to.be.checked;
});
});
I wrote the test to verify that the <input> exists and that it is checked, and the test passes.
However, on initial component mount/load that <input> should not be rendered (since showDiv is false), so why is the test passing?
I even output the state of the component with console.log(wrapper.find('#foo').at(0).debug()); and I can verify that nothing is being rendered... but it still passes.
$ npx jest --no-cache --config test/js/jest.config.js test/js/components/test.spec.jsx
● Console
console.log test/js/components/test.spec.jsx:34
Any reason why this gives a false positive? Specifically, why the find() returns elements that don't exist, but still pass the checks (e.g. .to.be.checked)
I've personally never worked with chai, but it may require some additional setup for React to work (I know jest with enzyme requires an adapter). On the same note, I'd personally recommend just using Jest over chai since it's already wrapped over enzyme... but ultimately up to your preference which testing suite to use.
Side note: A popular add-on for jest is jest-enzyme, which has assertion methods like toExist() and toBeChecked().
Working demo using Jest (switch from the Browser tab to the Tests tab to run assertions):
App.test.js
import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import MyComponent from "./App";
configure({ adapter: new Adapter() });
describe("<MyComponent />", () => {
let wrapper;
beforeEach(() => {
wrapper = mount(<MyComponent />);
});
it("initially displays a button", () => {
expect(wrapper.find("#show-radio")).toHaveLength(1);
});
it("renders the 'my-component' div", () => {
wrapper.find("#show-radio").simulate("click");
expect(wrapper.find(".my-component")).toHaveLength(1);
expect(wrapper.find("#foo").props().checked).toBeFalsy();
});
it("selects a radio", () => {
wrapper.find("#show-radio").simulate("click");
wrapper.find("#foo").prop("onChange")({ target: { value: "1" } });
wrapper.update();
expect(wrapper.find("#foo").props().checked).toBeTruthy();
});
it("resets radio value", () => {
wrapper.find("#show-radio").simulate("click");
wrapper.find("#foo").prop("onChange")({ target: { value: "1" } });
wrapper.find("#reset").simulate("click");
wrapper.update();
expect(wrapper.find("#foo").props().checked).toBeFalsy();
});
it("hides radios", () => {
wrapper.find("#show-radio").simulate("click");
wrapper.find("#close-radio").simulate("click");
wrapper.update();
expect(wrapper.find("#show-radio")).toHaveLength(1);
expect(wrapper.find(".my-component")).toHaveLength(0);
});
});
App.js
import React from "react";
import "./styles.css";
export default class MyComponent extends React.Component {
state = { showDiv: false, value: 0 };
setValue = ({ target: { value } }) => this.setState({ value });
resetValue = () => this.setState({ value: 0 });
toggleRadio = () =>
this.setState((prevState) => ({ showDiv: !prevState.showDiv }));
render() {
const { showDiv, value } = this.state;
if (!showDiv) {
return (
<button id="show-radio" type="button" onClick={this.toggleRadio}>
Toggle
</button>
);
}
return (
<div className="my-component">
<label htmlFor="foo">
<input
type="radio"
id="foo"
value="1"
checked={value === "1"}
onChange={this.setValue}
/>
1
</label>
<label htmlFor="bar">
<input
type="radio"
id="bar"
value="2"
checked={value === "2"}
onChange={this.setValue}
/>
2
</label>
<br />
<button id="reset" type="button" onClick={this.resetValue}>
Reset
</button>
<button id="close-radio" type="button" onClick={this.toggleRadio}>
Close
</button>
</div>
);
}
}

Get consolidated data from all the child components in the form of an object inside a parent component : React JS

I am implementing a setting page for an application. For each setting I have implemented a slider that has enabled(green) or disabled(red) state. But parent's settings is read only and is calculated based on the values of its children.
Parent's setting is derived as follows: If all children are red, parent stays red ; If all are green parent stays green; If at-least one of child is green then parent stays grey(Pending).
These settings are grouped something like this:
Parent Feature 1 : (read-only-toggle)
Setting 1 (Toggle)
Setting 2 (Toggle)
Parent Feature 2: (read-only-toggle)
Setting 1 (Toggle)
Setting 2 (Toggle)
And in the end there is also a button, that gives me a consolidated values of all parent and children. But so far I was able to do only with one parent and 2 children.
Can someone help with an approach of getting consolidated values of all the settings in one place(Like a super parent component where all these settings are configured).
For this , I am using react-multi-toggle for this toggle switch.
Help would be really appreciated.
Code Sandbox: https://codesandbox.io/s/react-multi-toggle-solution-perfect-v9bi5
App
import React from "react";
import ChildSwitch from "./ChildSwitch";
import ParentSwitch from "./ParentSwitch";
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
parentVal: "disabled",
switch1Val: "enabled",
switch2Val: "disabled"
};
}
componentDidMount() {
this.setParentSwitchValue();
}
onGetChildSwitchValues = () => {
console.log(this.state);
};
setChildSwitchValue = (whichSwitch, selected) => {
this.setState(
prevState => ({ ...prevState, [whichSwitch]: selected }),
this.setParentSwitchValue
);
};
setParentSwitchValue = () => {
const { switch1Val, switch2Val } = this.state;
const switchStates = [switch1Val, switch2Val];
let parent = "pending";
if (switchStates.every(val => val === "enabled")) {
parent = "enabled";
}
if (switchStates.every(val => val === "disabled")) {
parent = "disabled";
}
this.setState(prevState => ({ ...prevState, parentVal: parent }));
};
render() {
const { parentVal, switch1Val, switch2Val } = this.state;
return (
<>
<div className="boxed">
Parent Setting 1 :{" "}
<ParentSwitch
parentSwitch={parentVal}
onSelect={this.setParentSwitchValue}
/>
Setting 1:
<ChildSwitch
switchName={"switch1Val"}
selected={switch1Val}
onSelect={this.setChildSwitchValue}
/>
Setting 2:
<ChildSwitch
switchName={"switch2Val"}
selected={switch2Val}
onSelect={this.setChildSwitchValue}
/>
</div>
<button onClick={this.onGetChildSwitchValues}>Get All Values</button>
</>
);
}
}
ChildSetting
import MultiToggle from "react-multi-toggle";
import React from "react";
export default class ChildSwitch extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
displayName: "Disabled",
value: "disabled"
},
{
displayName: "Enabled",
value: "enabled"
}
]
};
}
onSelectOption = selected => {
this.props.onSelect(this.props.switchName, selected);
};
render() {
const { options } = this.state;
const { selected } = this.props;
return (
<MultiToggle
options={options}
selectedOption={selected}
onSelectOption={this.onSelectOption}
/>
);
}
}
Parent Setting
import MultiToggle from "react-multi-toggle";
import React from "react";
import "react-multi-toggle/style.css";
export default class ParentSwitch extends React.Component {
constructor(props) {
super(props);
this.state = {
options: [
{
displayName: "Disabled",
value: "disabled"
},
{
displayName: "Pending",
value: "pending"
},
{
displayName: "Enabled",
value: "enabled"
}
]
};
}
render() {
const { options } = this.state;
return (
<MultiToggle
options={options}
selectedOption={this.props.parentSwitch}
onSelectOption={() => {}}
/>
);
}
}
I will suggest that you group your child and parent under one component. Let say we name it Settings. Then, we create another component that will render a list of Settings and a button. This last component will hold the values of all Settings. Finally, each time the value of a Setting Component Change, we update the list. Checkout a sample working app here.
App Component
export default class App extends PureComponent {
state = {};
onSettingChange = (settingId, setting) => {
this.setState(prevState => ({
...prevState,
[settingId]: setting
}));
};
onGetSettingValues = () => {
console.log(this.state);
};
render() {
return (
<Fragment>
<Setting id="setting1" onChange={this.onSettingChange} />
<Setting id="setting2" onChange={this.onSettingChange} />
<button onClick={this.onGetSettingValues}>Get All Values</button>
</Fragment>
);
}
}
Setting Component
import React, { PureComponent, Fragment } from "react";
import ChildSwitch from "./ChildSwitch";
import ParentSwitch from "./ParentSwitch";
export default class Setting extends PureComponent {
state = {
parentVal: "disabled",
switch1Val: "enabled",
switch2Val: "disabled"
};
componentDidMount() {
this.setParentSwitchValue();
}
setChildSwitchValue = (whichSwitch, selected) => {
this.setState(
prevState => ({ ...prevState, [whichSwitch]: selected }),
this.setParentSwitchValue
);
};
handleChange = () => {
const { id, onChange } = this.props;
onChange(id, this.state);
};
setParentSwitchValue = () => {
const { switch1Val, switch2Val } = this.state;
const switchStates = [switch1Val, switch2Val];
let parent = "pending";
if (switchStates.every(val => val === "enabled")) {
parent = "enabled";
}
if (switchStates.every(val => val === "disabled")) {
parent = "disabled";
}
this.setState(
prevState => ({ ...prevState, parentVal: parent }),
this.handleChange
);
};
render() {
const { parentVal, switch1Val, switch2Val } = this.state;
return (
<Fragment>
<div className="boxed">
Parent Setting 1
<ParentSwitch
parentSwitch={parentVal}
onSelect={this.setParentSwitchValue}
/>
Setting 1:
<ChildSwitch
switchName={"switch1Val"}
selected={switch1Val}
onSelect={this.setChildSwitchValue}
/>
Setting 2:
<ChildSwitch
switchName={"switch2Val"}
selected={switch2Val}
onSelect={this.setChildSwitchValue}
/>
</div>
</Fragment>
);
}
}
Put all your states into a single context hook.
const SettingsContext = createContext({state1, state2/* all your states in here*/);
You'll then wrap the whole thing into this context as such:
<SettingsContext.Provider>
<App/>
</SettingsContext.Provider>
Now you can access the state in any of the children, parents etc. I suggest however not storing things like "disabled", "enabled" as strings, but rather store states as { enabled: true, pending: false}

React State Storing & Outputting Duplicate Values

Slight issue here which I think is relatively simple to solve but I can't quite get my head around. I'm quite new to React. I've decided to make a small sample app which just takes the input from two fields, saves them to Firebase and outputs those values on the page. It works completely fine in terms of submitting data and retrieving it, but when I click the submit button to add the data to Firebase it seems to duplicate the data stored in the state and render them twice:
Parent Component:
import React, { Component, Fragment } from 'react';
import firebase from '../../config/firebase';
import QuestFormField from './QuestFormField/QuestFormField';
import QuestFormSelection from './QuestFormSelection/QuestFormSelection';
import classes from './QuestForm.css';
class QuestForm extends Component {
state = {
value: '',
points: 0,
items: []
}
questHandler = e => {
this.setState({
value: e.target.value,
});
}
pointsHandler = e => {
this.setState({
points: e.target.value,
});
}
submitHandler = e => {
e.preventDefault();
const itemsRef = firebase.database().ref('quest');
const items = {
quest: this.state.value,
points: this.state.points
}
itemsRef.push(items);
this.setState({
value: '',
points: 0
});
}
render () {
return (
<Fragment>
<form className={classes.Form} onSubmit={this.submitHandler}>
<QuestFormField val='Quest' inputType='text' name='quest' value={this.state.value} changed={this.questHandler} />
<QuestFormField val='Points' inputType='number' name='points' value={this.state.points} changed={this.pointsHandler} />
<button>Away! To Firebase!</button>
</form>
<QuestFormSelection />
</Fragment>
);
}
}
export default QuestForm;
Child Component (Form Fields)
import React from 'react';
import classes from './QuestFormField.css';
const QuestFormField = (props) => (
<div className={classes.Container}>
<label htmlFor={props.name}>{props.val}</label>
<input type={props.inputType} name={props.name} onChange={props.changed}/>
</div>
);
export default QuestFormField;
Child Component B (Data Retriever/Displayer)
import React, { Component, Fragment } from 'react';
import firebase from '../../../config/firebase';
import classes from './QuestFormSelection.css';
class QuestFormSelection extends Component {
state = {
quests: []
}
componentDidMount() {
const database = firebase.database();
const quests = [];
database.ref('quest').on('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
quests.push({
id: childSnapshot.key,
quest: childSnapshot.val().quest,
points: childSnapshot.val().points,
});
});
console.log(quests);
this.setState(() => {
return {
quests: quests
}
});
console.log(this.state.quests);
});
}
render () {
return (
<section className='display-item'>
<div className="wrapper">
{this.state.quests.map(quest => (
<div key={quest.key}>
<p>{quest.quest}</p>
<p>{quest.points}</p>
</div>
))}
</div>
</section>
)
}
}
export default QuestFormSelection;
Example of behaviour here:
https://i.gyazo.com/c70972f8b260838b1673d360d1bec9cc.mp4
Any pointers would help :)
I haven't used firebase myself, but it looks like the code below is setting up a listener to "quest" changes which will execute each time a change occurs, but you defined const quests = [] outside of the db change handler. This means that on the second change, you will push everything in the snapshot to the same quests array that may have already had previous snapshots added to it. I believe you can fix this by moving the quests variable inside the listener function as shown below.
componentDidMount() {
const database = firebase.database();
database.ref('quest').on('value', (snapshot) => {
const quests = [];
snapshot.forEach((childSnapshot) => {
quests.push({
id: childSnapshot.key,
quest: childSnapshot.val().quest,
points: childSnapshot.val().points,
});
});
console.log(quests);
this.setState(() => {
return {
quests: quests
}
});
console.log(this.state.quests);
});
}

How to add to state array in React

I am making a simple to-do list app in React. I have 3 states, inputText (the task the user enters), triggerAnimation(to trigger animations), and tasks (the list of tasks user has entered). However I don't know how to update the tasks state (which is an array) to push the new tasks. Here is the code.
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
}
//The function triggered by button which sends the task the user has entered to the tasks array state:
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein', tasks:
this.state.inputText
})
}
render() {
//Where User enters task:
return (
<div className="App">
<main>
<div className="enterTask">
<input type="text" className="inputclass" id="textfield1"
placeholder='Enter a task.'
onChange={event => this.setState({
inputText: event.target.value })}
onKeyPress={event => {
if(event.key === 'Enter') {
this.addItem();
}
}}
/>
<br />
<br />
<button className="button"
onClick={() => this.addItem()} data-
toggle='fadein' data-target='list'>+
</button>
</div>
<!-- Where tasks will appear: -->
<div className="log">
<p className='list'>
<span class={this.state.triggerAnimation}>
{this.state.tasks}
</span>
</p>
<button className="button">-</button>
</div>
</main>
</div>
)
}
}
export default App;
However I don't know how to update the tasks state (which is an array) to push the new tasks.
Probably the cleanest way to "push to an array" in state is to use ES6 array spread. The best practice would also be to use the setState callback syntax to ensure the correct state is committed before you push the new task:
this.setState(prevState => ({
tasks: [...prevState.tasks, newTask]
}));
Seems like what you want is this..
addItem() {
document.querySelector("#textfield1").value = ""
this.setState({
triggerAnimation: 'fadein',
tasks: this.state.tasks.concat(this.state.inputText)})
}
You can use .concat method to create copy of your array with new data:
addTask() {
this.setState({tasks: this.state.tasks.concat(["new value"])})
}
You also need to bind this to addTask in your constructor:
this.addTask = this.addTask.bind(this)
See my example:
https://jsfiddle.net/69z2wepo/103069/
Documentation: https://reactjs.org/docs/react-component.html#setstate
try this
import React from 'react';
class Todo extends React.Component {
constructor(props) {
super();
this.state = {
value: '',
items: []
}
}
onChange = e => this.setState({ value: e.target.value })
onEnter = e => {
if(e.charCode !== 13) return;
this.addItem();
};
onClick = e => {
this.addItem()
};
addItem = () => {
const { value } = this.state;
if(!!value.trim()) return;
this.setState(prev => ({ items: [...prev.items, value], value: '' }))
};
render() {
const { value } = this.state
return (
<div>
<div>
<input
type="text"
value={value}
name="abc"
onChange={this.onChange}
onKeyPress={this.onEnter}
/>
</div>
<button onClick={this.onClick}>Add</button>
</div>
)
}
}
FTFY better to just use comments in the code, regarding the problem(s) you want to get the tasks array then can concat the stuff to get a new array.
setState({tasks:this.state.tasks.concat([this.state.inputText])})
Wouldn't hurt to clean up the code some too... learning react myself the book "the road to learning react" has some good tips on how to set things up to be a bit more readable.
Edit actually put the right code here now...
With react, you're almost always going to have to store form field information in state (controlled components) so, how about turning todo task input field into a controlled component, like so:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '',
triggerAnimation: '',
tasks: []
}
this.onInputChange = this.onInputChange.bind(this);
this.onInputKeyPress = this.onInputKeyPress.bind(this);
this.addItem = this.addItem.bind(this);
}
onInputChange(e) {
this.setState({ inputText: e.target.value });
}
onInputKeyPress(e) {
if (e.key === "Enter") {
this.addItem();
}
}
addItem() {
const itemToAdd = this.state.inputText;
const tasks = this.state.tasks;
this.setState({
inputText: "",
tasks: tasks.concat(itemToAdd);
});
}
render() {
const { inputText } = this.state;
return(
<div>
<input type="text" className="inputclass" id="textfield1" placeholder='Enter a task.'
value={inputText} onChange={this.onInputChange} onKeyPress={this.onInputKeyPress} />
<br />
<br />
<button className="button" onClick={this.addItem} data-
toggle='fadein' data-target='list'>+</button>
</div>
);
}
}
Notice how input state is controlled via component state

React.js moving on to the next list

I'm making a movie search page. When I search something, it goes through the data base and find the very first match and display on the page. However, I want to create a function, so when I click next, page displays next movie in the data base. My code follows:
import React, { Component, PropTypes } from 'react';
import SearchBar from './Bar/index.js';
import SearchResult from './Result/index.js';
import axios from 'axios';
import './index.css';
class SearchArea extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
searchResult: {},
result: false,
count: 0
};
}
handleSearchBarChange(event) {
this.setState({searchText: event.target.value});
}
handleSearchBarSubmit(event) {
event.preventDefault();
const movie = this.state.searchText;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=c6cd73ec4677bc1d7b6560505cf4f453&language=en-US&query=${movie}&page=1&include_adult=false`)
.then(response => {
if(response.data.results.length >= 0) {
const i = 0;
const {
title,
overview,
release_date: releaseDate
} = response.data.results[this.state.count];
const posterPath = 'https://image.tmdb.org/t/p/w154' + response.data.results[this.state.count].poster_path;
this.setState({
searchResult: {
title,
posterPath,
overview,
releaseDate
},
result: true
});
}
else {
this.setState({
searchResult: {
title: 'No Result',
overview: 'No Overview Available',
posterPath: ''
},
result: true
});
}
})
}
handleSearchNext(event) {
this.handelSearchBarSubmit.overview = response.data.results[1];
}
handleResultClose() {
this.setState({
searchResult: {},
result: false
});
}
render() {
return (
<div>
<SearchBar
value = {this.state.searchText}
onChange = {this.handleSearchBarChange.bind(this)}
onSubmit = {this.handleSearchBarSubmit.bind(this)}
onNext = {this.handleSearchNext.bind(this)}
/>
{this.state.result &&
<SearchResult
searchResult = {this.state.searchResult}
onClose = {this.handleResultClose.bind(this)}
onAdd = {this.props.onAdd}
/>
}
</div>
);
}
}
SearchArea.propTypes = {
onAdd: PropTypes.func.isRequired
};
export default SearchArea;
I can't seem to figure out how to make handleSearchNext. Please help
EDIT
Following is the SearchBar code
import React, { PropTypes } from 'react';
import { Button } from 'semantic-ui-react';
import styles from './index.css';
const SearchBar = (props) => {
return (
<div>
<form onSubmit={(event) => props.onSubmit(event)}>
<input
className="searchBar"
type="text"
placeholder="Search Here"
value={props.value}this
onChange={(event) => props.onChange(event)}
onNext={(event) => props.onChange(event)}
onBack={(event) => props.onChange(event)}
/>
<Button className="button" type="submit">Sumbit</Button>
</form>
<Button className={styles.button} type="previous">Back</Button>
<Button className="button" type="next">Next</Button>
</div>
);
};
SearchBar.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onNext: PropTypes.func.isRequired
};
export default SearchBar;
You could have your server respond with not only the requested title, but also the next one. That way, when you click on Next, you can immediately display the next movie without waiting for a response, while still querying it in the background by name or id (so that you have the next after it, etc.).
Edit: If I misunderstood what you meant and you already have this (it looks like you are actually querying a whole page of movies at once), you probably simply want something like
handleSearchNext(event) {
this.setState({ searchResult: response.data.results[1], result: true });
}
and handle specially the case when you hit the last item on the page.

Categories

Resources