Carousel with js and css in REACT - javascript

I created a carousel in html, css and js. The below are the respective codes.
HTML:
<div class="doorHardwareTile" id="dh">
<img class="mySlidesDH slide-to-right" src="images/Folder1/1.jpg?" alt="" />
<img class="mySlidesDH slide-to-right" src="images/Folder1/2.jpg?" alt="" />
<img class="mySlidesDH slide-to-right" src="images/Folder1/3.jpg?" alt="" />
<img class="mySlidesDH slide-to-right" src="images/Folder1/4.jpg?" alt="" />
<img class="mySlidesDH slide-to-right" src="images/Folder1/5.jpg?" alt="" />
<img class="mySlidesDH slide-to-right" src="images/Folder1/6.jpg?" alt="" />
<div class="overlayDH">
<img src="images/Folder1/0.png?" alt="" />
</div>
</div>
CSS:
.slide-to-right {
position: relative;
animation: animateright 0.5s;
}
#keyframes animateright {
from {
right: -300px;
opacity: 0;
}
to {
right: 0;
opacity: 1;
}
}
.doorHardwareTile {
display: flex;
overflow: hidden;
width: 560px;
height: 373px;
min-width: auto;
min-height: auto;
margin-bottom: 16px;
}
.mySlidesDH {
display: none;
}
.overlayDH {
position: absolute;
width: 560px;
height: 373px;
z-index: 800;
opacity: 0;
transition: 0.5s ease;
}
.overlayDH img {
object-fit: cover;
width: 100%;
height: 100%;
align-content: center;
}
.doorHardwareTile:hover .overlayDH {
opacity: 1;
}
JS:
$(function doorHardwareSS() {
var myIndex = 0;
carouselDH();
function carouselDH() {
var i;
var totalElements = document.getElementsByClassName("mySlidesDH");
for (i = 0; i < totalElements.length; i++) {
totalElements[i].style.display = "none";
}
myIndex++;
if (myIndex > totalElements.length) {
myIndex = 1;
}
totalElements[myIndex - 1].style.display = "block";
setTimeout(carouselDH, 5000);
}
});
This worked perfectly in the html, css and js.
However, when I tried to replicate the same in REACT. It throws error in the following line
totalElements[i].style.display = "none";
The error is "Uncaught TypeError: Cannot read property 'style' of undefined."
The images that I want in the carousel are retrieved from DB in a class based component.
I'm just a beginner in REACT. I would be thankful for any help in achiving the same result.
The below is the REACT code which is called in a class based component.
import React from "react";
const ImageSlide = props => {
if (
props.imagePath === undefined ||
props.imagePath === null ||
props.imagePath.length === 0
)
return null;
return (
<div className={props.styles}>
{props.imagePath.map(image => {
const path = props.svgsArray.find(str => str.indexOf(image.hash) > 1);
// console.log(path);
return (
<img
className="mySlidesDH slide-to-right"
key={image.id}
src={path}
alt={props.styles}
/>
);
})}
<div className={props.styles2}>
<img src={require("./images/1_Door_hardware/0.png?")} alt="" />
</div>
</div>
);
};
export default ImageSlide;
Please note that value doorHardwareTile is passed through props.styles and value overlayDH is passed through props.styles2

For React try the following package:
https://github.com/FormidableLabs/react-animations
For React Native:
https://facebook.github.io/react-native/docs/animations
Here is an example but its for react-native it might have the same idea of how you could do a simple right slide, but still you need a function to fade out or set opacity to zero of the container, once it is slide to right and have another function to set it to its original position with replacing picture inside the container.
First set position of container inside the this.state:
this.state = {
containerRightSlideAnim: new Animated.ValueXY({x: 0, y: 0})
}
And inside the constructor set position you would like for X:
this.containerRightSlide = Animated.timing(this.state.containerRightSlideAnim, {
toValue: {x: 200, y: 0},
duration: 10000,
//speed: 0.1,
easing: Easing.in(Easing.ease)
})
Make a function that triggers the right slide animation:
triggerRightSlide(){
this.containerRightSlide.start();
}
Add inside render:
render() {
const animatableRightSlideStyle = this.state.containerSlideAnim.getTranslateTransform()
return (<View>
<Animated.View style={[animatableRightSlideStyle]}></Animated.View>
</View>)
}

