I'm going to give you 2 versions of a component i wrote. Why does the FIRST one give me an infinite loop, while the second one works fine?
I isolated the problem but i am wondering why the logic doesn't follow 'under the hood'. No doubt some black magic from useState
//THROWS INFINTE LOOP ERROR
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Popover, PopoverHeader, PopoverBody } from 'reactstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faInfoCircle } from '#fortawesome/pro-light-svg-icons';
const HelpIcon = (props) => {
HelpIcon.propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired
};
const [isOpen, toggleIsOpen] = useState(false);
return (
<React.Fragment>
<span
className="pointer text-body"
id={props.id}
>
<FontAwesomeIcon icon={faInfoCircle} />
</span>
<Popover
trigger="legacy"
placement="left"
isOpen={isOpen}
target={props.id}
toggle={toggleIsOpen(!isOpen)}{//<-----look here!!!!!!!!!!!!!!!}
>
{props.title !== false && (
<PopoverHeader className="text-body bg-light">
{props.title}
</PopoverHeader>
)}
<PopoverBody className="text-xs cart__rebate_description text-body bg-white">
{props.children}
</PopoverBody>
</Popover>
</React.Fragment>
);
};
export default HelpIcon;
...AND...
//THIS ONE WORKS
//NOTICE THE EXTRA FUNCTION THAT CALLS USESTATE, INSTEAD OF CALLING IT DIRECTLY
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Popover, PopoverHeader, PopoverBody } from 'reactstrap';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faInfoCircle } from '#fortawesome/pro-light-svg-icons';
const HelpIcon = (props) => {
HelpIcon.propTypes = {
title: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired
};
const [isOpen, toggleIsOpen] = useState(false);
const toggle = () => toggleIsOpen(!isOpen);
return (
<React.Fragment>
<span
className="pointer text-body"
id={props.id}
>
<FontAwesomeIcon icon={faInfoCircle} />
</span>
<Popover
trigger="legacy"
placement="left"
isOpen={isOpen}
target={props.id}
toggle={toggle}
>
{props.title !== false && (
<PopoverHeader className="text-body bg-light">
{props.title}
</PopoverHeader>
)}
<PopoverBody className="text-xs cart__rebate_description text-body bg-white">
{props.children}
</PopoverBody>
</Popover>
</React.Fragment>
);
};
export default HelpIcon;
this is wrong, and it causes you the loop:
toggle={toggleIsOpen(!isOpen)} // you call the function in a loop
It should be:
toggle={() => toggleIsOpen(!isOpen)}
If you really want to use it your way you must do a double arrow function like this:
const toggle = isOpen => () => {
toggleIsOpen(isOpen)
}
// and use it like
toggle={toggle(!isOpen)}
You are instantly calling toggleIsOpen in the first example. You should always wrap it with another function if you want to call it with arguments other than toggle prop provides.
it is causing infinite since changing state cause re-render in react; if your function call don't cause re-render it will work( kinda, not causing infinite loop) something like this
foo = () => console.log(9)
toggle={foo()}
but since set state will cause re-render your app stuck; here is the call-stack (kinda)
render is called;
it will reach the set-state
state changes
calling render again
it will reach the set-state
state changes
calling render again
and ....
Related
App.js
import { Container, Row, Col } from "react-bootstrap";
import React, { useState, useEffect } from 'react';
import {person} from "./Data"
import DatesCounts from "./Components/DatesCounts";
import DatesList from "./Components/DatesList";
import DatesAction from "./Components/DatesAction";
function App() {
const [personData, setPersonData] = useState(person)
const onDelet =()=> {
setPersonData([])
}
const onViewData=() => {
setPersonData(person)
}
useEffect(()=>{
setPersonData([])
})
return (
<div className="color-body font">
<Container className="py-5">
<DatesCounts person={person} />
<DatesList person={person} />
<DatesAction deletData={onDelet} viewData={onViewData} />
</Container>
</div>
);
}
export default App;
DatesAction.js
import React from 'react';
import {Row,Col} from 'react-bootstrap';
const DatesAction = ({deletData , viewData}) => {
return (
<Row className=" justify-content-center my-2">
<Col sm="8" className="d-flex justify-content-between">
<button onClick={deletData} className="btn-style p-2">Clear All</button>
<button onClick={viewData} className="btn-style p-2">Show Data</button>
</Col>
</Row>
);
}
export default DatesAction;
I tried to execute useEffect to clear data in the beginning without success.
I also tried to execute onClick buttons Clear All and Show Data without success.
as you see the code is for Dates Reminder the componenets are working but the onClick buttons are not working also the useEffect doesn't work.
you need to send child components personData, instead of person. Since they are receiving the json instead of the useState information
<DatesCounts person={personData} />
<DatesList person={personData} />
To update the state try to use useEffect with dependency on your personData
useEffect(() => {}, [personData])
For setting the initial state, you can use use effect without dependency instead of setting it directly to useState.
useEffect(() => {
setPersonData(person);
}, [])
You don't need a hook especially when dealing with onclick events.
simply have a function which will reset the state and pass it as a prop to child component.
const initialState = { }; //definte initial state.
const [person, setPersons] = useState(initialState);
const resetState = () => {
setPersonState(initialState);
}
//render
<Child onReset={resetState} />
You are missing the dependency array in useEffect but anyway lets improve the performance of your code using React.memo and React.useCallback.
Wrap your function in a React.useCallback so that their reference will be same on next render and will improve some performance but to effectively use useCallback wrap the child component with React.memo
DatesAction.jsx
import React from "react";
import { Row, Col } from "react-bootstrap";
const DatesAction = React.memo(({ deletData, viewData }) => {
return (
<Row className=" justify-content-center my-2">
<Col sm="8" className="d-flex justify-content-between">
<button onClick={deletData} className="btn-style p-2">
Clear All
</button>
<button onClick={viewData} className="btn-style p-2">
Show Data
</button>
</Col>
</Row>
);
});
export default DatesAction;
App.jsx
import { Container, Row, Col } from "react-bootstrap";
import React, { useState, useEffect } from "react";
import { person } from "./Data";
import DatesCounts from "./Components/DatesCounts";
import DatesList from "./Components/DatesList";
import DatesAction from "./Components/DatesAction";
function App() {
const [personData, setPersonData] = useState(person);
const onDelet = React.useCallback(() => {
setPersonData([]);
}, [deps]);
const onViewData = React.useCallback(() => {
setPersonData(person);
}, [deps]);
useEffect(() => {
setPersonData([]);
}, [deps]);
return (
<div className="color-body font">
<Container className="py-5">
<DatesCounts person={person} />
<DatesList person={person} />
<DatesAction deletData={onDelet} viewData={onViewData} />
</Container>
</div>
);
}
export default App;
I am new to reactJS and stuck in an issue. i have a button in header that needs to toggle a class 'show' in a menu which is in some other file. I tried to use global state but do not know how to do that. here is what i did;
LAYOUT FILE
import React, { useState } from "react";
// importing header / menu etc.
function LayoutHome({ children }) {
const [state, setState] = React.useState({ MsgMenu: 'messages-dropdown' });
const handleOpenMsgMenu = (e) => {
e?.preventDefault();
setState({MsgMenu:'messages-dropdown show'});
};
return (
<>
<Header handleOpenMsgMenu={handleOpenMsgMenu} />
<MessageMenu handleOpenMsgMenu={state.MsgMenu} />
{children}
<Footer />
</>
);
}
HEADER
import React, { useState } from "react";
function Header({handleOpenMsgMenu}) {
<button type="button" onClick={handleOpenMsgMenu} className="header-notification-btn">MENU</button >
}
MENU
import React, { useState } from "react";
function MessageMenu({handleOpenMsgMenu}) {
<div id="messages-dropdown" className={handleOpenMsgMenu}>
// CONTENT
</div>
}
To achieve this you can use useState hook to toggle the display of the Menu.
create a new toggle state in global and pass it onto the menu component.
below is the complete code.
import React from "react";
export default function App({children}) {
const [state, setState] = React.useState({ MsgMenu: 'messages-dropdown' });
const [toggle, setToggle] = React.useState(false);
const handleOpenMsgMenu = (e) => {
e?.preventDefault();
setToggle(!toggle);
};
return (
<>
<Header handleOpenMsgMenu={handleOpenMsgMenu} />
<MessageMenu handleOpenMsgMenu={state.MsgMenu} toggle={toggle} />
{children}
</>
);
}
// Header
import React from "react";
function Header({handleOpenMsgMenu}) {
return <button type="button" onClick={handleOpenMsgMenu} className="header-notification-btn">MENU</button >
}
// Menu
import React from "react";
function MessageMenu({handleOpenMsgMenu, toggle}) {
return <div id="messages-dropdown" style={{display: toggle?"block":"none"}}>
<ul>
<li>
{handleOpenMsgMenu}
</li>
</ul>
</div>
}
You can toggle state with !value and then change your class depending on that value
setMenu(() => {
return {
...menu,
show: !menu.show // toggle
};
});
I've made a sample here
For the global state, check out Context or Redux
I am trying to use useState to conditionally render a div but for the life of me I can not figure out how to do it. I think I am close but I can not see what I am doing wrong, I have entirely misunderstood how to do this. What am I doing wrong? I have written this and it does not work..
import React, { useState } from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faWindowClose } from '#fortawesome/free-solid-svg-icons'
function ElementMenuHeader() {
const [elementMenuOpenClose, setElementMenuOpenClose] = useState(true)
const handleClick = () => setElementMenuOpenClose(false)
return (
<div id="App-Close-Element-Menu-Container"
style={{ display: elementMenuOpenClose ? 'block' : 'none'}}
>
<button id="App-Close-Element-Menu"
onClick={() => handleClick }
>
<FontAwesomeIcon icon={faWindowClose} />
</button>
</div>
);
}
export default ElementMenuHeader
Ideally I would like to be able to set the state of elementMenuOpenClose from other components too, but I will cross this bridge first I think.
You can just use a ternary operator and check a against the state in the operator condition return the div you would like based on the state:
{(elementMenuOpenClose? <div>some tags A </div>: <div>Some tags B </div>)}
You may have a parent component of ElementMenuHeader.
Let's say that is ParentElementMenuHeader.
Inside ParentElementMenuHeader component, you can define like
const ParentElementMenuHeader = () => {
const [elementMenuOpenClose, setElementMenuOpenClose] = useState(true);
...
return (
...
{elementMenuOpenClose && (
<ElementMenuHeader
setElementMenuOpenClose={setElementMenuOpenClose}
...
/>
)}
...
);
}
Inside ElementMenuHeader component,
import React, { useState } from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faWindowClose } from '#fortawesome/free-solid-svg-icons'
function ElementMenuHeader({setElementMenuOpenClose}) {
const handleClick = () => setElementMenuOpenClose(false)
return (
<div id="App-Close-Element-Menu-Container">
<button id="App-Close-Element-Menu" onClick={handleClick}>
<FontAwesomeIcon icon={faWindowClose} />
</button>
</div>
);
}
export default ElementMenuHeader
I've got a few React functional Components that I would like to share a state. In this example two toggle buttons that would conditionally show/hide a searchbar and a navbar.
--Solution, based on the accepted answer, on the bottom--
I'm completely new to useContext() and I keep running into the following error in the console:
Uncaught TypeError: setSearchbarToggle is not a function This goes for both buttons.
Bellow I have a filtered example code. It is just for the example I use the states in one file. In real life I would re-use the states in multiple functional components.
This is my header.js
import React, { useState, useContext } from "react"
import "./header.sass"
import { Context } from "./HeaderContext"
export const Header = () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export default Header
And this is my HeaderContext.jsx
import React, { createContext, useState } from "react";
import PropTypes from "prop-types";
export const Context = createContext({});
export const Provider = props => {
const {
navbarToggle: initialNavBarToggle,
searchbarToggle: initialSarchbarToggle,
children
} = props;
const [navbarToggle, setNavbarToggle] = useState(initialNavBarToggle);
const [searchbarToggle, setSearchbarToggle] = useState(initialSarchbarToggle);
const headerContext = {
navbarToggle, setNavbarToggle,
searchbarToggle, setSearchbarToggle
};
return <Context.Provider value={headerContext}>{children}</Context.Provider>;
};
export const { Consumer } = Context;
Provider.propTypes = {
navbarToggle: PropTypes.bool,
searchbarToggle: PropTypes.bool
};
Provider.defaultProps = {
navbarToggle: false,
searchbarToggle: false
};
I hope you can shed some light on this for me
--edit--
This is my code based on the accepted answer.
import React, { useContext } from "react"
import { Provider,Context } from "./HeaderContext"
export const HeaderWithContext= () => {
const headerContext = useContext(Context)
const { navbarToggle, setNavbarToggle, searchbarToggle, setSearchbarToggle } = headerContext
return (
<React.Fragment>
<div className={"sticky-top"}>
<button onClick={ () => setNavbarToggle( !navbarToggle )}> Toggle Menu </button>
<button onClick={ () => setSearchbarToggle( !searchbarToggle )}> Toggle Search </button>
{navbarToggle && <h3>Menu is showing</h3>}
{searchbarToggle && <h3>Searchbar is showing</h3>}
</div>
</React.Fragment>
)
}
export const Header = () => {
return (
<Provider>
<HeaderWithContext/>
</Provider>
)
};
One of the parent components, e.g. App, must wrap the header (or one of its ancestor components) with Context.Provider:
import { Provider } from "./HeaderContext"
...
<Provider>
<Header />
</Provider>
I have a react single page app, with multiple components. For the 5th component(visible only when scrolled down) I have a counter . Now I am using react-countup library to achieve the counter function. However , the counter starts soon as the page is loaded . Is it possible for countup to begin once we scroll down to the component.
Animation happens only once(which is good)after the page is loaded, but I would like the counter not to begin soon after the page is loaded, but when user scrolls down to the component the first time.
My code looks like this:
render() {
return (
<div className={style.componentName}>
<h2>Heading</h2>
<div className={style.col}>
<div>My counter</div>
<CountUp className={style.countup} decimals={1} start={0} end={25} suffix=" %" duration={3} />
</div>
</div>)}
Updated code:
import CountUp, { startAnimation } from 'react-countup';
import VisibilitySensor from 'react-visibility-sensor';
class className extends Component {
state = {
scrollStatus: true
};
onVisibilityChange = isVisible => {
if (isVisible) {
if (this.state.scrollStatus) {
startAnimation(this.myCountUp);
this.setState({ scrollStatus: false });
}
}
}
render() {
return (
<div className={style.componentName}>
<h2>Heading</h2>
<VisibilitySensor onChange={this.onVisibilityChange} offset = {{ top:
10}} delayedCall>
<CountUp className={style.countup} decimals={1} start={0} end={25}
suffix=" %" duration={3} ref={countUp => { this.myCountUp= countUp;}}/>
</VisibilitySensor>
</div>)}
}
The API may have changed since last year. I manage to make this work with this code now :
import React from "react";
import CountUp from "react-countup";
import VisibilitySensor from 'react-visibility-sensor';
const MyComponent = () => (
<>
<CountUp end={100} redraw={true}>
{({ countUpRef, start }) => (
<VisibilitySensor onChange={start} delayedCall>
<span ref={countUpRef} />
</VisibilitySensor>
)}
</CountUp>
</>
);
export default App;
I use this component inside a tab, so the redraw={true} prop is only here to redraw the animation on tabChange.
Per React CountUp's README, you can use the startAnimation hook to manually kick off the animation. Combine this with something like react-visibility-sensor, and you can wait to kick off the animation until it is visible in the user's browser.
import React, {Component} from 'react';
import CountUp, {startAnimation} from 'react-countup';
import './App.css';
import VisibilitySensor from 'react-visibility-sensor';
const style = {
componentName: {},
col: {},
countup: {},
};
class App extends Component {
constructor(props) {
super(props);
this.onVisibilityChange = this.onVisibilityChange.bind(this); // Bind for appropriate 'this' context
}
onVisibilityChange(isVisible) {
if (isVisible) {
startAnimation(this.myCountUp);
}
}
render() {
return (
<div className={style.componentName}>
<h2>Heading</h2>
<div className={style.col}>
<div>My counter</div>
<VisibilitySensor
onChange={this.onVisibilityChange}
delayedCall // Prevents react apps triggering elements as visible before styles are loaded
>
<CountUp className={style.countup} decimals={1} start={0} end={25} suffix=" %" duration={3}
ref={countUp => { this.myCountUp = countUp; }} // From react-countup README
/>
</VisibilitySensor>
</div>
</div>
);
}
}
export default App;
As is, it will startAnimation every time you scroll to the countup. If you want to only do that once, just add a piece of state that gets set after the first render (and then prevent it from doing startAnimation again based on that altered state).
Less elegant (not recommended) ways to accomplish the same effect might include:
Use the built-in animation triggers (i.e. changing the props duration, end, start) by setting them equal to some state that changes when the user scrolls down
Leveraging the onStart prop, called before the animation starts, to delay starting the animation until the user scrolls down
EDIT: Update to address your second question
Unfortunately, it looks like the react-countup library doesn't expose a way to prevent startAnimation on startup.
But we can hack together a fairly elegant fix by manipulating the end prop using state instead:
import React, {Component} from 'react';
import CountUp, {startAnimation} from 'react-countup';
import './App.css';
import VisibilitySensor from 'react-visibility-sensor';
const style = {
componentName: {},
col: {},
countup: {},
};
class App extends Component {
state = {
didViewCountUp: false
};
onVisibilityChange = isVisible => {
if (isVisible) {
this.setState({didViewCountUp: true});
}
}
render() {
return (
<div className={style.componentName}>
<h2 style={{fontSize: '40em'}}>Heading</h2>
<VisibilitySensor onChange={this.onVisibilityChange} offset={{
top:
10
}} delayedCall>
<CountUp className={style.countup} decimals={1} start={0} end={this.state.didViewCountUp ? 25 : 0}
suffix=" %" duration={3} />
</VisibilitySensor>
</div>)
}
}
export default App;
Here's my implementation. It just runs once and also doesn't re-render every time the component enters viewport to check visibility.
Dependencies:
1. react-countup v.4.3.2
2. react-visibility-sensor v.5.1.1
import React, { useState } from "react";
import CountUp from "react-countup";
import VisibilitySensor from "react-visibility-sensor";
const Ticker = ({ className, ...rest }) => {
const [viewPortEntered, setViewPortEntered] = useState(false);
return (
<CountUp {...rest} start={viewPortEntered ? null : 0}>
{({ countUpRef }) => {
return (
<VisibilitySensor
active={!viewPortEntered}
onChange={isVisible => {
if (isVisible) {
setViewPortEntered(true);
}
}}
delayedCall
>
<h4 className={className} ref={countUpRef} />
</VisibilitySensor>
);
}}
</CountUp>
);
};
export default Ticker;
Here's how to use it:
<Ticker className="count" end={21} suffix="M+" />
Here is my solution using class based component
Note: import two Libraries first run this code
react-visibility-sensor
react-countup
import React from "react";
import CountUp from "react-countup";
import VisibilitySensor from 'react-visibility-sensor';
class CountDown extends React.Component {
render() {
return (
<React.Fragment>
<CountUp start={0} end={100} prefix="+" duration="2">
{({ countUpRef, start }) => (
<VisibilitySensor onChange={start} delayedCall>
<span ref={countUpRef} />
</VisibilitySensor>
)}
</CountUp>
</React.Fragment>
)
}
}
export default CountDown;
<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>
The docs for that library have a way to manually start the counter. I would use that approach to start the counter once a user has scrolled to the required distance.
import React, { Component } from 'react';
import CountUp, { startAnimation } from 'react-countup';
const MyComponent = () => (
<div>
<CountUp className="CountUp" start={0} end={100} duration={3} ref={(countUp) => {
this.myCountUp = countUp;
}} />
<button className="Button" onClick={(event) => {
startAnimation(this.myCountUp);
}}>Count me up!</button>
</div>
);
export default App;
Link to Github. Read the README at the very bottom.
you can have look at my functional component to achieve this
import React from "react";
import { Box } from "#material-ui/core";
import CountUp from "react-countup";
import VisibilitySensor from "react-visibility-sensor";
export default function Counter() {
const [focus, setFocus] = React.useState(false);
return (
<Box component="div">
<CountUp start={focus ? 0 : null} end={100} duration={5} redraw={true}>
{({ countUpRef }) => (
<div>
<span ref={countUpRef} />
<VisibilitySensor
onChange={isVisible => {
if (isVisible) {
setFocus(true);
}
}}
>
<a>+</a>
</VisibilitySensor>
</div>
)}
</CountUp>
</Box>
);
}
Simply add below prop
enableScrollSpy: true
<CountUp enableScrollSpy={true} end={75}/>
react-visibility-sensor doesn't seem to be maintained at the moment and produces warnings related to findDOMNode which is deprecated.
I've used react-waypoint in my functional component which makes the animation only trigger once when the user has the child of the waypoint component in view.
// Ticker.tsx
import React, { useState } from "react";
import CountUp from "react-countup";
import { Waypoint } from "react-waypoint";
type TickerProps = {
end: number;
suffix: string;
}
const Ticker: React.FC<TickerProps> = ({ end, suffix }) => {
const [viewPortEntered, setViewPortEntered] = useState(false);
const onVWEnter = () => {
setViewPortEntered(true);
}
return (
<Waypoint onEnter={onVWEnter} >
<div>
{viewPortEntered && <CountUp end={end} suffix={suffix} start={0} /> }
</div>
</Waypoint>
);
};
export default Ticker;
<CountUp
className={styles.number}
start={0}
end={number}
enableScrollSpy={true}
scrollSpyDelay={0}
suffix={index !== -1 ? "+" : ""}
/>
try this
scroll spy should do the work
Here's an alternative solution using react-in-viewport package. Idea is to define this element and use <CountUpInViewport> instead of <CountUp>.
This example fires on the first entry. One could do variations on this to refire every time it enters/leaves the viewport.
import React from 'react';
import { useRef } from 'react';
import CountUp from 'react-countup';
import { useInViewport } from 'react-in-viewport';
let CountUpInViewport = props => {
const ref = useRef();
const { enterCount } = useInViewport(ref);
return (
<div ref={ref}>
{
enterCount > 0 ?
<CountUp key='in' {...props}></CountUp> :
<CountUp key='out' {...props}></CountUp> // ensures drawn when not visible
}
</div>
);
}