I'm using a document structure like this
render() {
return (
<div className="MyComponent">
<ul className="">
{parseRecommendations(this.props.recommendations)}
</ul>
</div>
);
}
function parseRecomendations(recommendations) {
return recommendations.map((recommendation, key) => {
return (<Recommendation data={recommendation} key={key} />);
});
}
Where each Recommendation is its own component containing a checkbox
class Recommendation extends Component {
const recommendation = this.props.data;
const pageUrl = recommendation.url;
return (
<li className="article-item" key={key}>
<div className="article-info">
<input type="checkbox" defaultChecked="checked" aria-described-by={recommendation.pii} />
<a className="journal-title" href={pageUrl} id={recommendation.pii}>{recommendation.title}</a>
</div>
</li>
);
I'd like to have a title saying [Download (x) PDFs], where x is the number of selected checkboxes. How do I find the value of x in this case?
You need to store information about whether input is "checked" in your data. Then, simply count items with truthy "checked" flag.
Here is my solution. You should be able to get principle here and modify your code.
const data = [
{ checked: false, value: 'document 1' },
{ checked: true, value: 'document 2' },
{ checked: true, value: 'document 3' },
{ checked: false, value: 'document 4' },
{ checked: false, value: 'document 5' },
];
const Item = props => (
<div>
<input type="checkbox" checked={props.checked} onChange={props.onCheckChange} />
{ props.value }
</div>
)
var Hello = React.createClass({
getInitialState() {
return {
items: this.props.items.concat(),
};
},
onCheckChange(idx) {
return () => {
const items = this.state.items.concat();
items[idx].checked = !items[idx].checked;
this.setState({items});
}
},
totalChecked() {
return this.state.items.filter(props => props.checked).length;
},
render() {
return (
<div>
{ this.state.items.map((props, idx) => (
<Item {...props} key={idx} onCheckChange={this.onCheckChange(idx)} />
)) }
Total checked: { this.totalChecked() }
</div>
);
}
});
ReactDOM.render(
<Hello items={data} />,
document.getElementById('container')
);
If you just want to get the number of selected check-boxes you can try this
let checkedBoxes = document.querySelectorAll('input[name=chkBox]:checked');
Then get the total checked boxes via checkedBoxes.length
Edit:
Instead of querying whole document. You can get the nearest possible parent via getElementsByClassName or getElementById and then apply querySelectorAll on that element.
e.g
let elem = document.getElementsByClassName("MyComponent");
let checkedBoxes = elem.querySelectorAll('input[name=chkBox]:checked');
You also could obtain the total of selected checkboxes by element type. The "console.log(totalSelectedCheckboxes)" will print them when the state of totalSelectedCheckboxes change using useEffect Hook.
import React, { useState, useEffect } from 'react';
const RenderCheckboxes = () => {
const [totalSelectedCheckboxes, setTotalSelectedCheckboxes] = useState(0);
function handleChk() {
setTotalSelectedCheckboxes(document.querySelectorAll('input[type=checkbox]:checked').length);
}
useEffect(() => {
console.log(totalSelectedCheckboxes);
}, [totalSelectedCheckboxes]);
return (<div>
<div>
<input type="checkbox" value={1} onChange={() => handleChk()} />Chk1
</div>
<div>
<input type="checkbox" value={2} onChange={() => handleChk()} />Chk2
</div>
<div>
<input type="checkbox" value={2} onChange={() => handleChk()} />Chk2
</div>
</div>);
}
export default RenderCheckboxes;
Related
This is the code I am trying to rebuild using functional component, but my arrays do not behave correctly.
EXPECTED RESULT: https://stackblitz.com/edit/antd-showhidecolumns
My forked functional component version:
MY WORK https://stackblitz.com/edit/antd-showhidecolumns-rdyc8h
Main issue here is I am not able to show/hide column cells, I am not sure why my array is different when I use the same method as the original code.
My code:
const onChange = (e) => {
let { checkedColumns } = colmenu;
if (e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (!e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
const filtered = checkedColumns.filter((el) => {
return el.dataIndex !== checkedColumns.el;
});
console.log('filtered items', filtered);
setColmenu({ ...colmenu, columns: filtered });
};
working version from the old code (class component)
onChange = (e) => {
var checkedColumns = this.state.checkedColumns
if(e.target.checked){
checkedColumns = checkedColumns.filter(id => {return id !== e.target.id})
}
else if(!e.target.checked){
checkedColumns.push(e.target.id)
}
var filtered = this.state.initialColumns;
for(var i =0;i< checkedColumns.length; i++)
filtered = filtered.filter(el => {return el.dataIndex !== checkedColumns[i]})
this.setState({columns: filtered, checkedColumns: checkedColumns})
}
Something really went wrong with your code (or homework i guess?)
Please have a look at least at the docs for React.useState to set some basics.
First you should init your initalColumns and later you should filter on them.
Additional i init the checkColumns with the correct values and changed the wrong logic for changing them.
Have a look how the filtering is done via Array.includes maybe someone will ask for this ;-)
Another point is that you may split the state object in separate primitive states.
Nevertheless here is a working stackblitz and the depending code.
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import './index.css';
import { Table, Button, Dropdown, Menu, Checkbox } from 'antd';
const App = () => {
const columns = [
{
title: 'Description',
dataIndex: 'description',
},
{
title: 'Employees',
dataIndex: 'employees',
},
];
const [colmenu, setColmenu] = React.useState({
value: false,
checkedColumns: ['description', 'employees'],
visibleMenuSettings: false,
columns,
initialColumns: columns,
});
const onChange = (e) => {
let { checkedColumns, columns, initialColumns } = colmenu;
if (!e.target.checked) {
checkedColumns = checkedColumns.filter((id) => {
return id !== e.target.id;
});
console.log('if checked columns is', checkedColumns);
} else if (e.target.checked) {
checkedColumns.push(e.target.id);
console.log('elseif checked columns', checkedColumns);
}
console.log(columns);
columns = initialColumns.filter((col) =>
checkedColumns.includes(col.dataIndex)
);
setColmenu({ ...colmenu, columns, checkedColumns });
};
const handleVisibleChange = (flag) => {
setColmenu({ ...colmenu, visibleMenuSettings: flag });
};
const menu = (
<Menu>
<Menu.ItemGroup title="Columns">
<Menu.Item key="0">
<Checkbox id="description" onChange={onChange} defaultChecked>
Description
</Checkbox>
</Menu.Item>
<Menu.Item key="1">
<Checkbox id="employees" onChange={onChange} defaultChecked>
Employees
</Checkbox>
</Menu.Item>
</Menu.ItemGroup>
</Menu>
);
const dataSource = [
{
key: '1',
description: 'Holiday 1',
employees: '79',
},
{
key: '2',
description: 'Holiday 2',
employees: '12',
},
{
key: '3',
description: 'Holiday 3',
employees: '0',
},
];
return (
<div>
<div className="row">
<div className="col-12 mb-3 d-flex justify-content-end align-items-center">
<Dropdown
overlay={menu}
onVisibleChange={handleVisibleChange}
visible={colmenu.visibleMenuSettings}
>
<Button>Show/Hide Columns</Button>
</Dropdown>
</div>
</div>
<div className="row">
<div className="col-12">
<Table
columns={colmenu.columns}
dataSource={dataSource}
size="small"
pagination={{
pageSizeOptions: ['20', '50'],
showSizeChanger: true,
}}
/>
</div>
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('container'));
I have a list of chat room channels for people to talk i.e there is a lifestyle channel, shopping channel, pets channel etc.
I am now trying to categorise each channel to make it easier for the user to find what they want. In order to do so, on creation of a chatroom channel I need the user to select which category the channel they are creating best fits into. A bit like YouTube does when you upload a video.
So far I have created a separate component which is a list of checkboxes with the different categories the user can put their channel into:
import React from 'react';
const options = [
{ label: "Lifestyle", value: "lifestyle"},
{ label: "Area", value: "area" },
{ label: "Random", value: "random" },
{ label: "Comedy", value: "comedy" },
{ label: "Entertainment", value: "entertainment" }
];
const ChannelCategory = (props) => {
return (
<div>
{props.title}
<ul>
{options.map((option) => (
<li key={props.key}>
<label>
{option.label}
<input
className={props.className}
name="test"
checked={props.checked}
onChange={() => props.onChange(option.value)}
type="checkbox"
/>
</label>
</li>
))}
</ul>
</div>
)
};
export default ChannelCategory;
I am using the above component on the page below, I would like that when the user selects just ONE of the options only ONE input box is checked, however at the moment when I click ONE input box for instance lifestyle they ALLLL get checked and for every single channel too:( Any ideas why?
const [checked, setCheckBoxChecked] = useState(false);
[...]
const onAddCategory = (value) => {
console.log(value);
if (value === "lifestyle") {
setCheckBoxChecked(checked => !checked);
}
if (value === "area") {
setCheckBoxChecked(checked => !checked);
}
if (value === "random") {
setCheckBoxChecked(checked => !checked);
}
if (value === "comedy") {
setCheckBoxChecked(checked => !checked);
}
};
[...]
const options = [
{ label: "Lifestyle", value: "lifestyle"},
{ label: "Area", value: "area" },
{ label: "Random", value: "random" },
{ label: "Comedy", value: "comedy" },
{ label: "Entertainment", value: "entertainment" }
];
return (
<form noValidate autoComplete='off' onSubmit={onSubmit}>
<Card style={styles.card}>
<CardContent>
<Box padding={3}>
<FormLegend title={`${formTitle} (${channels.length})`} description={formDescription} />
<Box marginTop={3} width='50%'>
<Grid container direction='column' justify='flex-start' alignItems='stretch' spacing={1}>
{channels.map(channel => {
return (
<Grid key={channel.key} item style={styles.gridItem} justify="space-between">
<ChannelListItem
channel={channel}
isSaving={isSaving}
onDeleteChannelClick={onDeleteChannelClick}
key={channel.Key}
onFormControlChange={onFormControlChange}
onUndoChannelClick={onUndoChannelClick}
/>
<ChannelCategory
key={channel.key}
options={options}
onChange={value => onAddCategory(value)}
title="Add your chatroom to a category so that users can find it easily"
checked={checked}
/>
</Grid>
)
})}
[...]
</Grid>
</Grid>
</Box>
</Box>
</CardContent>
</Card>
</form>
);
Instead of storing true or false inside the checked variable, you should store the value inside of checked. Like this:
const onChangeAttribute = (value) => {
console.log(value);
setCheckBoxChecked(value);
};
And now while rendering the checkbox you should check if checked is equal to the name of that checkbox like this:
<input
className={props.className}
name={option.value}
checked={props.checked === option.value}
onChange={() => props.onChange(option.value)}
type="checkbox"
/>
This should resolve your issue.
Use an array to store all checked boxes and in your ChannelCategory check if the current value exists in the checked array then set checked to true for that checkbox. If you want to select only one category use radio buttons
const {useState, useEffect} = React;
const options = [
{ label: "Lifestyle", value: "lifestyle" },
{ label: "Area", value: "area" },
{ label: "Random", value: "random" },
{ label: "Comedy", value: "comedy" },
{ label: "Entertainment", value: "entertainment" }
];
const ChannelCategory = props => {
return (
<div>
{props.title}
<ul>
{props.options.map(option => (
<li key={props.key}>
<label>
{option.label}
<input
className={props.className}
name={option.value}
checked={props.checked.includes(option.value)}
onChange={e => props.onChange(e.target.checked, option.value)}
type="checkbox"
/>
</label>
</li>
))}
</ul>
</div>
);
};
function App() {
const [checked, setCheckBoxChecked] = useState([]);
const onAddCategory = (isChecked, value) => {
const temp = [...checked];
if (isChecked) {
temp.push(value);
setCheckBoxChecked(temp);
return;
}
setCheckBoxChecked(temp.filter(item => item !== value));
};
return (
<div className="App">
<ChannelCategory
key={"channel.key"}
options={options}
onChange={onAddCategory}
title="Add your chatroom to a category so that users can find it easily"
checked={checked}
/>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Radio buttons example
I want to figure out whether my code is wrong or a bug. I think there is no problem, but it does not work...
The code I used is:
https://codepen.io/cadenzah/pen/wvwYLgj?editors=0010
class ItemView extends React.Component {
constructor(props) {
super(props)
this.state = {
options: [{
id: 1,
name: "Item 1"
},
{
id: 2,
name: "Item 2"
}],
optionSelected: 2
}
}
toggleCheckbox(e) {
console.log(e.target.id)
if (this.state.optionSelected === e.target.id) {
this.setState({
optionSelected: undefined
})
} else {
this.setState({ optionSelected: e.target.id })
}
}
render() {
return (
<div className="container">
<div className="row">
<ItemList
options={this.state.options}
optionSelected={this.state.optionSelected}
toggleCheckbox={(e) => this.toggleCheckbox(e)} />
</div>
</div>
)
}
}
const ItemList = ({ options, optionSelected, toggleCheckbox }) => {
return (
<div className="col s12">
{
options.map((option, index) => (
<Item
key={index}
option={option}
checked={(optionSelected === (index + 1) ? true : false)}
toggleCheckbox={toggleCheckbox} />
))
}
</div>
)
}
const Item = ({ option, checked, toggleCheckbox }) => {
return (
<div className="card">
<div className="card-content">
<p><label htmlFor={option.id}>
<input
className="filled-in"
type="checkbox"
id={option.id}
onChange={toggleCheckbox}
checked={(checked ? "checked" : "")} />
<span>{option.id}. {option.name}</span>
</label></p>
</div>
</div>
)
}
Code explaination:
React code, with materialize-css used.
It is a simple checkbox feature with multiple items, restricted to select only one item. So, if I check one of them, every item except for what I just selected will be unchecked automatically. If I uncheck what I just checked, every item will stay unchecked.
The core logic is: in <ItemList /> component, there is a conditional props that determines whether each item has to be checked or not. It compares the id, and hand in true or false into its children. That checked props is used in <Item /> component to set the checked attribute of <input>.
Strange thing is, as I set default choice in the initial state, when I just run the application, the check feature works as I expected. But if I click one of them, it does not work.
What is the problem of it?
You can check if the selected option is the checked one like this:
checked={optionSelected === option.id}
And then you simply get it into your input like this:
<input checked={checked} />
Also, make sure to change your state ids into strings (the DOM element id is of type string):
options: [{
id: '1',
name: "Item 1"
},
{
id: '2',
name: "Item 2"
}],
optionSelected: '2'
https://codepen.io/AndrewRed/pen/gOYBVPZ?editors=0010
class ItemView extends React.Component {
constructor(props) {
super(props)
this.state = {
options: [{
id: 1,
name: "Item 1"
},
{
id: 2,
name: "Item 2"
}],
optionSelected: 2
}
}
toggleCheckbox(e) {
this.setState({
optionSelected : e.target.id
})
}
render() {
return (
<div className="container">
<div className="row">
<ItemList
options={this.state.options}
optionSelected={this.state.optionSelected}
toggleCheckbox={(e) => this.toggleCheckbox(e)} />
</div>
</div>
)
}
}
const ItemList = ({ options, optionSelected, toggleCheckbox }) => {
return (
<div className="col s12">
{
options.map((option, index) => (
<Item
key={index}
option={option}
checked={(optionSelected === (index + 1) ? true : false)}
toggleCheckbox={toggleCheckbox}
optionSelected = {optionSelected}
/>
))
}
</div>
)
}
const Item = ({ option, checked, toggleCheckbox,optionSelected }) => {
return (
<div className="card">
<div className="card-content">
<p><label htmlFor={option.id}>
<input
className="filled-in"
type="checkbox"
id={option.id}
onChange={toggleCheckbox}
checked={option.id == optionSelected ? "checked" : ""} />
<span>{option.id}. {option.name}</span>
</label></p>
</div>
</div>
)
}
function tick() {
ReactDOM.render(
<ItemView />,
document.getElementById('root')
);
}
tick()
COPY PASTE AND RUN
e.target.id is a string while index is a number. When you do a === comparison the type is also checked and these are not the same. This results in checked always being false after the initial state (which you set yourself as an int)
I'm building a shopping cart application and I ran into a problem where all my inputs have the same state value. Everything works fine but when I type in one input box, it's the same throughout all my other inputs.
I tried adding a name field to the input and setting my initial state to undefined and that works fine but the numbers don't go through.
How do we handle inputs to be different when they have the same state value? Or is this not possible / dumb to do?
class App extends Component {
state = {
items: {
1: {
id: 1, name: 'Yeezys', price: 300, remaining: 5
},
2: {
id: 2, name: 'Github Sweater', price: 50, remaining: 5
},
3: {
id: 3, name: 'Protein Powder', price: 30, remaining: 5
}
},
itemQuantity: 0
},
render() {
return (
<div>
<h1>Shopping Area</h1>
{Object.values(items).map(item => (
<div key={item.id}>
<h2>{item.name}</h2>
<h2>$ {item.price}</h2>
{item.remaining === 0 ? (
<p style={{ 'color': 'red' }}>Sold Out</p>
) : (
<div>
<p>Remaining: {item.remaining}</p>
<input
type="number"
value={ itemQuantity }
onChange={e => this.setState({ itemQuantity: e.target.value})}
placeholder="quantity"
min={1}
max={5}
/>
<button onClick={() => this.addItem(item)}>Add To Cart</button>
</div>
)}
</div>
))}
</div>
)
}
}
If you are using same state key for all input, All input take value from one place and update to one place. To avoid this you have to use separate state. I suppose you are trying to show input for a list of item.
To achive you can create a component for list item and keep state in list item component. As each component have their own state, state value will not conflict.
Here is an example
class CardItem extends Component {
state = {
number: 0
}
render() {
render (
<input type="text" value={this.state.number} onChange={e => this.setState({ number: e.target.value })} />
)
}
}
class Main extends Component {
render () {
const list = [0,1,2,3,4]
return (
list.map(item => <CardItem data={item} />)
)
}
}
This is a solution which the problem is loosely interpreted, but it does work without having to create another component. As you know, you needed to separate the state of each items in the cart. I did this by dynamically initializing and setting the quantity states of each item. You can see the state changes with this example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = { quantities: {} }
}
componentDidMount() {
let itemIDs = ['1', '2', '3', 'XX']; //use your own list of items
itemIDs.forEach(id => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: 0})});
})
}
render() {
let list = Object.keys(this.state.quantities).map(id => {
return (
<div>
<label for={id}>Item {id}</label>
<input
id={id}
key={id}
type="number"
value={this.state.quantities[id]}
onChange={e => {
this.setState({quantities: Object.assign(this.state.quantities, {[id]: e.target.value})})
}}
/>
</div>
);
})
return (
<div>
{list}
<div>STATE: {JSON.stringify(this.state)}</div>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root'></div>
You can modify the state structure to your liking.
Here is how I usually handle this scenario. You say that you get an array of items? Each item object should contain a key to store the value (count in my example). You can use a generic onChange handler to update an individual item in the array. So now, your state is managing the list of items instead of each individual input value. This makes your component much more flexible and it will be able to handle any amount of items with no code changes:
const itemData = [
{ id: 0, count: 0, label: 'Number 1' },
{ id: 1, count: 0, label: 'Number 2' },
{ id: 2, count: 0, label: 'Number 3' },
{ id: 3, count: 0, label: 'Number 4' }
];
class App extends React.Component {
state = {
items: itemData
}
handleCountChange = (itemId, e) => {
// Get value from input
const count = e.target.value;
this.setState( prevState => ({
items: prevState.items.map( item => {
// Find matching item by id
if(item.id === itemId) {
// Update item count based on input value
item.count = count;
}
return item;
})
}))
};
renderItems = () => {
// Map through all items and render inputs
return this.state.items.map( item => (
<label key={item.label}>
{item.label}:
<input
type="number"
value={item.count}
onChange={this.handleCountChange.bind(this, item.id)}
/>
</label>
));
};
render() {
return (
<div>
{this.renderItems()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
label {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can't use the same state for the both inputs. Try to use a different state for each one like that:
class App extends Component {
state = {
number: ""
}
render() {
return (
<div>
<input
type="number"
value={this.state.number}
onChange={e => this.setState({ number: e.target.value })}
/>
<input
type="number"
value={this.state.number2}
onChange={e => this.setState({ number2: e.target.value })}
/>
</div>
)
}
}
I have a object's array of users and i'm using map to show them, each user have a option buttons that is 'edit' and 'remove' options each option have a onlclick function that set a state to show another view so the code explain itselft
class App extends React.Component {
state = {
edit: false,
remove: false
}
handleEdit = () => {
this.setState({ edit: true })
}
handleRemove = () => {
this.setState({ remove: true })
}
cancelEdit = () => {
this.setState({ edit: false })
}
cancelRemove = () => {
this.setState({ remove: false })
}
renderEditItem = () => {
const {
state: {
edit,
remove
},
cancelEdit,
cancelRemove,
handleEdit,
handleRemove
} = this
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
if (remove) {
return (
<div>
<span>Remove view</span>
<br/>
<button onClick={cancelRemove}>Cancel</button>
</div>
)
}
return (
<div>
<button onClick={handleEdit}>Edit</button>
<br/>
<button onClick={handleRemove}>Remove</button>
</div>
)
}
renderUsers = () => {
const {
renderEditItem
} = this
const users = [
{
id: 1,
name: 'User1'
},
{
id: 2,
name: 'User-2'
},
{
id: 3,
name: 'User-3'
}
]
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{renderEditItem()}
</div>
</li>
</ul>
)
})
}
render () {
return (
<div>
{this.renderUsers()}
</div>
)
}
}
React.render(
<App />,
document.getElementById('app')
);
JSfiddle: Here
The issue is how can you see is, when i click on the button to set the state for edit or remove option, this will show the view for all the items,
and should be only the view that is clicked, i know the state change to true and is the same for all the items but i don't know how to set the state only for one entry any idea?
Thank you in advance.
Your problem is that the edit/remove state is singular and for the entire list. Each item in the list receives the same state here:
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
The single edit variable from the state is applied to each list item. If you want to individually set the edit state for each item, it will need to be kept track of with that item.
EX:
const users = [
{
id: 1,
name: 'User1',
edit: true
}]
This way each individual item will be able to tell what state it is in individually. User1 item will have an edit mode that is independent of the other users.
Then you can render something like this:
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{user.edit ? 'EDIT MODE' : 'NOT EDIT MODE'}
</div>
</li>
</ul>
)
})