Here is a working prototype for Carousel with pagination in ReactJS Live Demo here
HTML
<h2>Prototype Carousel with pagination in ReactJS</h2>
<div id="app"></div>
JS React
import React from 'react'
import ReactDOM from 'react-dom'
class Pagination extends React.Component {
constructor( props ){
super( props );
}
paginationRender = ( source, activeItem, handleEvent ) => {
const items = source.map(( item, i ) => {
let itemClass = 'page-item';
if( item.id === activeItem ){
itemClass += ' active';
}
return <li key={i} className={ itemClass }>
<a className="page-link" href="#"
onClick={ e => handleEvent( e, 'clickItem', item )}>
{ i + 1 }</a>
</li>;
});
return <ul className="pagination pagination-sm justify-content-center">
<li className="page-item">
<a className="page-link" href="#"
onClick={e => handleEvent( e, 'prevItem', {}, items )}>Prev</a>
</li>
{items}
<li className="page-item">
<a className="page-link" href="#"
onClick={e => handleEvent( e, 'nextItem', {}, items )}>Next</a>
</li>
</ul>;
};
render() {
const { itemsSrc, activeItem, handleEvent } = this.props;
//console.info('MenuContent->render()', { source });
return <div>{this.paginationRender( itemsSrc, activeItem, handleEvent ) }</div>;
}
}
class Carousel extends React.Component {
constructor( props ){
super( props );
}
carouselRender = ( source, activeItem, handleEvent ) => {
//console.info('Carousel->carouselRender [0]', { source, state: this.state });
const indicators = source.map(( item, i ) => {
let itemClass = '';
if( item.id === activeItem ){
itemClass += ' active';
}
//console.info('Carousel->carouselRender [3]', { id: item.id, item, pageItemClass, activeItem: activeItem });
return <li key={i} data-target="#demo" data-slide-to="1" className={ itemClass }
onClick={ e => handleEvent( e, 'clickItem', item )}>></li>;
});
const imgs = source.map(( item, i ) => {
let itemClass = 'carousel-item';
if( item.id === activeItem ){
itemClass += ' active';
}
//console.info('Carousel->carouselRender [5]', { id: item.id, item, pageItemClass, activeItem: activeItem });
return <div key={i} className={ itemClass }>
<img src={item.src} className="img-fluid" alt="New York" />
</div>;
});
//console.info('Carousel->carouselRender [7]', { });
return <div id="demo" className="carousel slide" data-ride="carousel">
<ul className="carousel-indicators">
{ indicators }
</ul>
<div className="carousel-inner">
{ imgs }
</div>
<a className="carousel-control-prev" href="#demo" data-slide="prev">
<span className="carousel-control-prev-icon"
onClick={e => handleEvent( e, 'prevItem', {}, source )}>
</span>
</a>
<a className="carousel-control-next" href="#demo" data-slide="next">
<span className="carousel-control-next-icon"
onClick={e => handleEvent( e, 'nextItem', {}, source )}>
</span>
</a>
</div>;
};
render() {
const { itemsSrc, activeItem, handleEvent } = this.props;
//console.info('MenuContent->render()', { source });
return <div>{this.carouselRender( itemsSrc, activeItem, handleEvent ) }</div>;
}
}
const inputProps = {
itemsSrc: [
{ id: 0,
name: 'Los Angeles',
level: 'basic',
src: 'https://www.w3schools.com/bootstrap4/la.jpg'
},
{
id: 1,
name: 'Chicago',
level: 'basic',
src: 'https://www.w3schools.com/bootstrap4/chicago.jpg'
},
{
id: 2,
name: 'New York',
level: 'advanced',
src: 'https://www.w3schools.com/bootstrap4/ny.jpg'
},
],
};
class Wrapper extends React.Component {
constructor( props ){
super( props );
this.state = {
activeItem: 0,
};
}
handleEvent = ( e, actionType, item, items ) => {
e.preventDefault();
let itemsLength, activeItem;
switch( actionType ){
case 'clickItem':
//console.info('MenuContent->paginationRender', { actionType, id: item.id, item });
this.setState({ activeItem: item.id });
break;
case 'prevItem':
activeItem = this.state.activeItem;
if ( activeItem === 0 ){
break;
}
activeItem -= 1;
this.setState({ activeItem });
break;
case 'nextItem':
itemsLength = items.length;
activeItem = this.state.activeItem;
if (activeItem === itemsLength -1) {
break;
}
activeItem += 1;
this.setState({ activeItem });
break;
}
//console.info('MenuContent->handleEvent()', { actionType, item, items });
}
render(){
let props = this.props;
const { activeItem } = this.state;
props = { ...props, handleEvent: this.handleEvent, activeItem };
return <div className="App">
<div className="container-fluid">
<div className="row">
<div className="col-1">
</div>
<div className="col-10">
<Pagination { ...props }/>
<Carousel { ...props }/>
</div>
<div className="col-1">
</div>
</div>
</div>
</div>;
}
}
ReactDOM.render(<Wrapper { ...inputProps }/>, document.getElementById('app'));

