I would like to start the Counter when the related state value is true.
According to the document, the counter can be triggered with a button for example if the button is inside the Component. I would like to find a way how to start is from outside of the Component. I can't figure it out how to trigger the start.
<CountUp start={0} end={100}>
{({ countUpRef, start }) => (
<div>
<span ref={countUpRef} />
<button onClick={start}>Start</button>
</div>
)}
</CountUp>
Reference: https://www.npmjs.com/package/react-countup
I think this is said in the documentation
<CountUp start={0} end={100} delay={0}>
{({ countUpRef }) => (
<div>
<span ref={countUpRef} />
</div>
)}
</CountUp>
https://www.npmjs.com/package/react-countup#autostart-with-render-prop
EDIT: You can use the useCountUp hook
const { countUp, start, pauseResume, reset, update } = useCountUp(...)
Then you can get the start function and trigger it from outside. Below a simplified example from the doc.
import { useCountUp } from 'react-countup';
const ExternalComponent = () => {
const { countUp, start } = useCountUp({
start: 0,
end: 1234567,
});
useEffect(()=> start(), [])
return (
<div>
<div>{countUp}</div>
</div>
);
};
Here I trigger after the first render but you can easily put other condition on it.
if you pass redraw={true} as a prop to CountUp component ,then it will rerendered after any state change (or after any render)
Related
I have this codes in react:
const [categoryId, setCategoryId] = useState("");
{
catName.map((singleCategory, index) => {
const { catName, _id: categoryId } = singleCategory;
return (
<>
<div
className="category-single-div flex-3 center-flex-align-display"
key={index}
>
<p className="text-general-small2 category-custom-text">{catName}</p>
<div className="category-icons-div ">
<FaEdit
className="category-icon-edit"
onClick={() => {
setEditCategory(true);
setCategoryId(categoryId);
}}
/>
<AiFillDelete className="category-icon-edit category-icon-delete" />
</div>
</div>
</>
);
});
}
I used map to get an array of objects, and I needed their individual _id when a user clicks the edit button. I also want to call another function on the same edit button via onClick. It is working but displays an error.
Warning: Expected onClick listener to be a function, instead got a
value of string type.
I need that _id so as to pass it to a state and have access to it globally within the component at the top level.
Is this workable?
Your problem comes from the FaEdit component.
<FaEdit
id={categoryId}
className="category-icon-edit"
onClick={(editCategory, id) => { // you need to have onClick as a prop defined inside the FaEdit component
setEditCategory(editCategory);
setCategoryId(id);
}}
/>
Example...
export default function FaEdit({className, onClick, categoryId}){
const handleChange() => {
onClick(true, categoryId)
}
return(
<div className={className} onClick={() => handleChange()}>Click</div>
)
}
Currently I am facing the problem that I want to change a state of a child component in React as soon as a prop is initialized or changed with a certain value. If I solve this with a simple if-query, then of course I get an infinite loop, since the components are then rendered over and over again.
Component (parent):
function App() {
const [activeSlide, setActiveSlide] = useState(0);
function changeSlide(index) {
setActiveSlide(index);
}
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
Component (child):
function Button(props) {
const Icon = Icons[props.icon];
const [activeClass, setActiveClass] = useState("");
// This attempts an endless loop
if(props.active == props.index) {
setActiveClass("active");
}
function toggleView(e) {
e.preventDefault();
props.handler(props.index);
}
return(
<button className={activeClass} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
}
Is there a sensible and simple approach here? My idea would be to write the if-query into the return() and thus generate two different outputs, even though I would actually like to avoid this
The React docs have a nice checklist here used to determine if something does or does not belong in state. Here is the list:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
The active class does not meet that criteria and should instead be computed when needed instead of put in state.
return(
<button className={props.active == props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
This is a great use of useEffect.
instead of the if statement you can replace that with;
const {active, index} = props
useEffect(_ => {
if(active == index) {
setActiveClass("active");
}
}, [active])
The last item in the function is a dependency, so useEffect will only run if the active prop has changed.
React automatically re-renders a component when there is a change in the state or props. If you're just using activeClass to manage the className, you can move the condition in the className as like this and get rid of the state.
<button className={props.active === props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
however, if you still want to use state in the child component, you can use the useEffect hook to to update the state in the child component.
Try to use the hook useEffect to prevent the infinite loop. (https://fr.reactjs.org/docs/hooks-effect.html)
Or useCallback hook. (https://fr.reactjs.org/docs/hooks-reference.html#usecallback)
Try this and tell me if it's right for you :
function App() {
const [activeSlide, setActiveSlide] = useState(0);
const changeSlide = useCallback(() => {
setActiveSlide(index);
}, [index]);
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
I'm trying to make an app with a dynamic exit animation. Exit animation for SwitchTransition is set when the component is rendered and can't be changed later. It can be achieved using childFactory and TransitionGroup like here, but in this way the animation happens simultaneously. I need to make react wait until previous is unmounted - like in SwitchTransition. So the question: is it possible to have something like childFactory for SwitchTransition or make TransitionGroup behave the same way as SwitchTransition mode 'out-in'. Thanks in advance!
export default function App() {
const [state, setState] = React.useState(true);
const [direction, setDirection] = React.useState("left");
return (
<div className="main">
<SwitchTransition mode="out-in">
<CSSTransition
key={state}
addEndListener={(node, done) => {
node.addEventListener("transitionend", done, false);
}}
classNames={direction === "left" ? "fade--left" : "fade--right"}
>
<div className="button-container">
<Button
onClick={() => {
setState((state) => !state);
const dir = Math.random() > 0.5 ? "right" : "left";
setDirection(dir);
}}
>
{direction}
</Button>
</div>
</CSSTransition>
</SwitchTransition>
</div>
);
}
I'm making a Nextjs flashcard app. I'm passing a deck structure like this:
const deck = {
title: 'React 101',
flashcards: [flashcardOne, flashcardTwo],
};
as props to the Deck component. This component shows the first card in flashcards and a "next" button to increment the index and showing the next card in flashcards.
The Card component is very simple and shows the front and the back of the card depending of the state front.
This is what I got so far and it's working but if I click "next" when the card is showing the answer (flashcard.back), the next card is going to appear with the answer. And I'm not sure why, isn't the Card component re rendering when I click "next"? And if the component re renders, front is going to be set to true?
export default function Deck({ deck }) {
const [cardIndex, setCardIndex] = useState(0);
const { title, flashcards } = deck;
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>{title}</h1>
{cardIndex < flashcards.length ? (
<>
<div className={styles.grid}>
<Card flashcard={flashcards[cardIndex]} />
</div>
<button onClick={() => setCardIndex((cardIndex) => cardIndex + 1)}>
Next
</button>
</>
) : (
<>
<div>End</div>
<button>
<Link href='/'>
<a>Go to Home</a>
</Link>
</button>
<button onClick={() => setCardIndex(0)}>Play again</button>
</>
)}
</main>
</div>
);
}
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
return (
<>
{front ? (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(false)}
>
<p className={styles.front}>{flashcard.front}</p>
</div>
) : (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(true)}
>
<p className={styles.back}>{flashcard.back}</p>
</div>
)}
</>
);
}
When state changes, the card will re-render, but it will not re-mount. So, existing state will not be reset.
Call setFront(true) when the flashcard prop has changed:
const [front, setFront] = useState(true);
useLayoutEffect(() => {
setFront(true);
}, [flashcard]);
I'm using useLayoutEffect instead of useEffect to ensure front gets set ASAP, rather than after a paint cycle (which could cause flickering).
You can also significantly slim down the Card JSX:
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
const face = front ? 'front' : 'back';
return (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(!front)}
>
<p className={styles[face]}>{flashcard[face]}</p>
</div>
);
}
Okay, I guess I had the same issue. Since you're using functional components, and you're re-using the same component or in better words, you're not unmounting and remounting the component really, you're just changing the props, this happens. For this, you need to do useEffect() and then setFront(true).
Here's the code I used in my App.
useEffect(() => {
setFront(true);
}, [flashcard]);
This is what I have used in my Word.js file.
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
return(
<div className="results_wrapper">
{selected.map((r,i)=>{
let openState = (this.state.selectedImage==i)?true:false;
return(
<RenderPanel panelType={PanelType.large} openState={openState} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
this.setState({selectedImage:2})
console.log('wtfff'+this.state.selectedImage)
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})}
</div>
)
}
When I change the state of 'selectedImage', I expect the variable 'openState' to render differently within my map() function. But it does not do anything.
Console.log shows that the state did successfully change.
And what is even stranger, is if I run "this.setState({selectedImage:2})" within componentsDidMount(), then everything renders exactly as expected.
Why is this not responding to my state change?
Update
I have tried setting openState in my component state variable, but this does not help either:
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter(x=>this.state.selectedGroups.includes(x.domain))
console.log(selected)
let html = selected.map((r,i)=>{
return(
<RenderPanel key={i} panelType={PanelType.large} openState={this.state.openState[i]} title={r.domain+'.TheCommonVein.net'} preview={(openIt)=>(
<div className="result" onClick={openIt} style={{ boxShadow: theme.effects.elevation8}}>
<img src={r.url} />
</div>
)} content={(closeIt)=>(
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain,r.parent)}
<div onClick={()=>{
closeIt();
let openState = this.state.openState.map(()=>false)
let index = i+1
openState[index] = true;
this.setState({openState:openState},()=>console.log(this.state.openState[i+1]))
}
}>Next</div>
<img src={r.url} />
</div>
)}/>
)
})
return(
<div className="results_wrapper">
{html}
</div>
)
}
https://codesandbox.io/s/ecstatic-bas-1v3p9?file=/src/Search.tsx
To test, just hit enter at the search box. Then click on 1 of 3 of the results. When you click 'Next', it should close the pane, and open the next one. That is what I'm trying to accomplish here.
#Spitz was on the right path with his answer, though didn't follow through to the full solution.
The issue you are having is that the panel's useBoolean doesn't update it's state based on the openState value passed down.
If you add the following code to panel.tsx, then everything will work as you described:
React.useEffect(()=>{
if(openState){
openPanel()
}else{
dismissPanel();
}
},[openState, openPanel,dismissPanel])
What this is doing is setting up an effect to synchronize the isOpen state in the RenderPanel with the openState that's passed as a prop to the RenderPanel. That way while the panel controls itself for the most part, if the parent changes the openState, it'll update.
Working sandbox
I believe it's because you set openState in your map function, after it has already run. I understand you think the function should rerender and then the loop will run once more, but I think you'll need to set openState in a function outside of render.
The problem is that even though you can access this.state from the component, which is a member of a class component, there's nothing that would make the component re-render. Making components inside other components is an anti-pattern and produces unexpected effects - as you've seen.
The solution here is to either move RenderImages into a separate component altogether and pass required data via props or context, or turn it into a normal function and call it as a function in the parent component's render().
The latter would mean instead of <RenderImages/>, you'd do this.RenderImages(). And also since it's not a component anymore but just a function that returns JSX, I'd probably rename it to renderImages.
I tire to look at it again and again, but couldn't wrap my head around why it wasn't working with any clean approach.
That being said, I was able to make it work with a "hack", that is to explicitly call openIt method for selectedImage after rendering is completed.
RenderImages = (): React.ReactElement => {
let selected = this.state.results.filter((x) =>
this.state.selectedGroups.includes(x.domain)
);
return (
<div className="results_wrapper">
{selected.map((r, i) => {
let openState = this.state.selectedImage === i ? true : false;
return (
<RenderPanel
key={i}
panelType={PanelType.medium}
openState={openState}
title={r.domain + ".TheCommonVein.net"}
preview={(openIt) => {
/* This is where I am making explicit call */
if (openState) {
setTimeout(() => openIt());
}
/* changes end */
return (
<div
className="result"
onClick={openIt}
style={{ boxShadow: theme.effects.elevation8 }}
>
<img src={r.url} />
</div>
);
}}
content={(closeIt) => (
<div className="panel_wrapper">
<div className="panel_content">{r.content}</div>
{this.RenderPostLink(r.domain, r.parent)}
<div
onClick={() => {
closeIt();
this.setState({
selectedImage: i + 1
});
}}
>
[Next>>]
</div>
<img src={r.url} />
</div>
)}
/>
);
})}
</div>
);
};
take a look at this codesandbox.