How does React class component method's parameters work? - reactjs

I'm learning React and am having a hard time understanding how a class component's method parameters work. In the code below the handleAddOption method (which helps with adding items upon clicking a button, it's a to-do app) - it takes 'option' as parameter - but I do not see the arguments supplied in the render method.
Similarly in the AddOption component the handleAddOption has an argument 'option' - where is this coming from?
I'm a newbie to React and to stackoverflow as well, any norms I may not have followed please point out. Thanks for the help.
class App extends React.Component {
constructor(props) {
super(props);
this.handleAddOption = this.handleAddOption.bind(this);
this.state = {
options: []
};
}
handleAddOption(option) {
if (!option) {
return 'Enter valid value to add item';
} else if (this.state.options.indexOf(option) > -1) {
return 'This option already exists';
}
this.setState((prevState) => {
return {
options: prevState.options.concat(option)
};
});
}
render() {
return (
<div>
<div>{this.state.options.map((option) => <p>{option}</p>)}</div>
<AddOption handleAddOption={this.handleAddOption} />
</div>
);
}
}
class AddOption extends React.Component {
constructor(props) {
super(props);
this.handleAddOption2 = this.handleAddOption2.bind(this);
this.state = {
error: undefined
};
}
handleAddOption2(e) {
e.preventDefault();
const option = e.target.elements.option.value.trim();
const error = this.props.handleAddOption(option);
this.setState(() => {
return { error };
});
}
render() {
return (
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.handleAddOption2}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
</div>
);
}
}
render(<App />, document.getElementById('app'));

The arguments are being passed by the submit handler attached to the form.
You provide a function that you want called whenever there is a submit event. The form will call whatever function you provide with the arguments it usually passes in.
This happens the same way as it happens in plain JS:
const form = document.getElementById("form");
form.addEventListener("submit", e => {
e.preventDefault();
console.log("submit 1");
});
const submitHandler = e => {
e.preventDefault();
console.log("submit 2");
};
form.addEventListener("submit", submitHandler);
<form id="form">
<input type="submit" />
</form>
Consider the React example:
class MyForm extends React.Component {
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
foo:""
}
}
handleSubmit(e) {
e.preventDefault();
console.log("MyForm Submit 1");
this.setState(state => ({
foo: "foo"
}));
}
render() {
/*
onSubmit will always call the function that is provided
with a submit event argument.
*/
return (
<form onSubmit={this.handleSubmit}>
<div>{this.state.foo}</div>
<input type="submit"/>
</form>
)
}
}
class MyOtherForm extends React.Component {
constructor(props){
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
foo:""
}
}
handleSubmit(e) {
e.preventDefault();
console.log("MyForm Submit 2");
this.setState(state => ({
foo: "bar"
}));
}
render() {
// Here we will pass the argument explicitly
return (
<form onSubmit={e => this.handleSubmit(e)}>
<div>{this.state.foo}</div>
<input type="submit"/>
</form>
)
}
}
const App = () => {
return(
<div>
<MyForm/>
<MyOtherForm/>
</div>
);
};
ReactDOM.render(<App/>, document.getElementById("app"));
<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="app"></div>

According to the code you provide, handleAddOption(option) is called from the handleAddOption2 function in the AddOption component.
handleAddOption2(e) {
e.preventDefault();
const option = e.target.elements.option.value.trim();
const error = this.props.handleAddOption(option);
this.setState(() => {
return { error };
});
}
You can see that option is e.target.elements.option.value.trim(). Now, where does e comes from? Tracking the source of the function above, you can see that handleAddOption2(e) is a event handler called from here:
<form onSubmit={this.handleAddOption2}>
<input type="text" name="option" />
<button>Add Option</button>
</form>
In React, event handlers (e.g. onClick/onSubmit) by default has a default event parameter embedded to its handler function. This parameter can be any name (defined by you), and the author of the code you provide named it e.
Giving you another example, say you have a button:
<button onClick={this.sayHello}>
Click me!
</button>
There is already an event param embedded to sayHello, but you can choose not to use it.
sayHello = () => {
alert('Hello!');
}
Or if you want to use it, I'll name it event instead of e here:
sayHello = (event) => {
event => alert(event.target.value)
}
Or you can choose define the event handler inline like this:
<button value="hello!" onClick={event => alert(event.target.value)}>
Click me!
</button>

Related

Why is this.setState not updating in my click function?

