Setting setTimeOut in a react class base component - javascript

Plaese I want to setTimeOut in my class base component. Trying different method but still it did not work. I want the setTimeout to call nextClick/onClickI after a specified second but, still don't understand how to go about it. Please help, if you have an idea. below is the component code...
class AbCarousel extends Component {
state = {
data: []
}
myRef = React.createRef();
getData = async () => {
const res = await fetch('aboutdata.json');
const data = await res.json();
this.setState({data: data})
}
componentDidMount() {
this.getData();
}
prevClick = () => {
const slide = this.myRef.current;
slide.scrollLeft -= slide.offsetWidth;
if (slide.scrollLeft <= -0){
slide.scrollLeft = slide.scrollWidth;
}
};
nextClick = () => {
const slide = this.myRef.current;
slide.scrollLeft += slide.offsetWidth;
if (slide.scrollLeft >= (slide.scrollWidth - slide.offsetWidth)) {
slide.scrollLeft = 0;
}
};
render() {
const { data } = this.state;
return (
<div className="wrapper">
<div className="app" ref={this.myRef}>
<AbCard data={data} />
</div>
<div className="row">
<div className="prev" onClick={this.prevClick}>
<div><FaChevronLeft /></div>
</div>
<div className="next" onClick={this.nextClick}>
<div><FaChevronRight /></div>
</div>
</div>
</div>
)
}
}
export default AbCarousel