Related

React - Parsing error: Unexpected token, expected ";"

I'm trying to call a method inside resize() when the screen width is less than or equal to 900, but I get the error Parsing error: Unexpected token, expected ";" how to fix this problem? https://ibb.co/cX0QxS1
https://ibb.co/bQSj1hq
import React, { Fragment } from 'react';
import less from "./css/lesson.module.css";
import "./css/activeLink.css";
import "./css/betaLesson.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { Navbar } from "../../../Navbar/Navbar";
import ReactHtmlParser from 'react-html-parser';
import * as Icon from 'react-bootstrap-icons';
import styled from "styled-components";
import { slide as Menu } from 'react-burger-menu'
import { NavbarMobile } from '../../../Navbar/Mobile_Navbar/NavbarMobile';
const NextPage = styled.button`
display: flex;
align-items: center;
font-family: 'Roboto';
font-weight: 500;
letter-spacing: 0.2px;
color: #ff7b77d9;
padding: 9px 22px;
outline: none;
cursor: pointer;
text-align: center;
text-decoration: none;
font-size: 13px;
border: 1px solid #ff7b77d9;
border-radius: 2px;
`;
export class Lessons extends React.Component {
constructor(props) {
super(props);
this.state = {
indexDescription: 1,
listActiveIndex: 1,
sidebarMobile: true,
menuMobileIsOpen: false,
}
}
componentDidMount() {
window.addEventListener("resize", this.resize.bind(this));
this.resize();
}
resize() {
if (window.innerWidth > 900) {
this.setState({ sidebarMobile: true })
this.setState({ menuMobileIsOpen: false })
} else {
this.setState({ sidebarMobile: false })
}
}
hideMenu() {
this.setState({ sidebarMobile: false })
}
componentWillUnmount() {
window.removeEventListener("resize", this.resize.bind(this));
}
changeDescription(index) {
this.setState({ indexDescription: index, listActiveIndex: index })
}
nextPage() {
// next page
this.setState({ indexDescription: this.state.indexDescription + 1, listActiveIndex: this.state.indexDescription + 1 })
}
prevPage() {
// next page
this.setState({ indexDescription: this.state.indexDescription - 1, listActiveIndex: this.state.indexDescription - 1 })
}
showsidebarMobile = () => {
this.setState({ sidebarMobile: !this.state.sidebarMobile })
}
menuMobileIsOpen = () => {
this.setState({ menuMobileIsOpen: !this.state.menuMobileIsOpen })
}
HideMenuMobileIsOpen = () => {
this.setState({menuMobileIsOpen: false})
}
showSettings(event) {
event.preventDefault();
}
render() {
const listLessons = this.props.lesson.map((item, index) => {
// active link
const className = this.state.listActiveIndex === index ? 'list_active' : null;
return (
<Fragment key={index}>
{item.title && (
<li className={less.courseTitle}>
<div>
<p>{item.title}</p>
</div>
</li>
)}
{item.titleName && (
<li className={className} onClick={this.changeDescription.bind(this, index)}>
<div className={less.sidebar_list}>
<div style={{ display: "flex" }}>
<FontAwesomeIcon className={less.item_icon} icon={item.iconName} />
</div>
<div className={less.titleName}>
<div>
<p>{item.titleName}</p>
</div>
</div>
</div>
</li>
)}
</Fragment>
);
});
return (
<>
<div className="abc">
<div>
<Navbar color="blue" bg="tomato" centerFlexNavbarContainer="flex" LiItem="NavBarli" MainStream="MainStream"
navbarSearchPage="Search" navbarHomePage="Home" NavbarMobileIconsBlock="mobile"
centerHeadlineNavbarColumn="center" showsidebarMobile={this.showsidebarMobile} menuMobileIsOpen={this.menuMobileIsOpen} />
<div>
{
this.state.menuMobileIsOpen ? <NavbarMobile /> : null
}
</div>
</div>
<div className={less.wrapper}>
<Menu isOpen={this.state.sidebarMobile} >
<main id="page-wrap">
<div className={less.sidebar}>
<div>
<ul onClick={this.hideMenu.bind(this)}>
{listLessons}
</ul>
</div>
</div>
</main>
</Menu>
<div>
<div className={less.main_content}>
<div className={less.main_inside_content}>
<div className={less.header}>
<div className={less.header_next_page}>
<div>
<h2>{this.props.lesson[this.state.indexDescription]["heading"]}</h2>
</div>
</div>
</div>
<div className={less.info} onClick={this.HideMenuMobileIsOpen.bind(this)}>
<div className={less.description}>
{
ReactHtmlParser(this.props.lesson[this.state.indexDescription]["data"]["description"])
}
<div className={less.btn_Next_Prev_Container}>
<div>
{
this.state.indexDescription >= 2 ?
<NextPage onClick={this.prevPage.bind(this)} > <Icon.ArrowLeft className={less.arrowLeft} /> Back </NextPage>
:
null
}
</div>
<div>
<NextPage onClick={this.nextPage.bind(this)} > Next <Icon.ArrowRight className={less.arrowRight} /> </NextPage>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>
);
}
}
hideMenu is invalid syntax. You're kind of declaring a function in the style of a class method inside of a class method, and also trying to setState inside of the method without actually calling it.
if (window.innerWidth < 900) {
hideMenu() {
this.setState({ sidebarMobile: false })
}
}
You'd need to either...
a) Forgo attempting to call a function and simply set state:
if (window.innerWidth < 900) {
this.setState({ sidebarMobile: false })
}
b) Declare your function as a class method and call it within your condition, like this:
hideMenu() {
this.setState({ sidebarMobile: false })
}
resize() {
if (window.innerWidth > 900) {
this.setState({ sidebarMobile: true })
this.setState({ menuMobileIsOpen: false })
} else {
this.setState({ sidebarMobile: false })
}
if (window.innerWidth < 900) {
this.hideMenu()
}
}
If you're trying to create hideMenu inside of your class method, you would do this:
resize() {
const hideMenu = () => this.setState({ sidebarMobile: false })
if (window.innerWidth > 900) {
this.setState({ sidebarMobile: true })
this.setState({ menuMobileIsOpen: false })
} else {
this.setState({ sidebarMobile: false })
}
if (window.innerWidth < 900) {
hideMenu()
}
}

