I have a component that renders three custom radio buttons. The user can either submit the selected or clear (unselect) them, leaving with no selected radio buttons.
I tried some options with comparing the filterResult to the data.value, but without success. Here's a simplified code:
// imports
...
type Props = {
filterConfig: PropTypes.object,
filterValue: Proptypes.string,
onFilterChange: PropTypes.func
}
class Filter extends React.Component {
this.props = Props
this.state = {
filterValue: this.props.filterValue,
}
handleChange = (e) => {
this.setState({ filterValue: e.target.value })
}
handleSubmit = () => {
this.props.onFilterChange(this.state.filterValue)
this.refs.filterContainer.close()
}
handleClear = () => {
this.setState({ filterValue: '' })
}
renderOptions = () => {
const { data, name } = this.props.filterConfig
const options = data.map(
(o, i) => (
<div className='custom-radio' key={i}>
<input
id={`${name}-${i}`}
name={name}
onChange={this.handleChange}
type='radio'
value={o.value}
/>
<label htmlFor={`${name}-${i}`}>
<span />
{o.label}
</label>
</div>
)
)
return (
<div>
{options}
</div>
)
}
renderPickerNavigation = () => {
return (
<div>
<a
href='javascript:void(0)'
onClick={this.handleClear}
>
Clear
</a>
<a
href='javascript:void(0)'
onClick={this.handleSubmit}
>
Done
</a>
</div>
)
}
render = () => {
return (
<FilterWrapper
ref='filterWrapper'
>
{this.renderOptions()}
{this.renderPickerNavigation()}
</FilterWrapper>
)
}
}
The data I'm passing in is:
const filters = [
{
data: [{
label: 'Label 1',
value: 1
}, {
label: 'Label 2',
value: 2
}, {
label: 'Label 3',
value: 3
}],
name: 'userFilter'
}
]
EDIT: The click event on the native radio input works fine, so no need to change that to be on the custom radio (the span element) or the label.
You should begin with having a state variable that stores which radio is currently selected. The initial value for this should be null (or some other falsy value) if you want none to be pre-selected.
The reset button should trigger a function which resets this state variable back to the initial value.
Take a look at this simple demo, using custom css radio buttons as you requested:
class MyApp extends React.Component {
constructor() {
super();
this.state = {
selectedRadio: null,
products: [{id: 1, name: "foo"}, {id: 2, name: "bar"}, {id: 3, name: "baz"}]
}
}
select = (id) => {
this.setState({selectedRadio: id});
}
reset = () => {
this.setState({selectedRadio: null});
}
render() {
return (
<div>
{this.state.products.map(
(item) => {
return (
<div key={item.id}>
<input type="radio" name="myRadio" checked={this.state.selectedRadio === item.id} />
<label onClick={this.select.bind(this, item.id)}>{item.name}<span /></label>
</div>
);
}
)}
<button onClick={this.reset}>Reset</button>
</div>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
div {
margin: 10px 0;
}
input[type="radio"] {
display: none;
}
input[type="radio"]+label span {
display: inline-block;
width: 14px;
height: 14px;
margin-left: 4px;
vertical-align: middle;
cursor: pointer;
border-radius: 34%;
}
input[type="radio"]+label span {
background-color: #333;
}
input[type="radio"]:checked+label span {
background-color: orange;
}
<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>
Note: Since you are hiding the input element with css, you cannot have any listeners on it (e.g onChange or onClick). Instead, you should have onClick on the span that replaces it (see code below).
For a solution of how to reset all "traditional", non-css-only radio buttons, see the snippet below:
class MyApp extends React.Component {
constructor() {
super();
this.state = {
selectedRadio: null,
products: [{id: 1, name: "foo"}, {id: 2, name: "bar"}, {id: 3, name: "baz"}]
}
}
select = (id) => {
this.setState({selectedRadio: id});
}
reset = () => {
this.setState({selectedRadio: null});
}
render() {
return (
<div>
{this.state.products.map(
(item) => {
return (
<div key={item.id}>
<label>{item.name}</label>
<input type="radio" name="myRadio" onChange={this.select.bind(this, item.id)} checked={this.state.selectedRadio === item.id} />
</div>
);
}
)}
<button onClick={this.reset}>Reset</button>
</div>
);
}
}
ReactDOM.render(<MyApp />, 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>
This example can help you https://codepen.io/evoyan/pen/vxGBOw
Code:
class Radio extends React.Component {
constructor(props) {
super(props);
this.state = {selected: false};
}
toggle() {
const {onChange} = this.context.radioGroup;
const selected = !this.state.selected;
this.setState({selected});
onChange(selected, this);
}
setSelected(selected) {
this.setState({selected});
}
render() {
let classname = this.state.selected ? 'active' : ''
return (
<button type="button" className={classname} onClick={this.toggle.bind(this)}>
{this.state.selected ? 'yes' : 'no'}
</button>
);
}
}
Radio.contextTypes = {
radioGroup: React.PropTypes.object
};
class RadioGroup extends React.Component {
constructor(props) {
super(props);
this.options = [];
}
getChildContext() {
const {name} = this.props;
return {radioGroup: {
name,
onChange: this.onChange.bind(this)
}};
}
onChange(selected, child) {
this.options.forEach(option => {
if (option !== child) {
option.setSelected(!selected);
}
});
}
render() {
let children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
ref: (component => {this.options.push(component);})
});
});
return <div className="radio-group">{children}</div>;
}
}
RadioGroup.childContextTypes = {
radioGroup: React.PropTypes.object
};
class Application extends React.Component {
render() {
return (
<RadioGroup name="test">
<Radio value="1" />
<Radio value="2" />
<Radio value="3" />
</RadioGroup>
);
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
Related
I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!
I just want to show you the value it received
handleChange = (selectedOption, e) => {
e.preventDefault();
this.setState({
selectedOption
});
console.log(selectedOption);
}
render() {
let options = [];
if (this.state.cityName && this.state.cityName.length > 0) {
options = this.state.cityName.map((cityName) => {
return {
value: cityName.AdministrativeArea.LocalizedName,
label: cityName.AdministrativeArea.LocalizedName,
id: cityName.Key
};
})
}
return (
<div className="container">
<h1 htmlFor="Search">Search</h1>
<Select
name="htmlForm-field-name"
value={this.state.value}
onChange={(e) => this.handleChange}
defaultValue='Jerusalem'
options={options}
/>
<div>
<ul>
{this.state.value}
</ul>
How to do it with selectedOption
And another question is how do I do defaultValue within the API?
You are using this.state.value but updating this.state.selectedOption. Both of them must be the same. I think you are using Material UI Select so I do the same:
import React, { Component } from "react";
import { Select, MenuItem } from '#material-ui/core';
class Test extends Component {
state = {
selectedOption: ""
};
handleChange = selectedOption => {
this.setState({
selectedOption
});
console.log(selectedOption);
};
render() {
let options = [];
if (this.state.cityName && this.state.cityName.length > 0) {
options = this.state.cityName.map(cityName => {
return {
value: cityName.AdministrativeArea.LocalizedName,
label: cityName.AdministrativeArea.LocalizedName,
id: cityName.Key
};
});
}
const { selectedOption } = this.state;
return (
<div className="container">
<h1 htmlFor="Search">Search</h1>
<Select
name="htmlForm-field-name"
value={selectedOption}
onChange={e => this.handleChange(e.target.value)}
>
{options.map(o => <MenuItem value={o.value}>{o.label}</MenuItem>)}
</Select>
<div>
<ul>{selectedOption}</ul>
</div>
</div>
);
}
}
export default Test;
Working example:
import React, { Component } from "react";
import Select from "react-select";
class App extends Component {
handleChange = el => {
console.log(el.value);
};
render() {
return (
<div>
<Select
style={{ width: 100 }}
onChange={this.handleChange}
options={[
{ value: "green", label: "Green", color: "#36B37E" },
{ value: "forest", label: "Forest", color: "#00875A" },
{ value: "slate", label: "Slate", color: "#253858" },
{ value: "silver", label: "Silver", color: "#666666" }
]}
/>
</div>
);
}
}
export default App;
See example:
I try to build a to-do-list in react.
I have 2 components so far:
The first one handles the input:
import React from 'react';
import ListItems from './ListItems.js';
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
<ListItems
entries={this.state.entries}
/>
</div>
</div>
)
}
}
export default InputComponent;
The second one is about the actual entries in the list:
import React from 'react';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
this.listTasks = this.listTasks.bind(this);
}
lineThrough(item) {
console.log(item);
//item.style = {
// textDecoration: 'line-through'
//}
}
listTasks(item) {
return(
<li key = { item.index }>
<div
ref = { (r) => this._itemText = r }
style = {{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{ item.text }
</div>
<button onClick={ () => this.lineThrough(this._itemText) }>Done!</button>
<button>Dismiss!</button>
</li>
)
}
render() {
let items = this.props.entries;
let listThem = items.map( this.listTasks );
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
{ listThem }
</div>
</ul>
)
}
}
export default ListItems;
As you can see, i want to have two buttons for each entry, one for the text to be line-through, and one to delete the entry.
I am currently stuck at the point where i try to address a specific entry with the "Done!" button to line-through this entry's text.
I set a ref on the div containing the text i want to style and pass that ref to the onClick event handler.
Anyways, the ref seems to be overwritten each time i post a new entry...
Now, always the last of all entries is addressed. How can i properly address each one of the entries?
What would be the best practice to solve such a problem?
you could pass an additional prop with index/key of the todo into every item of your todo list. With passing event object to your handler lineThrough() you can now get the related todo id from the attributes of your event target.
Kind regards
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
}
this.done = this.done.bind(this);
}
done(id) {
this.state.todos[id].done();
}
render() {
return (
this.state.todos.map(t => <Todo item={t} onDone={this.done} />)
);
}
}
const Todo = ({item, onDone}) => {
return (
<div>
<h1>{item.title}</h1>
<button onClick={() => onDone(item.id)}>done</button>
</div>
)
}
You need map listItems then every list item get its own ref
import React from 'react';
import ReactDOM from 'react-dom';
class ListItems extends React.Component {
constructor() {
super();
this.lineThrough = this.lineThrough.bind(this);
}
lineThrough(item) {
item.style.textDecoration = "line-through";
}
render() {
return(
<ul style = {{
listStyle: 'none'
}}>
<div>
<li key={this.props.item.index}>
<div
ref={(r) => this._itemText = r}
style={{
width: 50 + '%',
display: 'inline-block',
backgroundColor: 'teal',
color: 'white',
padding: 10 + 'px',
margin: 5 + 'px',
borderRadius: 5 + 'px'
}}
>
{this.props.item.text}
</div>
<button onClick={() => this.lineThrough(this._itemText)}>Done!</button>
<button>Dismiss!</button>
</li>
</div>
</ul>
)
}
}
class InputComponent extends React.Component {
constructor(){
super();
this.state = {
entries: []
}
this.getText = this.getText.bind(this);
}
getText() {
if(this._inputField.value !== '') {
let newItem = {
text: this._inputField.value,
index: Date.now()
}
this.setState((prevState) => {
return {
entries: prevState.entries.concat(newItem)
}
})
this._inputField.value = '';
this._inputField.focus();
}
}
render() {
return(
<div>
<input ref={ (r) => this._inputField = r } >
</input>
<button onClick={ () => this.getText() }>Go</button>
<div>
{this.state.entries.map((item, index) => {
return <ListItems key={index} item={item} />
})}
</div>
</div>
)
}
}
Every time I click an option of size and click add to cart I would like to add the data of the selected object to this array cart. This currently works kinda but only one object can be added and when you try to do it again the old data disappears and is replaced with the new object.
I would like to keep odd objects in the array and add new objects too. How do I go about doing this?
index.js
export class App extends Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
render() {
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
,
{
name: "size three",
price: 3
}
];
const cart = [];
const addCart = function() {
cart.push(product[evenIndex]);
if (cart.length > 0) {
}
};
console.log("cart", cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={addCart}>Add to cart</button>
</div>
);
}
}
child.js
export class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
Here is my code on sandbox: https://codesandbox.io/s/14vyy31nlj
I've just modified your code to make it work. Here is the complete code. You need cart to be part of the state, so it does not initialize in each render, and to make the component render again when you add an element.
Remove the function to make it a method of the class:
addToCart() {
const selectedProduct = products[this.state.evenSelected];
this.setState({
cart: [...this.state.cart, selectedProduct]
});
}
And call it on render:
render() {
console.log("cart", this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = products[evenIndex] && products[evenIndex].price;
return (
<div>
<Child
product={products}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addToCart.bind(this)}>Add to cart</button>
</div>
);
}
}
Check that I have binded on render, which can bring performance issues in some cases. You should check this
Update
As devserkan made me notice (Thanks!), when you use the previous state to define the new state (for example adding an element to an array), it is better to use the updater function instead of passing the new object to merge:
this.setState(prevState => ({
cart: [...prevState.cart, products[selectedProduct]],
}));
For more info check the official docs.
I don't quite understand what are you trying to but with a little change here it is. I've moved product out of the components like a static variable. Also, I've changed the addCart method, set the state there without mutating the original one and keeping the old objects.
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
{
name: "size three",
price: 3
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null,
cart: [],
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
addCart = () => {
const evenIndex = this.state.evenSelected;
this.setState( prevState => ({
cart: [ ...prevState.cart, product[evenIndex] ],
}))
};
render() {
console.log(this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addCart}>Add to cart</button>
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.selectorRBox {
width: 260px;
height: 29.5px;
border: 1px solid #727272;
margin-top: 18px;
}
.selectedRBox {
width: 254px;
height: 29.5px;
margin-top: 14px;
border: 4px solid pink;
}
.selectorTextL {
font-family: "Shree Devanagari 714";
color: #727272;
cursor: pointer;
font-size: 18px;
}
<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>
I have two components named parent and child.
Child component contains checkbox's and it will send selected checkbox's values to Parent component
Child
class Child extends Component{
state = {
options: [],
selected_options: []
}
handleClose = (e) => {
this.props.add(this.state.selected_options)
}
handleChange = (event) => {
console.log(event.target.name);
if(event.target.checked==true){
event.target.checked=false
this.state.selected_options.slice(this.state.options.indexOf(event.target.value),1)
}
else{
event.target.checked = true
this.state.selected_options.push(event.target.value)
}
}
render() {
return(
<div>
<Grid>
{
this.state.options.map(value => {
return(
<Checkbox onChange={this.handleChange} label={value.name} value={value.name} checked={false} />
)
})
}
<Button color='green' onClick={this.handleClose} inverted>
<Icon name='checkmark' /> Apply
</Button>
</div>
);
}
}
and Parent Component
class Parent extends Component {
constructor(props){
super(props);
this.state = {
selected_options:[],
}
}
addOptions = (options) => {
this.setState({
selected_options: options
})
}
render() {
return(
<div>
<Child selected_options={this.state.selected_options} add={this.addOptions} />
</div>
);
}
}
When a checkbox is selected it must output its corresponding value in the console. but it showing undefined
Directly mutating the state or the event value is not the correct idea
You should be doing it like
class Child extends Component{
state = {
checkedState: []
options: [],
selected_options: []
}
handleClose = (e) => {
this.props.add(this.state.selected_options)
}
handleChange = (index, value) => {
var checkedState = [...this.state.checkedState];
if(checkedState[index] === undefined) {
checkedState.push(true)
}
else {
if(checkedState[index] === true) {
var selected_options=[...this.state.selected_options];
var idx = selected_options.findIndex((obj) => obj.name == value)
selected_options.splice(idx, 1);
checkedState[index] = false
} else {
var selected_options=[...this.state.selected_options];
selected_options.push(value);
}
}
this.setState({checkedState, selected_options})
}
render() {
return(
<div>
<Grid>
{
this.state.options.map((value, index) => {
return(
<Checkbox onChange={() => this.handleChange(index, value.name)} label={value.name} value={value.name} checked={this.state.checkedState[index] || false} />
)
})
}
<Button color='green' onClick={this.handleClose} inverted>
<Icon name='checkmark' /> Apply
</Button>
</div>
);
}
}
and from the Parent render the child and not the Parent
Parent
render() {
return(
<div>
<Child selected_options={this.state.selected_options} add={this.addOptions} />
</div>
);
}