Add multiple objects to an array? - javascript

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>

Related

Adding clicked items to new array in React

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!

Why can't I add any value to the array in state?

I have a lot of hits, which I want to add to an array once a hit is pressed. However, as far as I observed, the array looked like it got the name of the hit, which is the value. The value was gone in like half second.
I have tried the methods like building constructor, and doing things like
onClick={e => this.handleSelect(e)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
value={hit.name}
onClick={this.handleSelect.bind(this)}
defaultValue={hit.name}
and so on
export default class Tagsearch extends Component {
constructor(props) {
super(props);
this.state = {
dropDownOpen:false,
text:"",
tags:[]
};
this.handleRemoveItem = this.handleRemoveItem.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
}
handleSelect = (e) => {
this.setState(
{ tags:[...this.state.tags, e.target.value]
});
}
render() {
const HitComponent = ({ hit }) => {
return (
<div className="infos">
<button
className="d-inline-flex p-2"
onClick={e => this.handleSelect(e)}
value={hit.name}
>
<Highlight attribute="name" hit={hit} />
</button>
</div>
);
}
const MyHits = connectHits(({ hits }) => {
const hs = hits.map(hit => <HitComponent key={hit.objectID} hit={hit}/>);
return <div id="hits">{hs}</div>;
})
return (
<InstantSearch
appId="JZR96HCCHL"
apiKey="b6fb26478563473aa77c0930824eb913"
indexName="tags"
>
<CustomSearchBox />
{result}
</InstantSearch>
)
}
}
Basically, what I want is to pass the name of the hit component to handleSelect method once the corresponding button is pressed.
You can simply pass the hit.name value into the arrow function.
Full working code example (simple paste into codesandbox.io):
import React from "react";
import ReactDOM from "react-dom";
const HitComponent = ({ hit, handleSelect }) => {
return <button onClick={() => handleSelect(hit)}>{hit.name}</button>;
};
class Tagsearch extends React.Component {
constructor(props) {
super(props);
this.state = {
tags: []
};
}
handleSelect = value => {
this.setState(prevState => {
return { tags: [...prevState.tags, value] };
});
};
render() {
const hitList = this.props.hitList;
return hitList.map(hit => (
<HitComponent key={hit.id} hit={hit} handleSelect={this.handleSelect} />
));
}
}
function App() {
return (
<div className="App">
<Tagsearch
hitList={[
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
]}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
additionally:
note the use of prevState! This is a best practice when modifying state. You can google as to why!
you should define the HitComponent component outside of the render method. it doesn't need to be redefined each time the component is rendered!

Delete functionality is not working properly-Reactjs

i have created delete functionality,in which when user clicks delete[X] it has to get deleted from the respected row w.r.t datagrid view used in react
passing id as an parameter
used _find index(loadlash)
problem:
1) selected Rows are not getting deleted.
code:
onclick event
<div>
<button onClick={() => this.deleteHandler(params.value)}>X</button>
</div>
Delete code:
deleteHandler = (id) => {
const arrayPerson = this.props.rowData;
const index = _.findIndex(this.props.rowData, { id: id });
if (arrayPerson.indexOf(id) > -1) {
arrayPerson.splice(index, 1);
this.setState({ rows: arrayPerson });
}
can any one help me on this issue.
This is a working example for this what if different is that you are passing the data from parent to child for that u can do multiple thing.
The Parent Component.
class TestComp extends React.Component {
constructor() {
super();
this.state = { listItems: [{ id: 1, text: "abc" }, { id: 2, text: "bcd" }, { id: 3, text: "dec" }] }
this.handleRowClick = this.handleRowClick.bind(this);
}
handleRowClick(id) {
const listItems = this.state.listItems;
let copyListItems = [];
listItems.forEach(function (item) {
let objCopy = Object.assign({}, item);
copyListItems.push(objCopy);
});
let updatedArray = _.remove(copyListItems, function (item) { return item.id == id; });
this.setState({ listItems: copyListItems });
}
render() {
return (
<div>
<ChildComp
list={this.state.listItems}
deleteHandler={this.handleRowClick}
/>
</div>
)
}
}
The Child Component that is receiving List items as props and when clicked the parent handler is being called which will update the List items and setState of Parent which will update the list being provided to child component hence it will be rendered.
export class ChildComp extends React.Component {
render() {
let list = this.props.list.map((obj) =>
<div key={obj.id} id={obj.id} style={{ padding: "10px", backgroundColor: "grey", border: "1px solid black" }} onClick={() => { this.props.deleteHandler(obj.id) }}> {obj.text} </div>
);
return (
<div >
{list}
</div>
)
}
}

Dynamically wrapping a random number React components at run time

If I have say the following simple components:
const CompOne = (props) => {
return (
<div className="compOne">
{props.children}
</div>
);
};
const CompTwo = (props) => {
return (
<div className="compTwo">
{props.children}
</div>
);
};
const CompThree = (props) => {
return (
<div className="compThree">
{props.content}
</div>
);
};
Now during run time, after making an AJAX request the client receives information that gives the order in which components need to wrap into one another. The result of that AJAX request would look something like this:
let renderMap = [
{ component: CompOne, props: {} },
{ component: CompTwo, props: {} },
{ component: CompThree, props: { content: "hi there" } }
];
So the composition should flow by iterating through the array and composing one component into the next. e.g: CompOne(CompTwo(CompThree)))
Two important things to note when I tried creating a wrapping HOC to fix this issue:
Edit: Important detail I forgot to mention in the original post
1) The number of components to wrap will not be consistent. At times it could be 3, but other times as many as 4, 5, 6 components needed to wrap into each other
2) The order could be different each time
<CompOne>
<CompTwo>
<CompThree content="hi there">
</CompThree>
</CompTwo>
</CompOne>
So my resulting HTML would be:
<div className="compOne">
<div className="compTwo">
<div className="compThree">
hi there
</div>
</div>
</div>
I've tried various things but I can't get it to work once I start getting past just wrapping two components. Is this something I can even do in React?
Like the link that Arup Rakshit posted in the comments showed, you can use components that are stored in a variable - with JSX - as long as they are capitalized:
// in render()
const MyComp = props.someVariableContainingAComponent;
return <MyComp />;
With that in mind, one approach to your problem would be to iterate through all your components, starting with inner one, and then taking the each of the next to use as a wrapper for the previous one. Given the shape of your test data renderMap, and using Array.protype.reduce for the iteration, it could look something like this:
renderComponents(renderMap) {
const Component = renderMap
.reverse()
.reduce( (ComponentSoFar, {component, props}) => {
const Outer = component;
return () => (<Outer {...props} ><ComponentSoFar /></Outer>);
}, props => null ); // initial value, just a "blank" component
return ( <Component /> );
}
I have included a demo showing how both different number of components and varying order of nesting can be handled with this approach.
const CompOne = (props) => (
<div className="comp compOne"><p>One:</p>{ props.content || props.children } </div>);
const CompTwo = (props) => (
<div className="comp compTwo"><p>Two:</p> { props.content || props.children }</div>);
const CompThree = (props) => (
<div className="comp compThree"><p>Three:</p> { props.content || props.children }</div>);
const CompFour = (props) => (
<div className="comp compFour"><p>Four:</p> { props.content || props.children }</div>);
const CompFive = (props) => (
<div className="comp compFive"><p>Five:</p> { props.content || props.children }</div>);
const renderMap1 = [
{ component: CompOne, props: {} },
{ component: CompTwo, props: {} },
{ component: CompThree, props: {} },
{ component: CompFour, props: {} },
{ component: CompFive, props: { content: "hi there" } }
];
const renderMap2 = [].concat(renderMap1.slice(1,4).reverse(), renderMap1.slice(4))
const renderMap3 = renderMap2.slice(1);
class App extends React.Component {
constructor(props) {
super(props);
}
renderComponents(renderMap) {
const Component = renderMap
.reverse()
.reduce( (ComponentSoFar, {component, props}) => {
const Outer = component;
return () => (<Outer {...props} ><ComponentSoFar /></Outer>);
}, props => null ); // initial value, just a "blank" component
return ( <Component /> );
}
render() {
return ( <div>
{ this.renderComponents(renderMap1) }
{ this.renderComponents(renderMap2) }
{ this.renderComponents(renderMap3) }
</div> );
}
};
ReactDOM.render(<App />, document.getElementById('root'));
.comp {
border: 5px solid green;
padding: 5px;
margin: 5px;
display: inline-block;
}
.compOne { border-color: red;}
.compTwo { border-color: green;}
.compThree { border-color: blue;}
.compFour { border-color: black;}
.compFive { border-color: teal;}
<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>
Edit: New info was added to the question. Given that info, this approach doesn't work.
You can probably solve it using a Higher-order component (HOC), something like this:
const superWrapped = (Outer) => (Middle) => (Inner) => props => {
return (
<Outer>
<Middle>
<Inner content={props.content} />
</Middle>
</Outer>
)
};
Where you would later use it like this:
render() {
const SuperWrapped =
superWrapped(CompOne)(CompThree)(CompTwo); // any order is fine!
return (<SuperWrapped content="Something here.." /> );
}
Some minor adjustments on your components would be necessary for this to work. I've included a working demo below:
const superWrapped = (Outer) => (Middle) => (Inner) => props => {
return (
<Outer>
<Middle>
<Inner content={props.content} />
</Middle>
</Outer>)
};
const CompOne = (props) => {
return (
<div className="compOne">
<p>One:</p>
{props.children || props.content}
</div>
);
};
const CompTwo = (props) => {
return (
<div className="compTwo">
<p>Two:</p>
{props.children || props.content}
</div>
);
};
const CompThree = (props) => {
return (
<div className="compThree">
<p>Three:</p>
{props.children || props.content}
</div>
);
};
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const components = getComponentOrder();
const SuperWrapped1 =
superWrapped(components[0])(components[1])(components[2]);
const SuperWrapped2 =
superWrapped(components[2])(components[1])(components[0]);
return (
<div>
<SuperWrapped1 content="Hello, world!" />
<SuperWrapped2 content="Goodbye, world!" />
</div>
);
}
}
const getComponentOrder = () => {
return Math.random() < 0.5 ?
[CompOne, CompTwo, CompThree] :
Math.random() < 0.5 ?
[CompThree, CompOne, CompTwo] :
[CompTwo, CompOne, CompThree]
};
ReactDOM.render(<App />, document.getElementById('root'));
.compOne {
border: 5px solid red;
padding: 5px;
margin: 5px;
display: inline-block;
}
.compTwo {
border: 5px solid green;
padding: 5px;
margin: 5px;
display: inline-block;
}
.compThree {
border: 5px solid blue;
padding: 5px;
margin: 5px;
display: inline-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>
The easiest way to achieve this is to use a recursive function:
let renderMap = [
{ component: CompOne, props: {} },
{ component: CompTwo, props: {} },
{ component: CompThree, props: { content: "hi there" } }
];
function App() {
let index = 0;
let structure = "Hi There!"
function packing() {
if (index === renderMap?.length) return;
const Comp = renderMap[index].component
structure = <Comp>{structure}</Comp>;
index += 1;
packing();
}
packing();
return <>{structure}</>
}

React: Unselect all radio buttons via button click

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'));

Categories

Resources