I am trying to update my state by using a click function. However for some reason it is not updating. Could someone please explain to me what I am doing wrong?class Textbox extends
Component {
constructor(props) {
super(props);
this.handle = this.handle.bind(this);
this.state = {
text: 'jkjkljkljl'
}
}
handle(event) {
const myValue = event.target.value;
this.setState({
text: myValue
})
console.log(this.state)
}
render() {
return (
<div>
<textarea className="Textbox" rows="2" cols="30" type = "text" >
</textarea>
<button className="postbutton" onClick={this.handle.bind(this)}>Post</button>
<h1>{this.state.text}</h1>
</div>
);
}
}
export default Textbox;
Here is an updated version of your code that works.
Issue was that you were trying to set the value of the button to the state.
What you should do is setup textarea as a controlled input (have value and onChange setup as I did below) and use that value on click.
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
textArea: "",
text: "jkjkljkljl"
};
}
handle(event) {
console.log(event);
this.setState({
text: this.state.textArea
});
console.log(this.state);
}
handleChange(event) {
this.setState({ textArea: event.target.value });
}
render() {
return (
<div>
<textarea
className="Textbox"
rows="2"
cols="30"
value={this.state.textArea}
onChange={this.handleChange.bind(this)}
/>
<button className="postbutton" onClick={this.handle.bind(this)}>
Post
</button>
<h1>{this.state.text}</h1>
</div>
);
}
}
It seems you are trying to handle a form using React/JSX. There are great libraries for this purpose (React Forms).
This is the proper code:
class App extends React.Component {
constructor(props) {
super(props);
this.handle = this.handle.bind(this);
this.state = {
text: 'Static'
}
}
handleOnChange(event) {
this.setState({text: event.target.value});
}
handleSubmit(event) {
if (event.keyCode == 13) return this.sendData();
}
render() {
return (
<div>
<form onKeyUp={this.handleOnChange}>
<textarea className="Textbox"
rows="2" cols="30" type="text"
>
</textarea>
<button className="postbutton"
onClick={this.handleSubmit.bind(this)}>
Post
</button>
</form>
<h1>{this.state.text}</h1>
</div>
);
}
}
React.render(<App />, document.getElementById('app'));
In your example, you are binding the state to the root of the button and not the textarea. If you want a static example (whereas the above code changes as you type), you may simply handle the enter key via if (event.keyCode == 13) return this.sendData() and remove the onChange.

Push an item in array using ReactJS

class Demo extends React.Component{
constructor (){
super();
this.state = {
list : ['car','map', 'house']
}
}
inputValue(e){
var x = e.target.value;
console.log(x)
}
addValue(){
this.state.list.push();
this.setState({list: this.state.list});
}
render(){
return(
<div>
<input onChange={this.inputValue} type="text"/>
<ul>
{this.state.list.map(item => (
<li>{item}</li>
))}
</ul>
<button onClick={this.addValue.bind(this)}>Add Element</button>
</div>
)
}
}
ReactDOM.render(
<Demo/>,
document.getElementById('test')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id="test"></div>
Using my code, how can i push the value from <input onChange={this.inputValue} type="text"/> in list : ['car','map', 'house']. I use for this addValue function, but i can't insert the x variable from inputValue function in push() from addValue function. How to do this using my code?
You need a state value for the text-input so that your addValue() function knows what to use when its time to add a new item. The text state will be updated with anything the user types.
Working demo: https://codesandbox.io/s/magical-feynman-fze1n
import React from "react";
class Demo extends React.Component {
constructor() {
super();
this.state = {
text: "",
list: ["car", "map", "house"]
};
}
inputValue(e) {
this.setState({
text: e.target.value
});
}
addValue() {
const text = this.state.text;
this.setState({ list: [...this.state.list, text] });
}
render() {
return (
<div>
<input onChange={this.inputValue.bind(this)} type="text" />
<ul>
{this.state.list.map(item => (
<li>{item}</li>
))}
</ul>
<button onClick={this.addValue.bind(this)}>Add Element</button>
</div>
);
}
}
export default Demo;
Also, refrain from doing direct state-mutations like this.state.list.push(blah). This is against React principles and can lead to unwanted visual side-effects. If you need to reference an existing state, try to create a copy of it instead. In the case for you list, we use the spread-operator to create a shallow-copy and then added the new item to the array..
Since React is all about small components and reusability consider breaking it up into two separate components... That way, if you need a form anywhere else you can reuse it...
Here is your Demo:
class Demo extends Component {
state = { list: ['car', 'map', 'house'] };
addItem = item => {
this.setState({ list: [item, ...this.state.list] });
};
render() {
return (
<div>
<Form addItem={this.addItem} />
{this.state.list.map((item, index) => (
<div key={index}>{item}</div>
))}
</div>
);
}
}
And here is the Form:
class Form extends Component {
state = { item: '' };
handleChange = event => {
this.setState({ item: event.target.value });
};
handleSubmit = event => {
event.preventDefault();
this.props.addItem(this.state.item);
this.setState({ item: '' });
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
value={this.state.item}
onChange={this.handleChange}
/>
</form>
);
}
}
Live Demo: https://stackblitz.com/edit/react-611uzp