How to reset interval and call api with interval in a function in react js

Hello I wanted to reset my set interval whenever my function is been called iam calling my interval in componentDidmount in react js and I want it to reset previous interval whenever that function is called iam also changing the value of api in that same function and now i want it to behave with the changes. The function is all three of the keyPressed Below is my code.
import React, { Component } from "react";
import { HotKeys } from "react-hotkeys";
import Axios from "axios";
class Test extends Component {
constructor(props) {
super(props);
this.state = {
seconds: 3,
color: null,
items: [],
currentItem: [],
index: 0
};
this.handleChange = this.handleChange.bind(this);
this.keyMap = {
DONT_APPLIES: ["a", "A"],
APPLIES: ["s", "S"],
STRONGLY_APPLIES: ["d", "D"]
};
this.handlers = {
DONT_APPLIES: e => {
this.keyPressed();
},
APPLIES: e => {
this.keyPressed1();
},
STRONGLY_APPLIES: e => {
this.keyPressed2();
}
};
}
keyPressed(e) {
const { index, items } = this.state;
const nextQuestion = index + 1;
this.setState({ currentItem: items[nextQuestion], index: nextQuestion });
console.log(this.state);
}
keyPressed1(e) {
const { index, items } = this.state;
const nextQuestion = index + 1;
this.setState({ currentItem: items[nextQuestion], index: nextQuestion });
console.log(this.state);
}
keyPressed2(e) {
const { index, items } = this.state;
const nextQuestion = index + 1;
this.setState({ currentItem: items[nextQuestion], index: nextQuestion });
console.log(this.state);
}
handleChangeColor = newColor => {
this.setState({
color: newColor
});
};
componentDidMount() {
this.myInterval = setInterval(() => {
const { seconds } = this.state;
if (seconds > 0) {
this.setState(({ seconds }) => ({
seconds: seconds - 1
}));
} else if (seconds == 0) {
return this.handleChangeColor("#840000");
}
}, 1000);
Axios.get("https://jsonplaceholder.typicode.com/users")
.then(response => {
console.log(response);
this.setState({
items: response.data.result.items,
currentItem: response.data.result.items[0],
index: 0
});
})
.catch(error => {
console.log(error);
});
}
componentWillUnmount() {
clearInterval(this.myInterval);
}
render() {
const { seconds } = this.state;
const timebox = {
width: "50px",
height: "20px"
};
const { currentItem } = this.state;
return (
<HotKeys keyMap={this.keyMap} handlers={this.handlers}>
<div id="wrapper" class="myleaty">
<div class="myleaty-holder">
<div class="container">
<div style={{ background: this.state.color }} class="myleaty-box">
<div style={timebox}>
<h2>{seconds}</h2>
</div>
<div class="logo">
<a href="#">
<img src="images/logo.png" alt="leaty" />
</a>
</div>
<p key={currentItem.id}>{currentItem.pqDetail}</p>
<div class="btn-row">
<button className="btn btn1">Does not Applies</button>
<button className="btn">Applies</button>
<button className="btn btn1">Strongly Applies</button>
</div>
</div>
</div>
</div>
</div>
</HotKeys>
);
}
}
export default Test;

