reactjs multiple checkbox - reactjs

I have the following code which add checkboxes in my page. The question is how can I get their values? What I am currently doing is using onChange to get the values and add it to my state but not sure if that's the best way? Whats the best practice in collecting data in input, should I use onChange or ref, I am a little bit confused with ref(I am new to ReactJS)
{Object.keys(this.state.IndustryChoice).map(key => (
<label className="custom-control fill-checkbox">
<input
name="Industry"
type="checkbox"
value={this.state.IndustryChoice[key]}
className="fill-control-input"
onChange={this.props.onMainToggle}
/>
<span className="fill-control-indicator" />
<span className="fill-control-description">
{this.state.IndustryChoice[key]}
</span>
</label>
))}
Here are other parts of my code
handleMainObjectCheckBoxToggle = event => {
let field = event.target.name;
let value = event.target.value;
let MainObject = this.state.MainObject;
// console.log("Winning Ways", MainObject[field]);
if (customIncludes(MainObject[field].results, value)) {
MainObject[field].results = customRemoveStringArray(
MainObject[field].results,
value
);
} else {
MainObject[field].results = appendObjTo(
MainObject[field].results,
value
);
// console.log(MainObject[field].results);
}
return this.setState({ MainObject });
};
<FormTactics
onMainToggle={this.handleMainObjectCheckBoxToggle}
/>

You must put checkbox values in your state as it gives your component more power to control the checkbox updates. Just use setState on onChange method and you'll see updated checkboxes
Let me know if you need more help