jest input on change not triggered/simulated

I have a React component that looks like this. It's a simple form with an input element of type email. As usual, when the user types some text, I fire a callback for the onChange event. This is what the code looks like.
import React, { PureComponent, Fragment } from "react";
import CheckCircleOutline from "mdi-react/CheckCircleOutlineIcon";
import AccountOutlineIcon from "mdi-react/AccountOutlineIcon";
import styles from "./ForgotPassword.module.scss";
class ForgotPasswordFrom extends PureComponent {
constructor() {
super();
this.state = {
email: ""
};
}
updateEmailField = e => {
this.setState({ email: e.target.value });
};
resetPassword = async e => {
e.preventDefault();
const { email } = this.state;
this.props.onSubmit(email);
};
render() {
const { showResetMessage, email } = this.props;
return (
<Fragment>
<form className="form aim-form">
{!showResetMessage ? (
<Fragment>
<div className="form__form-group">
<div className="form__form-group-field">
<div className="form__form-group-icon">
<AccountOutlineIcon />
</div>
<input
name="email"
type="email"
onChange={this.updateEmailField}
placeholder="Enter Registered Email Address"
className="email-input"
data-testid="forgot_password_input"
/>
</div>
</div>
<button
type="button"
className="btn btn-primary account__btn account__btn--small login-btn"
onClick={this.resetPassword}
data-testid="forgot_password"
>
Submit
</button>
</Fragment>
) : (
<div className={styles.messageContainer}>
<CheckCircleOutline size={50} />
<div className={styles.emailMessage}>
<div>We have sent an email to {email}.</div>
<div>Click the link in the email to reset your password</div>
</div>
</div>
)}
</form>
</Fragment>
);
}
}
export default ForgotPasswordFrom;
I am trying to write a test for when the input field's change event is simulated. This test should basically ensure that the updateEmailField function is triggered. However, no matter what I try, I cannot get the test to pass. The error I get is that the mock function is not called. Any idea what I'm doing wrong?
it("should have called the function", () => {
const wrapper = mount(<ForgotPasswordForm />);
const instance = wrapper.instance();
instance.updateEmailField = jest.fn()
const input = wrapper.find(`[data-testid='forgot_password_input']`);
input.simulate('change', { target: { value: 'test ' } });
expect(instance.updateEmailField).toHaveBeenCalled();
})
Try this code.
it("should have called the function", () => {
jest.spyOn(ForgotPasswordForm.prototype, 'updateEmailField');
const wrapper = mount(<ForgotPasswordForm />);
afterAll(() => {
ForgotPasswordForm.prototype.updateEmailField.mockRestore();
});
const input = wrapper.find(`[data-testid='forgot_password_input']`);
input.simulate('change', { target: { value: 'test ' } });
expect(wrapper.instance().updateEmailField).toHaveBeenCalled();
})

Why does the onClick only work the first time?