How to change style of single component onClick in react.js

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>
)
}
}

How to check state inside styled-component?

Instead of styling the Li component via inline style, I want to do it via styled-component. How can I check the state and then assign the color red if the current Li is the selected language?
const Li = styled.li`
border: 0;
//Set color to red if this component is selected
${this.state => this.state.selectedLanguage`
color: 'red';
`}
`;
class Popular extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All'
}
this.updateLanguage = this.updateLanguage.bind(this);
}
updateLanguage(lang) {
this.setState(function() {
return {
selectedLanguage: lang
};
});
}
render() {
const languages = ['All', 'Javascript', 'Java', 'Go', 'Rust'];
return (
<ul className={`list-group d-flex flex-row justify-content-center ${this.props.className}`}>
{languages.map(function(lang){
return (
<Li
key={lang}
// style={lang === this.state.selectedLanguage ? {color: '#d00d1b'} : null}
onClick={this.updateLanguage.bind(null, lang)}
className={`list-group-item p-2 ${this.props.className}`}>
{lang}
</Li>
)
}, this)}
</ul>
);
}
}
export default Popular;
code is based from tyler mcginnis - react fundamentals
Passing down isSelected as props then calling the css helper if true
const selectedLanguageStyle = css`
color: '#d00d1b'
`
const Li = styled.li`
border: 0;
${(props) => props.isSelected && selectedLanguageStyle}
`;
class Popular extends Component {
constructor(props) {
super(props);
this.state = {
selectedLanguage: 'All'
}
this.updateLanguage = this.updateLanguage.bind(this);
}
updateLanguage(lang) {
this.setState(function() {
return {
selectedLanguage: lang
};
});
}
render() {
const languages = ['All', 'Javascript', 'Java', 'Go', 'Rust'];
return (
<ul className={`list-group d-flex flex-row justify-content-center ${this.props.className}`}>
{languages.map(function(lang){
return (
<Li
key={lang}
isSelected={lang === this.state.selectedLanguage}
onClick={this.updateLanguage.bind(null, lang)}
className={`list-group-item p-2 ${this.props.className}`}>
{lang}
</Li>
)
}, this)}
</ul>
);
}
}
export default Popular;

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