I try to add hammer js to my reactjs component and my component looks as it follows
import React from 'react';
import _ from 'underscore';
import Hammer from 'hammerjs';
class Slider extends React.Component {
constructor(props) {
super(props)
this.updatePosition = this.updatePosition.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
this.state = {
images: [],
slidesLength: null,
currentPosition: 0,
slideTransform: 0,
interval: null
};
}
next() {
console.log('swipe')
const currentPosition = this.updatePosition(this.state.currentPosition - 10);
this.setState({ currentPosition });
}
prev() {
if( this.state.currentPosition !== 0) {
const currentPosition = this.updatePosition(this.state.currentPosition + 10);
this.setState({currentPosition});
}
}
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next());
this.hammer.on('swiperight', this.prev());
}
componentWillUnmount() {
this.hammer.off('swipeleft', this.next())
this.hammer.off('swiperight', this.prev())
}
handleSwipe(){
console.log('swipe')
}
scrollToSlide() {
}
updatePosition(nextPosition) {
const { visibleItems, currentPosition } = this.state;
return nextPosition;
}
render() {
let {slides, columns} = this.props
let {currentPosition} = this.state
let sliderNavigation = null
let slider = _.map(slides, function (slide) {
let Background = slide.featured_image_url.full;
if(slide.status === 'publish')
return <div className="slide" id={slide.id} key={slide.id}><div className="Img" style={{ backgroundImage: `url(${Background})` }} data-src={slide.featured_image_url.full}></div></div>
});
if(slides.length > 1 ) {
sliderNavigation = <ul className="slider__navigation">
<li data-slide="prev" className="" onClick={this.prev}>previous</li>
<li data-slide="next" className="" onClick={this.next}>next</li>
</ul>
}
return <div ref={
(el) => this._slider = el
} className="slider-attached"
data-navigation="true"
data-columns={columns}
data-dimensions="auto"
data-slides={slides.length}>
<div className="slides" style={{ transform: `translate(${currentPosition}%, 0px)`, left : 0 }}> {slider} </div>
{sliderNavigation}
</div>
}
}
export default Slider;
the problem is like on tap none of the components method are fired.
How do I deal in this case with the hammer js events in componentDidMount
Reason is, inside componentDidMount lifecycle method swipeleft and swiperight expect the functions but you are assigning value by calling those methods by using () with function name. Remove () it should work.
Write it like this:
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', this.next); // remove ()
this.hammer.on('swiperight', this.prev); // remove ()
}
Related
This is really driving me in nuts. I wrote so many components and this is so basic, but probably there is something that I can't realize in this simple scenario. Basically my render is not being updated when I set the state.
import React, {Component} from 'react'
import styles from './styles'
import {css} from 'aphrodite'
export class Switcher1 extends Component {
constructor(props) {
super(props)
this.state = {
selected: 0
}
}
setIndex(ndx) {
console.log(ndx)
this.setState = {selected: ndx}
}
render() {
console.log('Rendering...')
const sel = this.state.selected
return (
<div className={css(styles.container)}>
{this.props.options.map((item, index) => {
const isSelected = index === sel
return <div key={index}
className={isSelected ? css(styles.active) : css(styles.notActive)}
onClick={() => this.setIndex(index)}>
<img className={css(styles.image)}
src={"/assets/images/" + item.icon + (isSelected ? '_blue.svg' : '_white.svg')}/>
{item.text} - {sel} - {index} - {isSelected.toString()}
</div>
})}
</div>)
}
}
Updated code...but it's still not works...
import React, {Component} from 'react'
import styles from './styles'
import {css} from 'aphrodite'
export class Switcher1 extends Component {
constructor(props) {
super(props)
this.state = {
selected: 0
}
this.setIndex = this.setIndex.bind(this)
}
setIndex(ndx) {
console.log(this.state)
this.setState = ({selected: ndx})
}
render() {
console.log('Rendering...')
const sel = this.state.selected
return (
<div className={css(styles.container)}>
{this.props.options.map((item, index) => {
const isSelected = index === sel
return <div key={index}
className={isSelected ? css(styles.active) : css(styles.notActive)}
onClick={() => this.setIndex(index)}>
<img className={css(styles.image)}
src={"/assets/images/" + item.icon + (isSelected ? '_blue.svg' : '_white.svg')}/>
{item.text} - {sel} - {index} - {isSelected.toString()}
</div>
})}
</div>)
}
}
Any thoughts ?
1) You forgot to bind this to your setIndex method : setIndex=(ndx)=> {
2) the setState is wrong, replace by : this.setState({ selected: ndx });
result :
setIndex = (ndx) => {
console.log(ndx);
this.setState({ selected: ndx });
}
You have:
this.setState = {selected: ndx}
This is not how you set state. Try this:
this.setState({ selected: ndx });
Also, as #benjamin mentioned, you should bing the setIndex function by using an arrow function:
setIndex = () => { ... }
or binding in your constructor:
this.setIndex = this.setIndex.bind(this);
You can read the docs on setState here.
I'm setting up a virtualized scrolling component in React, and using refs with a recycled observer to notify the app when to prepare another batch of data. Inside my Grid component, I map over the current batch of data and assign a ref to a sentinel div, except that ref returns null in componentDidMount(). I don't understand why since componentDidMount fires after render executes, so the reference should be available.
The only workaround to this I've found is using this janky solution in my componentDidMount: setTimeout(() => this.observer.observe(this.targetRef.current), 0);.
import React, { Component, createRef } from "react";
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
batch: []
};
this.observer = null;
this.targetRef = null;
this.lastRowFirstVisible =
props.rowCount * props.columnCount - props.columnCount;
this.config = {
rootMargin: "0px",
threshold: 1
};
this.setTargetRef = element => {
this.targetRef = element;
};
}
componentDidMount() {
const { startIndex, numberToDisplay } = this.props;
this.setBatch(startIndex, numberToDisplay);
this.observer = new IntersectionObserver(function(entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(entry);
// self.unobserve(entry.target);
}
});
}, this.config);
setTimeout(() => this.observer.observe(this.targetRef), 0);
}
setBatch = (startIndex, numberToDisplay) => {
const batch = this.getBatch(startIndex, numberToDisplay);
this.setState({ batch });
};
getBatch = (startIndex, numberToDisplay) => {
const { data } = this.props;
return data.slice(startIndex, numberToDisplay);
};
// TO DO
updateObserver = () => {
this.observer.observe(this.targetRef.current);
};
render() {
const { lastRowFirstVisible } = this;
const { batch } = this.state;
const { elementWidth, elementHeight } = this.props;
console.log(lastRowFirstVisible);
return (
<>
{batch.map((element, localIndex) => {
const { index } = element;
console.log(localIndex === lastRowFirstVisible);
return localIndex === lastRowFirstVisible ? (
<div
id={index}
key={index}
style={{ width: elementWidth, height: elementHeight }}
className="card"
ref={this.setTargetRef}
>
{this.props.renderRow(element)}
</div>
) : (
<div
key={index}
style={{ width: elementWidth, height: elementHeight }}
className="card"
>
{this.props.renderRow(element)}
</div>
);
})}
</>
);
}
}
export default Grid;
Expected results: render function finishes executing, assigns DOM node to this.targetRef for use in componentDidMount()
Actual results: this.targetRef is still null in componentDidMount()
I get the following error when trying to compile my app 'handleProgress' is not defined no-undef.
I'm having trouble tracking down why handleProgress is not defined.
Here is the main react component
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
this.handleProgress = this.handleProgress.bind(this);
}
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
handleProgress = () => {
console.log('hello');
};
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
Your render method is wrong it should not contain the handlePress inside:
You are calling handlePress on this so you should keep it in the class.
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
this.handleProgress = this.handleProgress.bind(this);
}
handleProgress = () => {
console.log('hello');
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
If you are using handleProgress inside render you have to define it follows.
const handleProgress = () => {
console.log('hello');
};
if it is outside render and inside component then use as follows:
handleProgress = () => {
console.log('hello');
};
If you are using arrow function no need to bind the function in constructor it will automatically bind this scope.
handleProgress should not be in the render function, Please keep functions in you component itself, also if you are using ES6 arrow function syntax, you no need to bind it on your constructor.
Please refer the below code block.
class App extends Component {
constructor(props) {
super(props);
this.state = {
progressValue: 0,
};
// no need to use bind in the constructor while using ES6 arrow function.
// this.handleProgress = this.handleProgress.bind(this);
}
// move ES6 arrow function here.
handleProgress = () => {
console.log('hello');
};
render() {
const { questions } = this.props;
const { progressValue } = this.state;
const groupByList = groupBy(questions.questions, 'type');
const objectToArray = Object.entries(groupByList);
return (
<>
<Progress value={progressValue} />
<div>
<ul>
{questionListItem && questionListItem.length > 0 ?
(
<Wizard
onChange={this.handleProgress}
initialValues={{ employed: true }}
onSubmit={() => {
window.alert('Hello');
}}
>
{questionListItem}
</Wizard>
) : null
}
</ul>
</div>
</>
);
}
}
Try this one, I have check it on react version 16.8.6
We don't need to bind in new version using arrow head functions. Here is the full implementation of binding argument method and non argument method.
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};
constructor() {
super();
}
render() {
return (
<div>
<button onClick={this.updateCounter}>NoArgCounter</button>
<button onClick={() => this.updateCounterByArg(this.state.count)}>ArgCounter</button>
<span>{this.state.count}</span>
</div>
);
}
updateCounter = () => {
let { count } = this.state;
this.setState({ count: ++count });
};
updateCounterByArg = counter => {
this.setState({ count: ++counter });
};
}
export default Counter;
I'm changing the class attribute of my props and then i want the component to rerender with the new classes but that doesn't work. I've read about the shouldComponentUpdate method but that method never gets called.
var ReactDOM = require('react-dom');
var React = require('react');
class Button extends React.Component {
constructor(props) {
super(props);
console.log("BUTTON")
console.log(props);
var options = props.options;
}
componentWillMount () {
var defaultFeatureOptionId = this.props.feature.DefaultFeatureOptionId;
this.props.options.forEach((option) => {
var classes = "";
if (option.Description.length > 10) {
classes = "option-button big-button hidden";
} else {
classes = "option-button small-button hidden";
}
if (option.Id === defaultFeatureOptionId) {
classes = classes.replace("hidden", " selected");
option.selected = true;
}
option.class = classes;
});
}
shouldComponentUpdate(props) {
console.log("UPDATE");
}
toggleDropdown(option, options) {
console.log(option);
console.log(options)
option.selected = !option.selected;
options.forEach((opt) => {
if (option.Id !== opt.Id) {
opt.class = opt.class.replace("hidden", "");
}
else if(option.Id === opt.Id && option.selected) {
opt.class = opt.class.replace("", "selected");
}
});
}
render() {
if (this.props.options) {
return (<div> {
this.props.options.map((option) => {
return <div className={ option.class } key={option.Id}>
<div> {option.Description}</div>
<img className="option-image" src={option.ImageUrl}></img>
<i className="fa fa-chevron-down" aria-hidden="true" onClick={() => this.toggleDropdown(option, this.props.options) }></i>
</div>
})
}
</div>
)
}
else {
return <div>No options defined</div>
}
}
}
module.exports = Button;
I have read a lot of different thing about shouldComponentUpdate and componentWillReceiveProps but there seems to be something else i'm missing.
You cannot change the props directly, either you call a parent function to change the props that are passed to your component or in your local copy that you createm you can change them. shouldComponentUpdate is only called when a state has changed either directly or from the props, you are not doing any of that, only modifying the local copy and hence no change is triggered
Do something like
var ReactDOM = require('react-dom');
var React = require('react');
class Button extends React.Component {
constructor(props) {
super(props);
console.log(props);
this.state = {options = props.options};
}
componentWillRecieveProps(nextProps) {
if(nextProps.options !== this.props.options) {
this.setState({options: nextProps.options})
}
}
componentWillMount () {
var defaultFeatureOptionId = this.props.feature.DefaultFeatureOptionId;
var options = [...this.state.options]
options.forEach((option) => {
var classes = "";
if (option.Description.length > 10) {
classes = "option-button big-button hidden";
} else {
classes = "option-button small-button hidden";
}
if (option.Id === defaultFeatureOptionId) {
classes = classes.replace("hidden", " selected");
option.selected = true;
}
option.class = classes;
});
this.setState({options})
}
shouldComponentUpdate(props) {
console.log("UPDATE");
}
toggleDropdown(index) {
var options = [...this.state.options];
var options = options[index];
option.selected = !option.selected;
options.forEach((opt) => {
if (option.Id !== opt.Id) {
opt.class = opt.class.replace("hidden", "");
}
else if(option.Id === opt.Id && option.selected) {
opt.class = opt.class.replace("", "selected");
}
});
this.setState({options})
}
render() {
if (this.state.options) {
return (<div> {
this.state.options.map((option, index) => {
return <div className={ option.class } key={option.Id}>
<div> {option.Description}</div>
<img className="option-image" src={option.ImageUrl}></img>
<i className="fa fa-chevron-down" aria-hidden="true" onClick={() => this.toggleDropdown(index) }></i>
</div>
})
}
</div>
)
}
else {
return <div>No options defined</div>
}
}
}
module.exports = Button;
If I have a simple react component that records a click count for a button and on each click records a new history state without changing the URL. When the user clicks back how do I restore the state to as it was?
I can do as it is here using the native JavaScript history object, but it fails when the user transitions back to the first state and back from a different component into the last state of this one.
I suspect that there is a better to do this using react-router (1.0)?
import React, { Component } from 'react';
export default class Foo extends Component {
state = {
clickCount: 0,
};
componentWillMount() {
window.onpopstate = (event) => {
if (event.state.clickCount) {
this.setState({ clickCount: event.state.clickCount });
}
};
}
onClick() {
const newClickCount = this.state.clickCount + 1;
const newState = { clickCount: newClickCount };
this.setState(newState);
history.pushState(newState, '');
}
render() {
return (
<div>
<button onClick={this.onClick.bind(this)}>Click me</button>
<div>Clicked {this.state.clickCount} times</div>
</div>
);
}
}
localStorage or even cookies are options, but probably not the best way. You should store the count in a database, this way you can set the initial state in your constructor to the last value saved in the database.
Another option, if you only need to persist the count on the client-side(and not in a database) is using a closure.
// CountStore.js
var CountStore = (function() {
var count = 0;
var incrementCount = function() {
count += 1;
return count;
};
var getCount = function() {
return count;
};
return {
incrementCount: incrementCount,
getCount: getCount
}
})();
export default CountStore;
So your code would change to the below.
import React, { Component } from 'react';
import CountStore from './CountStore';
export default class Foo extends Component {
state = {
clickCount: CountStore.getCount()
};
componentWillMount() {
window.onpopstate = (event) => {
if (event.state.clickCount) {
this.setState({ clickCount: event.state.clickCount });
}
};
}
onClick() {
const newClickCount = CountStore.incrementCount();
const newState = { clickCount: newClickCount };
this.setState(newState);
}
render() {
return (
<div>
<button onClick={this.onClick.bind(this)}>Click me</button>
<div>Clicked {this.state.clickCount} times</div>
</div>
);
}
}
There may be a cleaner way of using react-router, but this is an option.
An example:
import React, {Component} from "react";
import {NavLink} from "react-router-dom";
interface Props {
}
interface State {
count: number
}
export default class About extends Component<Props, State> {
UNSAFE_componentWillMount(): void {
this.setState(Object.getPrototypeOf(this).constructor.STATE || {});
}
componentWillUnmount(): void {
Object.getPrototypeOf(this).constructor.STATE = this.state;
}
constructor(props: Props) {
super(props);
this.state = {count: 0}
}
render() {
const {count} = this.state;
return <div style={{width: "100%", height: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "space-evenly", fontSize: "2em"}}>
<span>Count: {count}</span>
<button onClick={() => this.setState({count: count + 1})}>PLUS ONE</button>
<NavLink to="/">Redirect to HOME</NavLink>
</div>
}
}