After the first onClick the modal displays. It is then removed after when clicking outside of it. However, despite the button not being a child of the modal, its onClick fails to update this.state.changed and remove the div. Why?
Codepen: https://codepen.io/Matt-dc/pen/KJYxqv
class Modal extends React.Component {
setWrapperRef = (node) => {
this.wrapperRef = node;
}
componentWillMount = () => {
document.addEventListener('mouseup', this.handleClickOutside);
}
componentDidMount = () => {
document.addEventListener('mouseup', this.handleClickOutside);
}
handleClickOutside = (e) => {
if (this.wrapperRef && !this.wrapperRef.contains(e.target)) {
this.props.close()
}
}
render() {
if(!this.props.changed) {
return null
}
return(
<div id="modal">
<div
id="ref"
style={myDiv}
ref={this.setWrapperRef}
>
{this.props.children}
</div>
</div>
);
}
}
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
changed: false,
}
this.handleChange = this.handleChange.bind(this);
}
handleChange = () => {
this.setState({
changed: !this.state.changed,
});
}
render(){
return(
<div id="app">
<input type="button" value="show" onClick={this.handleChange} />
<Modal changed={this.state.changed} close={this.handleChange} />
</div>
This is because of document.addEventListener('mouseup', this.handleClickOutside) in you Modal Component. Try by commenting -
document.addEventListener('mouseup', this.handleClickOutside) and it will work.
Also its better if you can render {this.state.changed.toString()} to get more details while debugging :)
It only looks like it does not close on button click, but actually it does close the modal and then change it immediately, again, because the handleChange function is fired two times.
The problem lies in your handleClickOutside logic, which that you trigger the handleChange function (passed via close props):
<Modal changed={this.state.changed} close={this.handleChange} />
The button you are clicking is outside the modal - that's why it is fired, again.

Rxjs debounce on react text input component

I have the following react component
<input className={styles.incSrchTextBox} type="text" name="search" placeholder="Search.."
onChange={this.onChange} />
onChange(e) {
const newText = e.target.value;
console.log(newText);
this.setState({ searchText: newText });
}
How do I use debounce on rxjs on this?
You will need to cretae observable from change events(for example using Subject) and then debounce on that.
Here is the fully featured example for you:
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
search: '',
debounced: '',
};
this.onSearch$ = new Rx.Subject();
this.onSearch = this.onSearch.bind(this);
}
componentDidMount(){
this.subscription = this.onSearch$
.debounceTime(300)
.subscribe(debounced => this.setState({ debounced }));
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
onSearch(e) {
const search = e.target.value;
this.setState({ search });
this.onSearch$.next(search);
}
render() {
const { search, debounced } = this.state;
return (
<div>
<input type="text" value={search} onChange={this.onSearch} />
<div>debounced value: {debounced}</div>
</div>
);
}
}
ReactDOM.render(
<Search />,
document.getElementById('root')
);
<script src="https://unpkg.com/rxjs#5.4.0/bundles/Rx.min.js"></script>
<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>
This would be a good use case for Refract!
The first step would be to pull the input out into a separate component:
const Input = ({ onChange, value }) => (
<input type="text" value={value} onChange={onChange} />
)
Next step would be to wrap this component with Refract's withEffects higher-order component, with a handler and an aperture to handle the side-effects like this:
import { withEffects } from 'refract-rxjs'
import { debounceTime } from 'rxjs/operators'
const Input = ({ onChange, value }) => (
<input type="text" value={value} onChange={onChange} />
)
const aperture = () => component =>
component.observe('value').pipe(debounceTime(300))
const handler = ({ onUpdate }) => value => onUpdate(value)
const DebouncedInput = withEffects(handler)(aperture)(Input)
An aperture lets you observe your component's props. In this case, it would make sense to observe the value prop - every time the value changes, the component.observe('value') stream gets a new value.
The handler is a function called with each value output by the aperture's stream. In this case, the debounced value is passed straight through to a new prop called onUpdate.
Both apertures and handlers are explained in detail in the docs - Observing React introduces apertures, and Handling Effects explains handlers.
As an example of how you would use this:
class Search extends React.Component {
state = { debounced: '', search: '' }
onSearch = e => this.setState({ search: e.target.value })
onUpdate = debounced => this.setState({ debounced })
render() {
return (
<div>
<DebouncedInput
type="text"
value={this.state.search}
onChange={this.onSearch}
onUpdate={this.onUpdate}
/>
<div>debounced value: {debounced}</div>
</div>
)
}
}
With this code, the text DebouncedInput would display the user's input instantly (which is ideal for UX), while debouncing the side-effect of calling the onUpdate callback. It would then be trivial to expose this onUpdate to components which consume the Search component!
I agree with the example by Oles Savluk. In addition, I would extract the Subject logic out of the component. It doesn't need to live inside the component, as it has no state, and I think this also makes the component easier to understand.
Also: The example is updated to use RxJS 6.2.2
const { Subject } = rxjs;
const { debounceTime } = rxjs.operators;
const onSearch$ = new rxjs.Subject().pipe(
debounceTime(300)
);
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
search: '',
debounced: '',
};
}
componentDidMount(){
this.subscription = onSearch$.subscribe(
debounced => this.setState({ debounced })
);
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
onSearch = (e) => {
const search = e.target.value;
this.setState({ search });
onSearch$.next(search);
}
render() {
const { search, debounced } = this.state;
return (
<div>
<input type="text" value={search} onChange={this.onSearch} />
<div>debounced value: {debounced}</div>
</div>
);
}
}
ReactDOM.render(
<Search />,
document.getElementById('root')
);
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>
<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>

Resources