I have 3 circles that should change src of image when toggled, currently all circles toggle the src when one is clicked. I could use some help with how to get that problem fixed.
This is what i got right now
this.state = {
fillCircle: false
};
circleHandler = () => {
this.setState({ fillCircle: !this.state.fillCircle });
};
render() {
let circles = [];
for (var i = 0; i < 3; i++) {
circles.push(
<img
key={i}
className="circle"
onClick={this.circleHandler.bind()}
src={this.state.fillCircle ? filled_circle : circle}
alt=""
/>
);
}
return (
<div>
{circles}
</div>
);
This is because each of those elements needs it's own state. Write a separate component for each circle. Then you would do
circles.push(<CircleComponent key={index} />)
Inside CircleComponent you would have your state for each Component and toggle for each one of them.
Don't forget about keys as well.
Didn't try it, but you should get the concept.
this.state = {
fillCircle: [false,false,false]
};
circleHandler = (i) => {
this.setState((prev) => {
if(prev.fillCircle[i] == false)prev.fillCircle = [false,false,false] // <--
prev.fillCircle[i] = !prev.fillCircle[i];
return prev.fillCircle;
});
};
render() {
let circles = [];
for (var i = 0; i < 3; i++) {
circles.push(
<img
key={i}
className="circle"
onClick={()=> this.circleHandler(i) }
src={this.state.fillCircle[i] ? filled_circle : circle}
alt=""
/>
);
}
return (
<div>
{circles}
</div>
);
#Dille Please try below code this should solve your problem.
this.state = {
activeCircle: null
};
circleHandler = (i) => {
this.setState({ activeCircle: i});
};
render() {
let circles = [];
for (var i = 0; i < 3; i++) {
circles.push(
<img
key={i}
className="circle"
onClick={this.circleHandler.bind(this, i)}
src={this.state.activeCircle === i ? filled_circle : circle}
alt=""
/>
);
}
return (
<div>
{circles}
</div>
);
Related
I would like to sort table rows when clicking on table headers. I have the code working on a regular html/javascript snippet here. However it is not working as a React method. I think the problem is the use of the method .querySelectorAll() and .parentNode. I have already replaced the method .getElementById() with this.refs.rows by adding a ref to the <tbody> but I don't think one ref can point to multiple elements (to get all the <td> elements). I am looking for a similar way to replace both of the methods which don't seem to work. Or is it another problem entirely?
Edit:
Added the entire React component as asked in the comments
import React from "react";
import { Row, Col, Table } from "reactstrap";
export class ComparePlayers extends React.Component {
constructor(props) {
super(props);
this.state = {
players: [],
};
}
componentDidMount() {
fetch("/players")
.then((response) => response.json())
.then((state) => {
this.setState({ players: state });
});
}
handleSort(n) {
let rows, switching, i, x, y, shouldSwitch;
switching = true;
while (switching) {
switching = false;
rows = this.refs.rows; /* replaced document.getElementById("myTable").rows; */
for (i = 0; i < rows.length; i++) {
shouldSwitch = false;
x = rows[i].querySelectorAll("td")[n]; // how to replace?
y = rows[i + 1].querySelectorAll("td")[n]; // how to replace?
if (!isNaN(Number(x.innerHTML))) {
if (Number(x.innerHTML) > Number(y.innerHTML)) {
shouldSwitch = true;
break;
}
} else {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); // how to replace?
switching = true;
}
}
}
render() {
return (
<Row>
<Col>
<Table bordered>
<thead>
<tr>
{this.state.players.length > 0 &&
Object.keys(this.state.players[0]).map((key, id) => (
<th
key={"header_" + key}
onClick={() => this.handleSort(id)}
>
{key.toUpperCase()}
</th>
))}
</tr>
</thead>
<tbody ref="rows">
{this.state.players.length > 0 &&
this.state.players.map((player, id) => {
return (
<tr key={"row" + id}>
{Object.values(player).map((value) => (
<td key={"table_value_" + value}>{value}</td>
))}
</tr>
);
})}
</tbody>
</Table>
</Col>
</Row>
);
}
}
UPDATE AND SOLUTION:
I used #MahdyAslamy 's answer and adapted to my state which is an array of objects. I used this tutorial to sort array of objects according to property values. Here is the final code:
handleSort(el) {
const compareValues = (key) => {
return function innerSort(a, b) {
if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
// property doesn't exist on either object
return 0;
}
const varA = typeof a[key] === "string" ? a[key].toUpperCase() : a[key];
const varB = typeof b[key] === "string" ? b[key].toUpperCase() : b[key];
let comparison = 0;
if (varA > varB) {
comparison = 1;
} else if (varA < varB) {
comparison = -1;
}
return comparison;
};
};
const sorted = this.state.players.sort(compareValues(el));
this.setState({
players: sorted
});
}
It's not good approach to sort table by changing tags position on dom. react suggest to use states and usual component life cycle for changing appearance.
for example:
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [5,6,8,9,6,5,4,22,4]
}
}
render() {
return (
<div className="game">
<button onClick={() => this.setState({ list: this.state.list.sort() })}>sort</button>
<ul>
{this.state.list.map(el => <li>{el}</li>)}
</ul>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Only sort your list and then will show sorted list on render.
I basically need to create an array or make a list of items that have been clicked in this div imgCard. its for a game where i have to update score when a button is clicked. If its clicked more than once the game ends. My only guess is that I should pass two functions on onClick and create an array of items already clicked.
import React, { Component, Fragment } from 'react'
import Header from './components/header'
import characters from "./characters.json"
import ImageCard from "./components/ImageCard"
import Wrapper from './components/wrapper'
class Game extends Component {
state = {
characters,
Header,
}
shuffle = () => {
var array = this.state.characters;
var ctr = array.length;
var temp;
var index;
var isClicked = []
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = array[ctr];
array[ctr] = array[index];
array[index] = temp;
}
this.setState({
characters: array
});
};
render() {
return (
<Fragment>
<Header />
<Wrapper>
<div className="imgContainer" >
{this.state.characters.map(character => (
<div className="imgCard" onClick={this.onClick} showAlert={this.id}>
<ImageCard
key={character.id}
image={character.image}
width={'120px'}
>
</ImageCard >
</div>
))}
</div>
</Wrapper>
</Fragment>
);
}
}
export default Game;
<div className="imgCard"
onClick={()=>{
this.firstFunction();
this.secondFunction(); //two functions are called in onClick callback
}}
showAlert={this.id}>
<ImageCard
key={character.id}
image={character.image}
width={'120px'}
>
</ImageCard >
</div>
You can just call a function which contains the two function you want to run. With your code it would look something like below:
class Game extends Component {
state = {
characters,
Header,
}
shuffle = () => {
var array = this.state.characters;
var ctr = array.length;
var temp;
var index;
var isClicked = []
while (ctr > 0) {
index = Math.floor(Math.random() * ctr);
ctr--;
temp = array[ctr];
array[ctr] = array[index];
array[index] = temp;
}
this.setState({
characters: array
});
};
buttonOnClickHandler = () => {
// Destructure state or props here.
//Performe anything else
//run the functions
this.firstFunctionYouWantToRun();
this.secondFunctionYouWantToRun();
};
render() {
return (
<Fragment>
<Header />
<Wrapper>
<div className="imgContainer" >
{this.state.characters.map(character => (
<div className="imgCard" onClick={this.buttonOnClickHandler} showAlert={this.id}>
<ImageCard
key={character.id}
image={character.image}
width={'120px'}
>
</ImageCard >
</div>
))}
</div>
</Wrapper>
</Fragment>
);
}
}
export default Game;
Like this, you can call
onClick={() => {this.onClick() ; this.somefucntionname()}}
I am working on React.
I have a method called on_render_cell, where I have created a div and I am inserting image in that div based on some conditions.
This is the approach I am following to insert image in the div.I get the desired images. Now my goal is:
To open a modal when the image is clicked. When I try to assign onClick={this.openModal()}, the modal doesn't pops up. I noticed that this is because this points to the <img>. Can anyone help me how can I call openModal() on click handler of image tag written in result variable of on_render_cell()?
export class ScheduleManager extends SampleBase {
constructor() {
super(...arguments);
this.state = {
visible : false
}
}
openModal() {
this.setState({
visible : true
});
}
closeModal() {
this.setState({
visible : false
});
}
on_render_cell(args) {
let ele = document.createElement('div');
var sum=0;
var cursor=Event.find({Teacher:teacherName});
cursor.forEach(function(event){
if(convert(event.StartTime)===convert(args.date))
sum = sum + parseFloat(event.Duration);
});
let hoursPerDay = sum/60;
let percentHours=(hoursPerDay/maxHoursPerDay)*100;
let result ='';
if(percentHours===0)
{
result='<img id="aaa" ref="abc" className="weather-image" height="25px" width="25px" src="http://www.clker.com/cliparts/h/e/s/t/j/U/grey-circle.svg.thumb.png" onClick={console.log(this)} />';
}
else if(percentHours<=25)
{
result = '<img className="weather-image" src= "http://www.clker.com/cliparts/0/w/P/n/G/3/blue-circle-th.png" />';
}
else if(percentHours>25 && percentHours<=50)
{
result='<img className="weather-image" src= "http://www.clker.com/cliparts/o/b/y/x/Z/c/yellow-dot-th.png" />';
}
else if(percentHours>50 && percentHours<=75)
{
result = '<img className="weather-image" src= "http://www.clker.com/cliparts/1/A/W/q/h/h/orange-circle-th.png" />';
}
else
{
result='<img className="weather-image" src= "http://www.clker.com/cliparts/3/m/Q/u/P/J/red-circle-small-new.svg.thumb.png" />';
}
ele.innerHTML = result;
(args.element).appendChild(ele.firstChild);
}
render(){
return (
<div>
<input type="button" value="Open" onClick={() => this.openModal()} />
<Modal
visible={this.state.visible}
width="400"
height="300"
effect="fadeInUp"
onClickAway={() => this.closeModal()}
>
<div>
<h1>Title</h1>
<p>Some Contents</p>
<a href="javascript:void(0);" onClick={() => this.closeModal()}>Close</a>
</div>
</Modal>
<p>Grey circle denotes no classes for the day</p>
<p>Blue circle denotes less than 25% occupancy for the day</p>
)
}
}
Goal : Open Modal on image click. i.e, access openModal() of component
You can give some var like myRef to your componen as a prop, then in onComponentDidMount of your component assign to prop myRef current instance (this). this.props.myRef = this, then in your parent component you can use methods of myRef. For more you can check this post React.js - access to component methods
sorry but you are not doing it right. when you write:
onClick={() => this.openModal()}
you are creating a new function each time the element renders. instead, I suggest you to bind the method inside the constructor:
this.openModal = this.openModal.bind(this);
and inside the input write:
onClick={this.openModal}
this way you won't create a new function each time the elements renders, and the openModal this will point to the element itself.
If I understand correctly the problem is in this line:
result='<img id="aaa"... onClick={console.log(this)} />';
have you tried pass JSX instead of a string? bind the function openModal in the constructor, and pass to the img the function this.openModal. I think that if you will pass JSX instead of a string it will work
UPDATE
ok so I took the code from Here and edited it a bit:
export class CellTemplate extends SampleBase {
getCellContent(date) {
if (date.getMonth() === 10 && date.getDate() === 23) {
return ( < div > < img src = "src/schedule/images/thanksgiving-day.svg" / > < div className = "caption" > Thanksgiving day < /div></div > );
}
cellTemplate(props) {
if (props.type === "monthCells") {
return ( < div className = "templatewrap" > {
this.getCellContent(props.date)
} > < /div>);
}
return;
}
render() {
return ( < div className = 'schedule-control-section' >
<
div className = 'col-lg-12 control-section' >
<
div className = 'control-wrapper' >
<
ScheduleComponent cssClass = 'cell-template'
width = '100%'
height = '650px'
selectedDate = {
new Date(2017, 11, 15)
}
cellTemplate = {
this.cellTemplate.bind(this)
} >
<
ViewsDirective >
<
ViewDirective option = 'Month' / >
<
/ViewsDirective> <
Inject services = {
[Month, Resize, DragAndDrop]
}
/> <
/ScheduleComponent> <
/div> <
/div> <
/div>);
}
}
in cellTemplate you can see how to call getCellContent correct, and in getCellContent you can see how I would have return JSX.
I used this.handleClick in onClick but it alerts a mistake.(this is not difined)
var Buttons = React.createClass({
getInitialState() {
return {
field: ':P '
}
},
handleClick(field) {
return this.setState = {
field
}
},
render() {
let count = 1;
return ( < div >
< h1 > {
this.state.field
} < /h1> {
buttonValues.map(function(val) {
return <button key = {
count++
}
onClick = {
Buttons.handleClick
} > {
val
} < /button>
})
} < /div>
)
}
})
ReactDOM.render( < Buttons / > , document.getElementById('app'));
You have to replace
this.setState = {field}
by
this.setState({field: field})
You cannot use Buttons.handleClick() directly because Buttons is a class. You need to instantiate the class before accessing it's method. And This won't work with React.
So, the best way is to use this to access the local methods within the class. And this.setState() is a function, to which you need to pass the new state object. Hope it helps!
<script src="https://unpkg.com/babel-core#5.8.38/browser.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
<script type="text/babel">
var buttonValues = ['hello','there']
var Buttons = React.createClass({
getInitialState() {
return {
field: ':P '
}
},
handleClick(field) {
this.setState({
field
})
},
render() {
var _this = this
return ( < div >
< h1 > {
this.state.field
} < /h1> {
buttonValues.map(function(val, i) {
return <button key = {
i
}
onClick = {() =>
_this.handleClick(val)
} > {
val
} < /button >
})
} < /div>
)
}
})
ReactDOM.render( < Buttons / > , document.getElementById('app'));
</script>
you are not passing any arguments to your function, also use this.handleClick instead of calling the class.
onClick = {this.handleClick.bind(null, val)}
also setState is a function
this.setState({field: field})
I have a react component and the requirement is to setState before render on update (url hash change). Below is code snippet:
componentConfig: function() {
.....
this.setState({rows: rows});
this.setState({loadMoreBtn: loadMoreIsVisible})
},
I was it working before, I was calling this method from getInitialState and it was working fine. getInitialState fires only once so I was not able to update it on url change. I tried various other inbuilt update methods such as componentWillReceiveProps but they are one step behind. Seems like render happens before this method gets called. I also tried to call it from render but obviously states get confused and it breaks.
As this image demonstrates, componentRillReceiveProps always behind render. I need something that fires before render each time on url update. Hope it makes sense.
or in other words, I would like to fire getInitialState on hash change.
var React = require('react'),
projectsData = require('./../projects'),
ProjectsRow = require('./projects_row'),
itemsInRow = 3,
noOfDefaultRows = 2,
projects = [],
currentProjects = [],
currentPageRows,
currentParamKey;
var Projects = React.createClass({
componentWillMount: function() {
this.componentConfig(this.props);
},
componentWillReceiveProps: function(nextProps){
this.componentConfig(nextProps);
},
componentConfig: function(props) {
var rows = [],
currentParamKey = 'projects',
loadMoreIsVisible = true,
i;
if(props.params.key) {
currentParamKey = props.params.key;
}
projects = projectsData.getByKey(currentParamKey);
projectsData.currentState[currentParamKey] = noOfDefaultRows;
currentProjects = projects.slice(); //Create a copy or array
noOfDefaultRows = projectsData.currentState[currentParamKey] || noOfDefaultRows;
for (i = 0; i < noOfDefaultRows; i++) {
if(currentProjects.length) {
rows.push(currentProjects.splice(0, itemsInRow));
}
}
currentProjects.length ? loadMoreIsVisible = true : loadMoreIsVisible = false;
this.setState({rows: rows});
this.setState({loadMoreBtn: loadMoreIsVisible})
console.log('Finished executing componentConfig and currentParamKey = ' ,currentParamKey);
},
loadMoreProjects: function(e) {
e.preventDefault();
var addRow = this.state.rows;
if(currentProjects.length) {
currentPageRows++;
addRow.push(currentProjects.splice(0, itemsInRow));
this.setState({rows: addRow});
}
if(!currentProjects.length) {
this.setState({loadMoreBtn: false})
}
},
render: function() {
console.log('Now in render and currentParamKey = ' ,currentParamKey);
var projectUrl;
//currentParamKey = this.props.params.key;
if(currentParamKey === 'projects') {
projectUrl = '#/project';
} else {
projectUrl = '#/project/' + currentParamKey
}
return (
< div className="projects">
< div className = "jumbotron" >
< div className = "container" >
< h1 > Projects < /h1>
< /div>
< /div>
< div className = "container" >
{this.state.rows.map(function(row, i) {
return <ProjectsRow url={projectUrl} row={row} key={i} />
}.bind(this))}
< /div>
< div className = "container text-center" >
<a id="loadMore" className= {this.state.loadMoreBtn ? 'linkStyle1' : 'hide'}
onClick = {this.loadMoreProjects}
role="button" > <i className="fa fa-angle-down"></i><span>Load More Projects</span>
</a>
<br />
<br />
<div className="contact-me-link">
<a className="linkStyle1" href="#/contact">
<i className="fa fa-angle-right"></i><span>Contact</span>
</a>
</div>
</div>
< /div>
);
}
});
module.exports = Projects;
The componentWillReceiveProps get new props as a parameter. The old props remain unchanged on this method execution, so you need to initialize your state based on this parameter. Try passing the props as a parameter to your componentConfig method:
componentWillMount: function() {
this.componentConfig(this.props);
},
componentWillReceiveProps: function(nextProps){
this.componentConfig(nextProps);
},
componentConfig: function(data) {
...
}