I can't quite understand your code but it seems you are trying to push or remove items somewhere in the state. I don't know if it suits you but I am providing a simple solution. Main differences with your code:
Hardcoded Industry key.
I'm not mutating the state directly which is a good behavior.
I am using the checked value for the elements.
I'm not quite sure how you implement this code in your app and if you need the checked values elsewhere. Here, we are keeping the checked values per value in the state in case you use them in the future.
class App extends React.Component {
state = {
IndustryChoice: {
foo: "foo",
bar: "bar",
baz: "baz"
},
MainObject: {
Industry: {
results: []
}
},
checkedValues: {}
};
handleMainObjectCheckBoxToggle = e => {
const { value, checked } = e.target;
const { results } = this.state.MainObject.Industry;
if (checked) {
const newResults = [...results, value];
this.setState(prevState => ({
MainObject: {
...prevState.MainObject,
Industry: { ...prevState.MainObject.Industry, results: newResults }
},
checkedValues: { ...prevState.checkedValues, [value]: checked }
}));
} else {
const newResults = results.filter(el => el !== value);
this.setState(prevState => ({
MainObject: {
...prevState.MainObject,
Industry: { ...prevState.MainObject.Industry, results: newResults }
},
checkedValues: { ...prevState.checkedValues, [value]: checked }
}));
}
};
render() {
return (
<div>
{Object.keys(this.state.IndustryChoice).map(key => (
<label>
<input
name={key}
type="checkbox"
value={this.state.IndustryChoice[key]}
className="fill-control-input"
onChange={this.handleMainObjectCheckBoxToggle}
/>
<span>{this.state.IndustryChoice[key]}</span>
</label>
))}
<p>results: {JSON.stringify(this.state.MainObject.Industry.results)}</p>
<p>checked: {JSON.stringify(this.state.checkedValues)}</p>
</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>

Related

How to update object using index value in Reactjs?

I am having a onChange function i was trying to update the array options by index wise and i had passed the index to the function.
Suppose if i am updating the options array index 0 value so only that value should be update rest should remain as it is.
Demo
Here is what i tried:
import React from "react";
import "./styles.css";
export default function App() {
const x = {
LEVEL: {
Type: "LINEN",
options: [
{
Order: 1,
orderStatus: "INFO",
orderValue: "5"
},
{
Order: 2,
orderStatus: "INPROGRESS",
orderValue: "5"
},
{
Order: 3,
orderStatus: "ACTIVE",
orderValue: "9"
}
],
details: "2020 N/w UA",
OrderType: "Axes"
},
State: "Inprogress"
};
const [postdata, setPostData] = React.useState(x);
const handleOptionInputChange = (event, idx) => {
setPostData({
...postdata,
LEVEL: {
...postdata.LEVEL.options,
[event.target.name]: event.target.value
}
});
};
return (
<div className="App">
{postdata.LEVEL.options.map((item, idx) => {
return (
<input
type="text"
name="orderStatus"
value={postdata.LEVEL.options[idx].orderStatus}
onChange={e => handleOptionInputChange(e, idx)}
/>
);
})}
</div>
);
}
Suppose if i want to add the objects in another useState variable for all the updated options only, will this work?
const posting = {
"optionUpdates": [],
}
const [sentdata , setSentData] = useState(posting);
setSentData({
...sentdata,
optionUpdates: [{
...sentdata.optionUpdates,
displayOrder: event.target.value
}]
})
Basically, you need to spread properly, use callback approach to set state etc.
Change your handler to like this.
Working demo
const handleOptionInputChange = (event, idx) => {
const target = event.target; // with callback approach of state, you can't use event inside callback, so first extract the target from event.
setPostData(prev => ({ // prev state
...prev, // spread prev state
LEVEL: { //update Level object
...prev.LEVEL,
options: prev.LEVEL.options.map((item, id) => { // you need to loop thru options and find the one which you need to update.
if (id === idx) {
return { ...item, [target.name]: target.value }; //spread all values and update only orderStatus
}
return item;
})
}
}));
};
Edit Added some comments to code and providing some explanation.
You were spreading postdata.LEVEL.options for LEVEL which is incorrect. For nested object you need to spread each level.
Apparently, your event.target.name is "orderStatus", so it will add an "orderStatus" key to your postData.
You might want to do something like this:
const handleOptionInputChange = (value, idx) => {
setPostData(oldValue => {
const options = oldValue.LEVEL.options;
options[idx].orderStatus = value;
return {
...oldValue,
LEVEL: {
...oldValue.LEVEL,
options
}
};
});
};
return (
<div className="App">
{postdata.LEVEL.options.map((item, idx) => {
return (
<input
type="text"
name="orderStatus"
value={postdata.LEVEL.options[idx].orderStatus}
onChange={e => handleOptionInputChange(e.target.value, idx)}
/>
);
})}
</div>
);
See this demo

Problem on handling array of object as state

I'm trying to set state as array of objects, but it fails.
I created project using CRA, and using react-hooks for states.
I get data from graphql server using react-apollo-hooks.
I just declared data object in codesandbox, but it doesn't affect my problem.
For every click, set state(array of object) with data(array of object).
const data = {
lists: [
{
id: "1"
},
{
id: "2"
},
{
id: "3"
}
]
};
const Sample = () => {
const [sample, setSample] = useState([]);
const Updator = async () => {
try {
await data.lists.map(list => {
setSample([
...sample,
{
label: list.id,
value: list.id
}
]);
return true;
});
console.log(sample);
} catch (err) {
throw err;
}
};
return (
<div>
<React.Fragment>
<button
onClick={e => {
Updator();
}}
>
Click me
</button>
<p>
<strong>
{sample.map(single => {
return <div>{single.label}</div>;
})}
</strong>
</p>
</React.Fragment>
</div>
);
};
I attached all test code on below.
Here is codesandbox link.
https://codesandbox.io/s/zr50rv7qjp
I expect result of
123
by click, but result is
3
Also, for additional click, expected result is
123
123
And I get
3
3.
When I use setSample(), I expect function something like Array.push(). But it ignores all the previous data expect the last one.
Any helps will be thankful!
state updater does batching and since you are calling the setSample method in map, only your last value is being written in state.
The best solution here is to return data from map and then update the state once like below.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const data = {
lists: [
{
id: "1"
},
{
id: "2"
},
{
id: "3"
}
]
};
const Sample = () => {
const [sample, setSample] = useState([]);
const Updator = async () => {
try {
const newData = data.lists.map(list => {
return {
label: list.id,
value: list.id
};
});
setSample([...sample, ...newData]);
} catch (err) {
throw err;
}
};
return (
<div>
<React.Fragment>
<button
onClick={e => {
Updator();
}}
>
Click me
</button>
<p>
<strong>
{sample.map((single, index) => {
return <div key={index}>{single.label}</div>;
})}
</strong>
</p>
</React.Fragment>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<Sample />, rootElement);
Working Demo
Another solution is to use the callback method to update state but you must avoid calling state updates multiple times.
You're destructing sample which will not have the latest version of itself when you're looping and calling setSample. This is why it only puts 3 in the list of samples, because the last iteration of the map will destruct an empty sample list and add 3.
To make sure you have the newest value of sample you should pass a function to setSample. This function will get the latest version of your state var from react.
setSample((latest) => {
return [
...latest,
{
label: list.id,
value: list.id
}
]
});

React efficiently update object in array with useState hook

I have a React component that renders a moderately large list of inputs (100+ items). It renders okay on my computer, but there's noticeable input lag on my phone. The React DevTools shows that the entire parent object is rerendering on every keypress.
Is there a more efficient way to approach this?
https://codepen.io/anon/pen/YMvoyy?editors=0011
function MyInput({obj, onChange}) {
return (
<div>
<label>
{obj.label}
<input type="text" value={obj.value} onChange={onChange} />
</label>
</div>
);
}
// Passed in from a parent component
const startingObjects =
new Array(100).fill(null).map((_, i) => ({label: i, value: 'value'}));
function App() {
const [objs, setObjects] = React.useState(startingObjects);
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
return (
<div>
{objs.map((obj) => <MyInput obj={obj} onChange={handleChange(obj)} />)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
The issue is related to:
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
In this, you will update the objs array. This is obviously fine, but React doesn't know what has changed, so triggered Render on all the Children.
If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost.
https://reactjs.org/docs/react-api.html#reactmemo
const MyInput = React.memo(({obj, onChange}) => {
console.log(`Rerendered: ${obj.label}`);
return <div style={{display: 'flex'}}>
<label>{obj.label}</label>
<input type="text" value={obj.value} onChange={onChange} />
</div>;
}, (prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);
However, React.Memo only does a shallow comparison when trying to figure out if it should render, so we can pass a custom comparison function as the second argument.
(prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);
Basically saying, if the label and the value, on the obj prop, are the same as the previous attributes on the previous obj prop, don't rerender.
Finally, setObjects much like setState, is also asynchronous, and will not immediately reflect and update. So to avoid the risk of objs being incorrect, and using the older values, you can change this to a callback like so:
function handleChange(obj) {
return (event) => {
const value = event.target.value;
setObjects(prevObjs => (prevObjs.map((o) => {
if (o === obj) return {...obj, value }
return o;
})))
};
}
https://codepen.io/anon/pen/QPBLwy?editors=0011 has all this, as well as console.logs, showing if something rerendered.
Is there a more efficient way to approach this?
You are storing all your values in an array, which means you don't know which element needs to be updated without iterating through the whole array, comparing if the object matches.
If you started with an Object:
const startingObjects =
new Array(100).fill(null).reduce((objects, _, index) => ({...objects, [index]: {value: 'value', label: index}}), {})
After some modifications, your handle function would change to
function handleChange(obj, index) {
return (event) => {
const value = event.target.value;
setObjects(prevObjs => ({...prevObjs, [index]: {...obj, value}}));
}
}
https://codepen.io/anon/pen/LvBPEB?editors=0011 as an example of this.

Convert React events to Webcomponent events

The React docs provide an example of how to use react inside a webcomponent. But the example provided is trivial, and not enough to learn from. In particular it does not provide information about how to bubble up an event out of a webcomponent.
Suppose that the code started as
const proto = Object.create(HTMLElement.prototype, {
attachedCallback: {
value: function() {
const mountPoint = document.createElement('span');
this.createShadowRoot().appendChild(mountPoint);
const name = this.getAttribute('name');
const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
ReactDOM.render(<input onchange={....} value={...}></input>, mountPoint);
}
}
});
document.registerElement('x-search', {prototype: proto});
How would this be wired up?
What about instead of using xeact?
Define component:
import xeact, {exposed, dispatchEvent, Component} from 'xeact';
#xeact('search')
export default class Search extends Component {
state = {
value: '',
};
onChange(e) {
const value = e.target.value;
this.setState({
value
}, () => {
dispatchEvent(this, 'change' , {
value
});
});
}
#exposed
get value() {
return this.state.value;
}
render() {
const {value} = this.state;
return <span>
<input value={value} onChange={(e) => {this.onChange(e)}} />
</span>
}
}
Use it from HTML:
<x-search></x-search>
<script>
// get value from `change` event
document.querySelector('x-search').addEventListener('change', function(e) {
console.log(e.detail.value);
});
// get value from getter
document.querySelector('x-search').value;
</script>

How to debounce user input in reactjs using rxjs

My problem may be a trivial one but I wasn't able to find the answer so far.
How can I defer (debounce) updating state in React while user is typing, to avoid unnecessary updates?
Having <input onChange={this.onChange} .../>, how can I bind onChange event with rxjs? Should I try to make this input observable or should I use FromEventPattern?
In both cases I have no idea how to bind React events with rxjs.
The second question is whether the user will see any input changes during debounce?
Solution #1
Using subjects:Fiddle
const state = new Rx.Subject()
.debounceTime(1000)
.scan((acc) => {
return ++acc
}, 0).do(::console.log)
const handler = (e) => {
state.next(e)
}
state.startWith(0).subscribe((clicks) => {
ReactDOM.render(<button onClick={handler}>Clicked {clicks}</button>, document.querySelector('#app'))
})
Solution #2
Using rxjs's fromEvent: Fiddle
// Intial render so element exists in dom (there is probably a better pattern)
ReactDOM.render( <button id='clickMe'>Click Me</button>, document.querySelector('#app'))
const clicks = Rx.Observable
.fromEvent(document.getElementById('clickMe'), 'click')
.do(::console.log)
.debounceTime(1000)
.scan((acc) => {
return ++acc
}, 0)
clicks.subscribe((clicks) => {
ReactDOM.render( <button id='clickMe'>Click Me {clicks}</button>, document.querySelector('#app'))
})
Solution #3
Note: highly experimental, and just something I tried to do for fun.
This is more for an action based architecture, where you have actions that change your state (flux). This is a handler that is fully standalone. It is used with a custom operator 'fromEventArgs': Fiddle (look at the console)
const handler = (e) => {
Rx.Observable
.fromEventArgs(e, 'UniqueKey')
.debounceTime(1000)
.subscribe(x => console.log('Send an action', x))
}
based on omerts propositions, (especially solution #1) here is my final code
input: Rx.Subject<any>;
constuctor(...){
this.input = new Rx.Subject();
this.input.debounce(1000).subscribe(this.processInput);
}
handleChange = event => {
event.persist();
this.input.onNext(event);
};
processInput = x => {
// invoke redux/flux action to update the state
}
render(){
...
<input onChange={this.handleChange} ... />
...
}
try like this:
class MyScene extends React.Component {
constructor(props) {
var onChangeTextObservable = Rx.Observable.fromEventPattern(
(handler) => {this.onChangeText = handler}
);
onChangeTextObservable.subscribe(x => console.log(x));
}
render() {
return (
<TextInput onChangeText={this.onChangeText}>
)
}
}
I keep getting to this question, while I prefer this solution from another one.
Copied below for simplicity. Please upvote the original.
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>

Resources