How to access component methods in React? - javascript

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.

Related

Using jQuery in React to modify CSS of an element

Hello,
I know its not recommended to use jQuery with react & I am aware of method on react for changing CSS of element but here I am just trying to see if my req can be achieved or not , all i want is to change the colour of li element when corresponding tick icon is clicked for it, I am using a jQuery code
const markdone = () => {
let c = $("#ll")
console.log(c)
$(c).closest("li").css("background-color", "green");
};
but when i am clicking the css gets applied but not on its corresponding li element in my case for ex have attached image when i click on 3 tick icon css gets changed for 1 is there any way i can fix it
attaching whole code below
check markdone function for making css change :
const [input, setValue] = useState("");
const [todos, setTodos] = useState([]);
// passing entered
const handleInput = (event) => {
setValue(event.target.value);
};
const lp = (event) => {
// let c = [1,2,34,55]
event.preventDefault();
// if no input nothing will happen return none
if (!input) return;
// using spread operator its used whenever we need to add array data to exiting array
const newTodos = [...todos, input];
setTodos(newTodos);
// clearing input text
setValue("");
};
const handeldel = (index) => {
// console.log(index)
todos.splice(index, 1);
setTodos([...todos]);
// const newTodos = todos.splice(index, 1);
// setTodos([...newTodos]);
};
const [line, setline] = useState(false);
// const [ll, setll] = useState(false);
const markdone = () => {
let c = $("#ll")
console.log(c)
$(c).closest("li").css("background-color", "green");
};
useEffect(() => {
$(document).ready(function() {
$("#pk").click(function(e) {
// e.preventDefault();
alert('hello')
});
});
});
return ( <
div >
<
h1 id = "pk"
className = "text-center font-weight-bolder alert-info mb-5" >
Tasks To Do < i class = "fas fa-clipboard-list text-success" > < /i> <
/h1> <
div class = "input-group mb-3 container" >
<
input className = "form-control border-primary font-weight-bold"
style = {
{
height: 60
}
}
placeholder = "Enter Text here"
type = "text"
value = {
input
}
onChange = {
handleInput
}
/> <
div class = "input-group-append" >
<
button className = "input-group-append font-weight-bolder "
style = {
{
fontSize: 20
}
}
onClick = {
lp
} >
{
" "
} <
i class = "fas fa-plus-square fa-2x p-2" > < /i>{" "} <
/button> <
/div> <
/div> {
todos.map((x, index) => ( <
ol style = {
{
listStyle: "outside"
}
}
className = "container" >
<
li className = "font-weight-bolder table-bordered text-capitalize alert-secondary "
style = {
{
fontSize: 30,
textDecoration: line ? "line-through" : "none",
// backgroundColor: ll ? "Chartreuse" : "none",
}
} >
{
x
} <
i class = "fas fa-check-circle float-md-right text-success"
id = "ll"
onClick = {
markdone
} >
< /i>{" "} <
i class = "fas fa-trash-alt text-danger float-md-right"
onClick = {
() => handeldel(index)
} >
< /i> <
/li> <
/ol>
))
}
{ /* for future ref */ } {
/* <div >
{data.map((user) => (
<div className="user">{user.id + " " + user.name
}</div>
))}
</div> */
} <
/div>
I suppose using a Ref should do the trick, as Refs provide a way to access DOM nodes or React elements created in the render method.
Just put it on the element you'd like to style using jQuery and access it with RefName.current
IDs must be unique
You do not need jQuery, just delegation
Plain JS - there are other ways in React
I am assuming .input-group-append is the container
document.querySelector(".input-group-append").addEventListener("click",function(e) {
const tgt = e.target;
if (tgt.classList.contains("text-success")) {
tgt.closest("li").style.backgroundColor = "green";
}
})

Splicing only one element when creating a new element

I'm trying to make it where when a user creates a widget, and then reloads the page (it'll appear because it's saved in localStorage) and then once you create another widget, I want to be able to delete the old widget before the page refreshes but it deletes the widget that the user clicked and the new widget.
Each time a new widget it created, it gets assigned a property name 'id' and the value is determined based on what is already in localStorage and it finds the next available (or not in use) id number. The widgets array also gets sorted from smallest id to largest id before setting it back to localStorage.
I've tried attaching a click listener for the delete button on the widget both when it's created and when the document is loaded. But that wasn't working.
Now i'm thinking I have to call a function with the id as its param to add a click listener to all the widgets that are appended to the document and when a new widget is created.
app.js:
function addRemoveListener(id) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
$(`#widget-${id} > span > .widget-clear`).click(() => {
for (let i = 0; i < localUi.widgets.length; i++) {
let thisWidget = `#widget-${id}`;
if (localUi.widgets[i].id == id) {
localUi.widgets.splice(i, 1)
}
$(thisWidget).remove();
console.log(localUi)
}
let newUi = JSON.stringify(localUi);
localStorage.setItem('ui', newUi);
})
}
widget.js:
static appendToDom(ui) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
for (let i = 0; i < localUi.widgets.length; i++) {
let widget = localUi.widgets[i];
let query = () => {
if (widget.type == 'humidity') {
return `${Math.floor(ui.weather.currently.humidity * 100)}`
} else if (widget.type == 'eye') {
return `${Math.floor(ui.weather.currently.visibility)}`
} else if (widget.type == 'windsock') {
return `${Math.floor(ui.weather.currently.windSpeed)}`
} else if (widget.type == 'pressure') {
return `${Math.floor(ui.weather.currently.pressure)}`
} else if (widget.type == 'uv-index') {
return `${ui.weather.currently.uvIndex}`
}
}
$('nav').after(`<div class="widget widget-${widget.size}" id="widget-${widget.id}">
<span>
<i class="material-icons widget-clear">clear</i>
<i class="material-icons widget-lock-open">lock_open</i>
<i class="material-icons widget-lock">lock_outline</i>
</span>
<div class="data-container">
<img src=${widget.image}>
<h1> ${widget.type}: ${query()} ${widget.unit} </h1>
</div>
</div>`)
$(`#widget-${widget.id}`).delay(1000 * i).animate({ opacity: 1 }, 1000);
$(`#widget-${widget.id}`).css({ left: `${widget.left}`, top: `${widget.top}`, 'font-size': `${widget.dimensions[2]}` })
$(`.widget`).draggable();
$(`#widget-${widget.id}`).css({ width: `${widget.dimensions[0]}`, height: `${widget.dimensions[1]}` })
addRemoveListener(i);
}
// this function is called earlier in the script when the user has selected
// which kind of widget they want
let makeWidget = () => {
let newWidget = new Widget(this.size, this.id, this.image, this.type, this.unit, this.dimensions);
saveWidget(newWidget);
addRemoveListener(this.id)
}
I have no problems with this until I delete an existing widget after I create a new one, and before refreshing.
You might have a problem with the id that is passed to your addRemoveListener function. It could be passing the same id for any widget so the loop will delete the UI because thisWidget is in the for loop. Try adding some console logging.
function addRemoveListener(id) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
$(`#widget-${id} > span > .widget-clear`).click(() => {
for (let i = 0; i < localUi.widgets.length; i++) {
let thisWidget = `#widget-${id}`;
if (localUi.widgets[i].id == id) {
localUi.widgets.splice(i, 1)
}
// Move this inside the if statement above.
$(thisWidget).remove();
console.log(localUi)
}
let newUi = JSON.stringify(localUi);
localStorage.setItem('ui', newUi);
})
}
or better yet, re-write it to continue if the id doesn't match
function addRemoveListener(id) {
let storageUi = localStorage.getItem('ui');
let localUi = JSON.parse(storageUi);
$(`#widget-${id} > span > .widget-clear`).click(() => {
for (let i = 0; i < localUi.widgets.length; i++) {
let thisWidget = `#widget-${id}`;
if (localUi.widgets[i].id !== id) {
continue;
}
localUi.widgets.splice(i, 1)
$(thisWidget).remove();
console.log(localUi)
}
let newUi = JSON.stringify(localUi);
localStorage.setItem('ui', newUi);
})
}

need to find a code to call two functions when div is clicked in react

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

React, make state apply to single element in loop

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

reactjs setState before render on update

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

Categories