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>
Related
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>
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
Solved see comment below (Only had to move the Chooser and Section function outside of the class component to get it to work.
So I have a problem with the react onChange function. It does not seem to work when it is passed to a component as props. I tried to pass the component instead of the data but still it did not work. Please consider the following example:
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
type: '1',
number: ''
}
}
handleChange = e => {
const { name, value } = e.target
this.setState({ [name]: value })
}
render() {
return (
<div className="App">
<Form
type={this.state.type}
number={this.state.number}
handleChange={this.handleChange}
/>
</div>
}
}
//receiving the props
export default class Child extends Component {
constructor(props) {
super(props)
}
render() {
const Chooser = ({ type, section }) => {
switch (type) {
case '1':
return <Fragment>{section}</Fragment>
default:
return <Fragment>></Fragment>
}
}
const Section = ({ number, handleChange }) => (
<Fragment>
<div>
<label>Number</label>
<input
type='text'
name='number'
placeholder='123456789'
value={number}
onChange={handleChange}
/>
</div>
</Fragment>
)
return (
<Chooser
type={this.props.type}
section={
<Section
number={this.props.number}
handleChange={this.props.handleChange}
/>
}
/>
)
}
}
Interestingly if I put the onChange on the Section level it does work. But this is not what I want since a passed component could have multiple Input functions that I want to pass.
return (
<Chooser
type={this.props.type}
section={
<Section
number={this.pops.number}
onChange={this.pops.handleChange}
/>
}
/>
Any ideas how I can pass the onChange function down using props? On a similar example the Input change does work but it is loosing focus each time a value is pressed. Already tried assigning keys but that did not work either.
You currently have this as your handleChange method
handleChange = e => {
const { name, value } = e.target.value
this.setState({ [name]: value })
}
You should change it to this.
handleChange = e => {
const { name, value } = e.target;
this.setState({ [name]: value })
}
You seem to be accessing the wrong property in the target because name will always be undefined inside e.target.value and as such, calling setState won't do anything.
Also, you should probably be declaring your function components outside of the class component.
this is because you need to destructure it like this on the next like
const {handleChange} = this.props.handleChange
You can wrap handleChange() in an anonymous function so that it will actively wait for you to make changes to the input. Otherwise it will run on render.
const Section = ({ number, handleChange }) => (
<Fragment>
<div>
<label>Number</label>
<input
type='text'
name='number'
placeholder='123456789'
value={number}
onChange={(e) => handleChange(e)}
/>
</div>
</Fragment>
)
I'm trying to pass 2 elements within the todo array, however it just returns the term element value. I see no error in the console.
It seems that the
items: [...this.state.items, this.state.term, this.state.name ]
only accepts two parameters.
I'm currently following this
https://reactjs.org/docs/handling-events.html
const { Component } = React;
class App extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
name: '',
items: []
};
}
onChange = (event) => {
this.setState({name: event.target.value, term: event.target.value});
}
onSubmit = (event) => {
event.preventDefault();
this.setState({
term: '',
name: '',
items: [
...this.state.items,
this.state.term,
this.state.name
]
});
}
render() {
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input value={this.state.term} onChange={this.onChange}/>
<input value={this.state.name} onChange={this.onChange}/>
<button>Submit</button>
</form>
<pre>{JSON.stringify(this.state.items)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I suspect the issue you're asking about is the fact that your onChange event updates both term and name and whatever you type into one input goes into the other. Here's how you can resolve that:
Add a name attribute to your input that corresponds to the key in the state.
Access the value of name in onChange and update the value accordingly.
Solution
const { Component } = React;
class App extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
name: '',
items: []
};
}
onChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}
onSubmit = (event) => {
event.preventDefault();
this.setState({
term: '',
name: '',
items: [
...this.state.items,
this.state.term,
this.state.name
]
});
}
render() {
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input name="term" value={this.state.term} onChange={this.onChange}/>
<input name="name" value={this.state.name} onChange={this.onChange}/>
<button>Submit</button>
</form>
<pre>{JSON.stringify(this.state.items)}</pre>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I dont think your this.setState has any issue for your example.
You seem to be using this.onChange wrong.
I changed its implementation so that both input can have their own respective handler.
onChange = key => event => {
this.setState({ [key]: event.target.value }); };
Also changed input callbacks to pass key value from the render function below.
render() {
const { term, name, items } = this.state;
return (
<div>
<form className="App" onSubmit={this.onSubmit}>
<input value={term} onChange={this.onChange('term')} />
<input value={name} onChange={this.onChange('name')} />
<button>Submit</button>
</form>
{items.join(", ")}
</div>
);
}
}
Your prior code was problematic which was rendering same value for name and term with whatever you type in any of your input boxes.
I have a CheckboxGroup component which takes in an options array prop and generates CheckboxInput components. On page load I make a call to an API which returns an array of pre-selected checkboxes (delivered to the value prop). Depending on the logged in user, this call can return an empty array or a selection of previously selected checkbox options.
The following code successfully takes the response of the API call and sets the relevant checkboxes to 'checked'. The issue I have is that this code doesn't allow me to make changes to the checkboxes after page load (clicking a checkboxes has no effect).
I think there is also some disconnect between the initial selectedCheckboxes state and the value of the API call but I read that setting props as initial state is an anti-pattern (e.g. selectedCheckboxes: props.value,).
export default class CheckboxGroup extends Component {
constructor(props) {
super(props);
this.state = {
selectedCheckboxes: [],
};
}
addCheckboxToSelected = (id) => {
if (this.state.selectedCheckboxes.includes(id)) {
// Remove checkbox from array and update state
const filteredArray = this.state.selectedCheckboxes.filter(item => item !== id);
this.setState({ selectedCheckboxes: filteredArray });
} else {
// Add checkbox to array and update state
this.setState({ selectedCheckboxes: this.state.selectedCheckboxes.concat(id) });
}
}
checkIfSelected = (checkboxValue) => {
const preSelectedCheckboxes = this.props.value;
let selectedBool = false;
preSelectedCheckboxes.some(function(object) {
if (object.id === checkboxValue) {
selectedBool = true;
}
return false;
});
return selectedBool;
}
render() {
const { label, name, options } = this.props;
return (
<div className="form-group form-inline">
<span className="checkboxgroup-heading">{label}</span>
<div className="form-group-container">
{options.map(object => (
<CheckboxInput
key={object.value}
name={name}
label={object.label}
onChange={this.addCheckboxToSelected}
value={object.value}
checked={this.checkIfSelected(object.value)}
/>
))}
</div>
</div>
);
}
}
This is the stateless CheckboxInput component
const CheckboxInput = ({ name, label, onChange, value, checked }) => {
return (
<div className="field form-group filter-input">
<input
type="checkbox"
id={value}
name={name}
value={value}
onChange={() => onChange(value)}
checked={checked}
/>
<label htmlFor={value} className="form-control">{label}</label>
</div>
);
};
Check the following code snippet. This might help. Let me know if you have questions.
const CheckboxField = ({checked, onChange}) => {
return (
<input type="checkbox" checked={checked} onChange={ev => onChange(ev.target.checked)} />
);
};
class App extends React.Component {
constructor() {
super();
this.state = {
options: [{id: "1", checked: true}, {id: "2", checked: false}]
};
}
handleCheckboxChange(checked, option) {
const {options} = this.state;
var cOptions = [...options];
for(var i in cOptions) {
if(cOptions[i].id == option.id) {
cOptions[i].checked = checked;
}
}
this.setState({
options: cOptions
}, () => console.log(options));
}
render() {
const {options} = this.state;
return (
<div>
{
options.map(option => {
return (
<CheckboxField key={option.id} checked={option.checked} onChange={value => this.handleCheckboxChange(value, option)} />
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<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>