Simplest is just to update your onClick handler to take setTimeout as a callback like the following
onClick={() => setTimeout(this.nextClick, numSeconds)
or you can update the nextClick method directly and use a callback function within the setTimeout and wrap around the code you wish to delay the execution on:
nextClick = () => {
const slide = this.myRef.current;
setTimeout(() => {
slide.scrollLeft += slide.offsetWidth;
if (slide.scrollLeft >= (slide.scrollWidth - slide.offsetWidth)) {
slide.scrollLeft = 0;
}, numSeconds)
}

Related

Updating state twice when the button is clicked, React

I want to create simple 2048 game application. My initial board is composed of 16 array elements.
The generate() function generates a value of '2' in a random empty element
It works just fine and creates one random '2' for me, but the problem starts when I want to call it twice in a handler like this:
const handleNewGame = () => {
generate()
generate()
}
I've read about prevState but have no idea how to implement it in this batch of code to work properly.
Here is my game component:
const width = 4;
const Game = () => {
const [Board, setBoard] = useState([]);
const createBoard = () => {
let initialBoard = [];
for (let i = 0; i < width * width; i++) {
initialBoard.push("");
}
return initialBoard;
};
const generate = () => {
let board = [...Board];
let randomNumber = Math.floor(Math.random() * Board.length);
console.log(randomNumber);
if (board[randomNumber] === "") {
board[randomNumber] = 2;
setBoard(board);
} else generate()
};
const handleNewGame = () => {
generate()
generate()
}
useEffect(() => {
setBoard(createBoard);
console.log(`Board Created!`);
}, []);
return (
<div className="game-container">
<button onClick={handleNewGame}>NewGame</button>
<div className="game">
{Board.map((value, index) => (
<div className="tile" key={index}>
{value}
</div>
))}
</div>
</div>
);
};
export default Game;
I'll be glad for the answer.
setState(), named setBoard() in your code is asynchronous, whatch this great video on the Event Loop for you to understand more: https://www.youtube.com/watch?v=8aGhZQkoFbQ.
See if this will suit your needs:
import { useEffect, useState } from 'react';
const width = 4;
const Game = () => {
const [Board, setBoard] = useState([]);
const createBoard = () => {
let initialBoard = [];
for (let i = 0; i < width * width; i++) {
initialBoard.push('');
}
return initialBoard;
};
const randomBoardPosition = () => {
return Math.floor(Math.random() * Board.length);
};
const generate = () => {
let board = createBoard();
let randomNumber = randomBoardPosition();
let positionsFilled = 0;
while (positionsFilled < 2) {
if (board[randomNumber] === '') {
board[randomNumber] = 2;
positionsFilled++;
} else {
randomNumber = randomBoardPosition();
}
}
setBoard(board);
};
const handleNewGame = () => {
generate();
};
useEffect(() => {
setBoard(createBoard);
console.log(`Board Created!`);
}, []);
return (
<div className="game-container">
<button onClick={handleNewGame}>NewGame</button>
<div className="game">
{Board.map((value, index) => (
<div className="tile" key={index}>
{value}
</div>
))}
</div>
</div>
);
};
export default Game;
Just us function instead of value
const generate = () => {
setBoard(Board => {
let board = [...Board];
while (true) {
let randomNumber = Math.floor(Math.random() * Board.length);
console.log(randomNumber);
if (board[randomNumber] === "") {
board[randomNumber] = 2;
break
}
}
}
}

Why debounce doesnt invoke my function?

Im working on something with React and mobx.
I created an ImageCarousel Component where I show the image that was clicked.
I have a previous and a next buttons(which are themselves a Component), to move between images.
I tried to wrap those actions (prev and next) with lodash debounce,
but something fails on the way.
My current store has those actions:
prevCarousel
nextCarousel
debounceAction
debounceAction is just a Higher Order Function that get 2 parameters (fn, wait), and invoke lodash debounce with those parameters.
My CarouselButton component get through its props those actions I mantioned above. inside the Component I trigger with onClick event to invoke debounceAction(fn, wait) with the actual action (prev, or next).
Im not sure how to wrap my actions with debounce the right way.
I invoke debounceAction (the HOF that wraps the debounce) in the second code snippet (in CarouselButton Component).
do you see my mistake here?
galleryStore.jsx - current Store:
class GalleryStore {
// Observables
#observable images = [
// ....images
];
#observable carouselleButtons= {
next: "fa fa-chevron-right",
back: "fa fa-chevron-left"
}
#observable selectedImageIndex = null;
#observable hideCarousel = true;
#observable onTransition = false;
// Actions
selectImage = (index) =>{
this.selectedImageIndex = index;
}
toggleCarousel = () =>{
this.hideCarousel = !this.hideCarousel;
}
carouselNext = () => {
if(this.selectedImageIndex == this.images.length - 1) {
return;
}
this.onTransition = true;
setTimeout(() => {
this.selectedImageIndex = this.selectedImageIndex + 1;
this.onTransition = false;
},500)
}
carouselPrev = () => {
if(this.selectedImageIndex == 0) {
console.log('start of the collection');
return;
}
this.onTransition = true;
setTimeout(() => {
this.selectedImageIndex = this.selectedImageIndex - 1;
this.onTransition = false;
}, 500)
}
debounceAction = (fn, wait) => {
//lodash's debounce
return debounce(fn, wait);
}
carouselButton Component - here I Invoke the debounce:
// React's
import React from 'react';
// Styles
import CarouselButtonStyle from './carouselButtonStyle';
// CarouselButton Component
export default class CarouselButton extends React.Component {
debounce = (e) =>{
const { debounceAction } = this.props;
// -----> HERE I CALL DEBOUNCE ! <---------
e.stopPropagation();
debounceAction(this.buttonHandler, 400);
}
buttonHandler = (e) => {
const {limit, index, action, debounceAction} = this.props;
if(index == limit) return;
else action();
}
render(){
const {limit, index, icon, action, debounceAction} = this.props;
return(
<CarouselButtonStyle
onClick={(e) => {this.debounce(e)}}
className={ index == limit ? 'end-of-collection' : '' } >
<i className={icon} aria-hidden="true" />
</CarouselButtonStyle>
);
}
}
imageCarousel.jsx - carouselButton parent Component:
// React's
import React from 'react';
// Mobx-react's
import { observer, inject } from 'mobx-react';
// Styles
import ImageCarouselStyle from './imageCarouselStyle';
// Components
import ImgContainer from './imgContainer/imgContainer';
import CarouselButton from './carouselButton/carouselButton';
// ImageCarousel Component
#inject('galleryStore')
#observer
export default class ImageCarousel extends React.Component {
closeCarousel = () => {
this.props.galleryStore.toggleCarousel();
}
onKeyDown = (e) => {
const { keyCode } = e;
if(keyCode ===27) this.onEscHandler();
else if (keyCode == 37) this.onLeftArrow();
else if (keyCode == 39) this.onRightArrow();
else return;
}
onLeftArrow = () => { this.props.galleryStore.carouselPrev() }
onRightArrow = () => { this.props.galleryStore.carouselNext() }
onEscHandler = () => { this.closeCarousel() }
componentDidMount(){
document.addEventListener('keydown', this.onKeyDown, false);
}
render(){
return(
<ImageCarouselStyle
hidden={this.props.galleryStore.hideCarousel}
onClick={this.closeCarousel} >
<CarouselButton action={'prev'}
icon={this.props.galleryStore.carouselleButtons.back}
action={this.props.galleryStore.carouselPrev}
limit={0}
index={this.props.galleryStore.selectedImageIndex}
debounceAction={this.props.galleryStore.debounceAction} />
<ImgContainer
images={this.props.galleryStore.images}
imgIndex={this.props.galleryStore.selectedImageIndex}
onTransition={this.props.galleryStore.onTransition}/>
<CarouselButton action={'next'}
icon={this.props.galleryStore.carouselleButtons.next}
action={this.props.galleryStore.carouselNext}
limit={this.props.galleryStore.amountOfImages}
index={this.props.galleryStore.selectedImageIndex}
debounceAction={this.props.galleryStore.debounceAction} />
</ImageCarouselStyle>
);
}
}
The issue is that you have to return debounceAction from CarouselButton's debounce method.
debounce = (e) =>{
const { debounceAction } = this.props;
// -----> HERE I CALL DEBOUNCE ! <---------
e.stopPropagation();
debounceAction(this.buttonHandler, 400);
// -^^^ must return here
}
However, I would suggest going a step further to avoid future confusion. Simply invoke lodash's debounce when you need it, rather than rewriting it multiple times in your code and leading to questionable method names.
Here is the most basic example of how you can wrap a click handler:
class Button extends React.Component {
handleClick = _.debounce((e) => {
alert('debounced click reaction')
}, 1000)
render() {
return <button onClick={this.handleClick}>CLICK ME</button>
}
}
ReactDOM.render(
<Button />,
document.getElementById('app')
);
<div id="app"></div>
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

on React Button onClick, start and stop a function(method)

Got a nagging issue and was wondering if anyone can shed some light.
I made a function that automates the routing for my react app...but i am trying to attach a button to this function to ensure it starts and stops on button click. However, when i try the code below...nothing happens
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.handleClick}>
Toggle
</button>
........
but when i try the second code, the tabbing function starts onClick of the button but of course doesn't stop when you click the button again.
class App extends React.Component {
constructor (props) {
super(props);
this.state = { tabControl: true };
this.handleClick = this.handleClick.bind(this);
this.tabControl = this.tabControl.bind(this);
}
tabControl(props){
RoutePaths(this.props);
}
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
render() {
return (
<div className="clearfix" id="topContent">
<Sidebar />
<div className="white-bg" id="page-wrapper">
<Header tagline="Welcome to JuDGE" />
<button className="AutoTab" onClick={this.tabControl}>
Toggle
</button>
Try using the current state instead of the optional callback inside setState:
handleClick() {
this.setState({ tabControl: !this.state.tabControl });
}
I'm not sure i fully get what you are trying to do but it seems to me that you forgot a condition.
You say if you invoke this method:
tabControl(props){
RoutePaths(this.props);
}
it works but won't stop.
Well, you are not running it conditionally.
In this method:
handleClick() {
this.setState(function (prevState, props){
return { tabControl: !prevState.tabControl }
});
}
You are setting the tabControl state. I think you forgot to check it before running tabControl().
tabControl(props){
const {tabControl} = this.state;
tabControl && RoutePaths(this.props); // invoke of tabControl is true
}
Edit
After seeing the code for RoutePaths as you posted on comments:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
It seems to me that you will have another problem. you need the id of the interval that returned from setInterval in order to stop it, but you didn't stored it anywhere.
Quote from the docs:
... It returns an interval ID which uniquely identifies the interval,
so you can remove it later by calling clearInterval() ...
So you will need to store it somewhere and call clearInterval with ID.
this.intervalId = setInterval(() => {...});
And somewhere else in your class:
clearInterval(this.interval);
Edit #2
As a followup to your comment, here is a simple usage of interval with react:
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
ticks: 0
};
}
onStart = () => {
this.intervalId = setInterval(() => {
this.setState({ ticks: this.state.ticks + 1 })
}, 500);
}
onStop = () => {
clearInterval(this.intervalId)
}
render() {
const { ticks } = this.state;
return (
<div>
<button onClick={this.onStart}>Start</button>
<button onClick={this.onStop}>Stop</button>
<div>{ticks}</div>
</div>
);
}
}
ReactDOM.render(<Timer />, document.getElementById("root"));
<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>
So you can try this approach,
RoutePaths will return the interval id:
function RoutePaths(props) {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let index = 0;
let interval = 3000;
return setInterval(() => {
props.history.push(pathUrls[index]);
index = (index + 1) % paths;
}, interval);
}
and tabControl will store the id and conditionally call or clear the interval:
tabControl() {
const { tabControl } = this.state;
if (tabControl && this.intervalId) { // i'm not sure this is the condition you want, but you can play with it
clearInterval(this.intervalId);
} else {
this.intervalId = RoutePaths(this.props);
}
}
I haven't tested this code but i think it can lead you to a good start.
You don't need tabControl state for what you are trying to do. However, you need to call clearInterval somewhere. Change your handleClick to something like this:
handleClick() {
// change RoutePath to return the id that setInterval returns.
if (this.routePathInterval) {
clearInterval(this.routePathInterval);
this.routePathInterval = null;
} else {
this.routePathInterval = RoutePath(this.props);
}
}
Also, when you call clearInterval and then start it again, your index will start over from zero. You may want to keep the current index in state and pass it to RoutePaths, if you want to resume from the index that you were on.
edit:
On second thought, you don't need to keep the index in state, since you don't want to trigger a re-render when you increment it. However, you should make index an instance variable and make RoutePath an instance method of your App component.
First, initialize this.index = 0; in your constructor and then:
routePaths() {
let pathUrls = ['/deploymentqueue', '/deploydb', '/currentstatus'];
let paths = pathUrls.length;
let interval = 3000;
return setInterval(() => {
this.props.history.push(pathUrls[index]);
this.index = (this.index + 1) % paths;
}, interval);
}

Maximum call stack size exceeded - Connected React Component

I can't for the life of me figure out why I'm getting error:
Maximum call stack size exceeded
When this code is run. If I comment out:
const tabs = this.getTabs(breakpoints, panels, selectedTab);
the error goes away. I have even commented out other setState() calls to try and narrow down where the problem was at.
Code (removed the extra functions):
export default class SearchTabs extends Component {
constructor() {
super();
this.state = {
filters: null,
filter: null,
isDropdownOpen: false,
selectedFilter: null,
};
this.getTabs = this.getTabs.bind(this);
this.tabChanged = this.tabChanged.bind(this);
this.setSelectedFilter = this.setSelectedFilter.bind(this);
this.closeDropdown = this.closeDropdown.bind(this);
this.openDropdown = this.openDropdown.bind(this);
}
componentDidMount() {
const { panels } = this.props;
if (!panels || !panels.members || panels.members.length === 0) {
this.props.fetchSearch();
}
}
getTabs(breakpoints, panels, selectedTab) {
const tabs = panels.member.map((panel, idx) => {
const { id: panelId, headline } = panel;
const url = getHeaderLogo(panel, 50);
const item = url ? <img src={url} alt={headline} /> : headline;
const classname = classNames([
searchResultsTheme.tabItem,
(idx === selectedTab) ? searchResultsTheme.active : null,
]);
this.setState({ filter: this.renderFilters(
panel,
breakpoints,
this.setSelectedFilter,
this.state.selectedFilter,
this.state.isDropdownOpen,
) || null });
return (
<TabItem
key={panelId}
classname={`${classname} search-tab`}
headline={headline}
idx={idx}
content={item}
onclick={this.tabChanged(idx, headline)}
/>
);
});
return tabs;
}
render() {
const { panels, selectedTab } = this.props;
if (!panels || panels.length === 0) return null;
const tabs = this.getTabs(breakpoints, panels, selectedTab);
return (
<div className={searchResultsTheme.filters}>
<ul className={`${searchResultsTheme.tabs} ft-search-tabs`}>{tabs}</ul>
<div className={searchResultsTheme.dropdown}>{this.state.filter}</div>
</div>
);
}
}
export const TabItem = ({ classname, content, onclick, key }) => (
<li key={key} className={`${classname} tab-item`} onClick={onclick} >{content}</li>
);
Because of this loop:
render -----> getTabs -----> setState -----
^ |
| |
|____________________________________________v
You are calling getTabs method from render, and doing setState inside that, setState will trigger re-rendering, again getTabs ..... Infinite loop.
Remove setState from getTabs method, it will work.
Another issue is here:
onclick={this.tabChanged(idx, headline)}
We need to assign a function to onClick event, we don't need to call it, but here you are calling that method, use this:
onclick={() => this.tabChanged(idx, headline)}

Prefer pure function than React Component?

I'm working on a HTMl5 video player for a French company. We use React and Redux to build the UI, and it works very well, it's very pleasant to code ! We currently use eslint-plugin-react to check React code style. Since last versions, the linter recommands to use pure functions instead of React Components (view the rule) but it raises some debates in my team.
We are already using pure function for very small components that render always the same things (for given props). No problems with that. But for bigger components, in my opinion, pure functions seem to make the code less elegant (and less uniform compared to other components).
This is an example of one of our components that should be changed :
const ControlBar = ({ actions, core, root }) => {
const onFullscreenScreen = (isFullscreen) => {
const playerRoot = root;
if (isFullscreen && !screenfull.isFullscreen) {
screenfull.request(playerRoot);
} else {
screenfull.exit();
}
};
​
const renderIconButton = (glyph, action, label = false) => {
let content = (
<Button modifier="icon" clickCallback={ action }>
<Icon glyph={ glyph } />
</Button>
);
if (label) {
content = <TooltipOrigin content={label}>{content}</TooltipOrigin>;
}
return content;
};
​
const renderPlayButton = () => {
const { play, pause } = actions;
const { playerState } = core;
if (playerState === CoreStates.PAUSED) {
return renderIconButton(playGlyph, play, 'lecture');
}
return renderIconButton(pauseGlyph, pause, 'pause');
};
​
const renderMuteButton = () => {
const { mute, unmute } = actions;
const { currentVolume } = core;
if (currentVolume === 0) {
return renderIconButton(muteGlyph, unmute);
}
return renderIconButton(volumeGlyph, mute);
};
​
const renderFullscreenButton = () => {
const { isFullscreen } = core;
if (!isFullscreen) {
return renderIconButton(fullscreenGlyph, () => { onFullscreenScreen(true); });
}
return renderIconButton(fullscreenExitGlyph, () => { onFullscreenScreen(false); });
};
​
const { setCurrentVolume } = actions;
const { currentVolume } = core;
return (
<div className={ style.ControlBar }>
<div className={ style.audio }>
{ renderMuteButton() }
<SoundBar setCurrentVolume={ setCurrentVolume } volume={ currentVolume } />
</div>
<div className={ style.controls }>
{ renderPlayButton() }
</div>
<div className={ style.settings }>
{ renderFullscreenButton() }
</div>
</div>
);
};
​
ControlBar.propTypes = {
actions: PropTypes.object.isRequired,
core: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
};
​
export default ControlBar;
versus :
export default class ControlBar extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
core: PropTypes.object.isRequired,
root: PropTypes.object.isRequired,
};
onFullscreenScreen(isFullscreen) {
const playerRoot = this.props.root;
if (isFullscreen && !screenfull.isFullscreen) {
screenfull.request(playerRoot);
} else {
screenfull.exit();
}
}
renderIconButton(glyph, action, label = false) {
let content = (
<Button modifier="icon" clickCallback={ action }>
<Icon glyph={ glyph } />
</Button>
);
if (label) {
content = <TooltipOrigin content={label}>{content}</TooltipOrigin>;
}
return content;
}
renderPlayButton() {
const { play, pause } = this.props.actions;
const { playerState } = this.props.core;
if (playerState === CoreStates.PAUSED) {
return this.renderIconButton(playGlyph, play, 'lecture');
}
return this.renderIconButton(pauseGlyph, pause, 'pause');
}
renderMuteButton() {
const { mute, unmute } = this.props.actions;
const { currentVolume } = this.props.core;
if (currentVolume === 0) {
return this.renderIconButton(muteGlyph, unmute);
}
return this.renderIconButton(volumeGlyph, mute);
}
renderFullscreenButton() {
const { isFullscreen } = this.props.core;
if (!isFullscreen) {
return this.renderIconButton(fullscreenGlyph, () => { this.onFullscreenScreen(true); });
}
return this.renderIconButton(fullscreenExitGlyph, () => { this.onFullscreenScreen(false); });
}
render() {
const { setCurrentVolume } = this.props.actions;
const { currentVolume } = this.props.core;
return (
<div className={ style.ControlBar }>
<div className={ style.audio }>
{ this.renderMuteButton() }
<SoundBar setCurrentVolume={ setCurrentVolume } volume={ currentVolume } />
</div>
<div className={ style.controls }>
{ this.renderPlayButton() }
</div>
<div className={ style.settings }>
{ this.renderFullscreenButton() }
</div>
</div>
);
}
}
We like the structure of a React Component. The PropTypes and default props can be inside the class thanks to ES7, it's not possible with pure functions. And, in this example particularly, we have many function to render sub-components.
We could simply disable this rule if we don't like, but we really want to understand that and we care about performance and React good practices. So, maybe you can help us.
How can you help me ? I would get other opinions about this interesting problematic. What are the arguments in favor of pure functions ?
Maybe the solution is not to change ControlBar Component in pure function but just to improve it. In this case, what would be your advices to do that ?
Thanks a lot for your help !

Categories

Resources