I am trying to hide the the card once the time is equal with 0 but the timeLeft is never updated. Any idea what am I doing wrong in here?
<div style={{ flexDirection: "column", overflowY: "scroll" }}>
{sorted.map((d, i) => {
let timeLeft;
return (
<div key={i} className="card__container">
<ProgressBar
percentage={d.time * 10}
timing={res => (timeLeft = res)}
/>
</div>
);
})}
</div>
My ProgressBar looks like this
constructor(props) {
super(props);
this.state = {
timeRemainingInSeconds: props.percentage
};
}
componentDidMount() {
timer = setInterval(() => {
this.decrementTimeRemaining();
}, 1000);
}
decrementTimeRemaining = () => {
if (this.state.timeRemainingInSeconds > 0) {
this.setState({
timeRemainingInSeconds: this.state.timeRemainingInSeconds - 10
});
} else {
clearInterval(timer);
this.props.timing(0);
}
};
render() {
return (
<div className="Progress_wrapper">
<div
className="Progress_filler"
style={{ width: `${this.state.timeRemainingInSeconds / 2}%` }}
/>
</div>
);
}
}
I think the variable doesn't get updated, because your map function is kind of a functional component, which therefore is a render function. The variable is basically always reset if you make changes to your parent component.
You could create a real functional component by externalizing it and using a useState hook, kind of the following (I really don't know, but this could also work as definition inside your map function):
function CardContainer(props) {
const [timeLeft, setTimeLeft] = useState(0); // #todo set initial state based on your own definition
return (
<div key={i} className="card__container">
<ProgressBar
percentage={d.time * 10}
timing={res => setTimeLeft(res)}
/>
</div>
);
}
It would be easier if you can reference the library that you are using for ProgressBar. If it is not from a library, reference the source code for it.
I see some issues with the timing prop here.
<ProgressBar
percentage={d.time * 10}
timing={res => (timeLeft = res)}
/>
My understanding of your props for ProgressBar
Timing
This should be a static value that the component would just display as a visual indicator. We have to take note of the required units for this prop.
Percentage
This should be a value from 0 to 100 that will decide how the progress bar will visually look like.
Your question: Hide the component when timing is zero(or perhaps when percentage is at 100?).
For this, we can deploy the strategy to manipulate the style of the div that is wrapping this component.
<div style={{ flexDirection: "column", overflowY: "scroll" }}>
{sorted.map((d, i) => {
const percentage = d.time * 10; // this is taken from your own computation of percentage. I reckon that you need to divide by 100.
let customStyle = {};
if(percentage === 100){
customStyle = {
display: "none"
}
}
return (
<div key={i} className="card__container" style={customStyle}>
<ProgressBar
percentage={percentage}
timing={res => (timeLeft = res)}
/>
</div>
);
})}
</div>
Related
I passed a function to the child to check a checkbox and then to set setDispatch(true), the problem is that when I check the checkbox everything freezes and the website stops until I close and open again.
the function:
const [selectedChkBx, setSelectedChkBx] = useState({ arrayOfOrders: [] });
const onCheckboxBtnClick = (selected) => {
const index = selectedChkBx.arrayOfOrders.indexOf(selected);
if (index < 0) {
selectedChkBx.arrayOfOrders.push(selected);
} else {
selectedChkBx.arrayOfOrders.splice(index, 1);
}
setSelectedChkBx(selectedChkBx)
toggleDispatchButton()
};
const toggleDispatchButton = () => {
if (selectedChkBx.arrayOfOrders.length == 0) {
setDispatchButtonDisplay(false)
}
else {
setDispatchButtonDisplay(true)
}
}
Child Component:
<form style={{ display: 'block' }} >
<Row sm={1} md={2} lg={3}>
{ordersDisplay.map((value, key) => {
return (
<motion.div key={value.id} layout>
<DeliveryQueueComp
selectedChkBx={selectedChkBx}
toggleDispatchButton={toggleDispatchButton}
setDispatchButtonDisplay={setDispatchButtonDisplay}
value={value}
onCheckboxBtnClick={onCheckboxBtnClick}
/>
</motion.div>
)
})
}
</Row> </form>
DeliveryQueueComp Code:
<div
className={styles1.checkBox}
style={{ background: selectedChkBx.arrayOfOrders.includes(value.id) ?
'#f84e5f' : 'transparent' }}
onClick={() => { onCheckboxBtnClick(value.id) }}
>
<FontAwesomeIcon icon={faCheck} style={{ fontSize: '10px', opacity:
selectedChkBx.arrayOfOrders.includes(value.id) ? '1' : '0' }} />
</div>
If I remove toggleDispatchButtonDisplay, it works but then after a while the page freezes again.
Any thoughts about this?
As you didn't provide setDispatch code I don't know what it does, but for the rest I think I know why it's not working.
You're assigning the array and then set it to the state. If you want to do this that way you should only do a forceUpdate instead of a setState (as it has already been mutated by push and splice).
To properly update your state array you can do it like this
const onCheckboxBtnClick = (selected) => {
const index = selectedChkBx.arrayOfOrders.indexOf(selected);
if (index < 0) {
//the spread in array creates a new array thus not editing the state
setSelectedChkBx({
arrayOfOrders: [...selectedChkBx.arrayOfOrders, selected]
});
} else {
// same thing for the filter here
setSelectedChkBx({
arrayOfOrders: selectedChkBx.arrayOfOrders.filter(
(value) => value !== selected
)
});
}
toggleDispatchButton();
};
Here is the sandbox of your code https://codesandbox.io/s/eager-kalam-ntc7n7
I'm trying to create a library to generate skeleton-loading components on the fly. I like those skeletons components with a shimmer effect, but I don't want to create them manually. So my idea is something like this:
I create a component which receives the component that I want to load as a children
while loading, I render a copy of this component, but invisible
I iterate through the component elements, and render a skeleton component based on that
I've already made some progress so far, I created a nextjs boilerplate just for testing purposes, I'm new at opensource, and I will create a library with typescript and rollup when I'm done testing.
My code by now is in this repository: https://github.com/FilipePfluck/skeleton-lib-test
here is the core component:
const Shimmer = ({ children, isLoading, component: Component, exampleProps }) => {
const fakeComponentRef = useRef(null)
useEffect(()=>{
console.log(fakeComponentRef.current.children)
},[fakeComponentRef])
const texts = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong']
const contents = ['img', 'video', 'button']
const styleProps = ['borderRadius', 'padding', 'margin', 'marginRight', 'marginLeft', 'marginTop', 'marginBottom', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 'display', 'alignItems', 'justifyContent', 'flexDirection']
const renderElement = (element) => {
console.log('renderElement')
const object = {}
styleProps.forEach(s => Object.assign(object, {[s]: element.style[s]}))
if(texts.includes(element.localName)){
const fontSize = +document.defaultView.getComputedStyle(element, null)["fontSize"].replace('px','')
const lineHeight = +document.defaultView.getComputedStyle(element, null)["lineHeight"].replace('px','') | fontSize * 1.2
const numberOfLines = Math.round(element.offsetHeight / lineHeight)
const lineMarginBottom = lineHeight - fontSize
const lines = []
for(let i=0; i<numberOfLines; i++){
lines.push(i)
}
return(
<div style={{display: 'flex', flexDirection: 'column'}}>
{lines.map(line => (
<div
style={{
width: element.offsetWidth,
...object,
height: fontSize,
marginBottom: lineMarginBottom
}}
className="shimmer"
key={"line"+line}
/>))}
</div>
)
}
if(contents.includes(element.localName)){
return (
<div
style={{
width: element.offsetWidth,
height: element.offsetHeight,
...object
}}
className={'shimmer'}
/>
)
}
return (
<div
style={{
width: element.offsetWidth,
height: element.offsetHeight,
display: element.style.display,
alignItems: element.style.alignItems,
justifyContent: element.style.justifyContent,
flexDirection: element.style.flexDirection,
padding: element.style.padding,
margin: element.style.margin
}}
>
{!!element.children
? [...element.children]
.map(child => renderElement(child))
: null
}
</div>
)
}
return isLoading ? (
<>
<div style={{visibility: 'hidden', position: 'absolute'}} ref={fakeComponentRef}>
<Component {...exampleProps}/>
</div>
{fakeComponentRef?.current && renderElement(fakeComponentRef.current)}
</>
) : children
}
Basically I'm doing a recursive function to render the skeleton component. But I got a problem: the loading component is taking too long to render. Sometimes it takes over 40 seconds. And this is a massive problem, a loading component should not take longer to render then the loading itself. But I have no clue why is it taking so long. Also, I know that there is a lot of other problems, but my first goal was to render the component on the screen. I'd be pleased if any of you wanted to contribute to my project
UPDATE:
I kinda solved it. I commented the piece of code where the function calls itself, and it took the same time to render. So, I concluded something was interrupting the code during the first execution or before. I guess the problem was the fakeComponentRef?.current && before the function call. without this validation it wouldn't work because the ref starts as null, but I guess the UI doesn't change when a ref changes, as it would because of a state? Then I created a useEffect with a setTimeout to wait 1ms (just to make sure it is not null) to render the component and it worked.
I am trying to conditionally render part of an object (user comment) onClick of button.
The objects are being pulled from a Firebase Database.
I have multiple objects and want to only render comments for the Result component I click on.
The user comment is stored in the same object as all the other information such as name, date and ratings.
My original approach was to set a boolean value of false to each Result component and try to change this value to false but cannot seem to get it working.
Code and images attached below, any help would be greatly appreciated.
{
accumRating: 3.7
adheranceRating: 4
cleanRating: 2
date: "2020-10-10"
place: "PYGMALIAN"
staffRating: 5
timestamp: t {seconds: 1603315308, nanoseconds: 772000000}
userComment: "Bad"
viewComment: false
}
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button>i</button>
{/* <span>{item.userComment}</span> */}
</div >
)
})
You have to track individual state of each button toggle in that case.
The solution I think of is not the best but you could create a click handler for the button and adding a classname for the span then check if that class exists. If it exists then, just hide the comment.
Just make sure that the next sibling of the button is the target you want to hide/show
const toggleComment = (e) => {
const sibling = e.target.nextElementSibling;
sibling.classList.toggle('is-visible');
if (sibling.classList.contains('is-visible')) {
sibling.style.display = 'none'; // or set visibility to hidden
} else {
sibling.style.display = 'inline-block'; // or set visibility to visible
}
}
<button onClick={toggleComment}>i</button>
<span>{item.userComment}</span>
You can try like this:
const [backendData, setBackendData] = useState([]);
...
const showCommentsHandler = (viewComment, index) => {
let clonedBackendData = [...this.state.backendData];
clonedBackendData[index].viewComment = !viewComment;
setBackendData(clonedBackendData);
}
....
return(
<div>
....
<button onClick={() => showCommentsHandler(item.viewComment, index)}>i</button>
{item.viewComment && item.userComment}
<div>
You can store an array with that places which are clicked, for example:
const [ selectedItems, setSelectedItems] = React.useState([]);
const onClick = (el) => {
if (selectedItems.includes(el.place)) {
setSelectedItems(selectedItems.filter(e => e.place !== el.place));
} else {
setSelectedItems(selectedItems.concat(el));
}
}
and in your render function
const results = props.data.map((item, index) => {
return (
<div className='Results' key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={'read-only'}
value={item.accumRating}
style={{
width: 'auto',
alignItems: 'center',
}}
/>
<button onClick={() => onClick(item)}>i</button>
{ /* HERE */ }
{ selectedItems.includes(item.place) && <span>{item.userComment}</span> }
</div >
)
})
You need to use useState or your component won't update even if you change the property from false to true.
In order to do so you need an id since you might have more than one post.
(Actually you have a timestamp already, you can use that instead of an id.)
const [posts, setPosts] = useState([
{
id: 1,
accumRating: 3.7,
adheranceRating: 4,
cleanRating: 2,
date: "2020-10-10",
place: "PYGMALIAN",
staffRating: 5,
timestamp: { seconds: 1603315308, nanoseconds: 772000000 },
userComment: "Bad",
viewComment: false
}
]);
Create a function that updates the single property and then updates the state.
const handleClick = (id) => {
const singlePost = posts.findIndex((post) => post.id === id);
const newPosts = [...posts];
newPosts[singlePost] = {
...newPosts[singlePost],
viewComment: !newPosts[singlePost].viewComment
};
setPosts(newPosts);
};
Then you can conditionally render the comment.
return (
<div className="Results" key={index}>
<span>{item.place}</span>
<span>{item.date}</span>
<Rating
name={"read-only"}
value={item.accumRating}
style={{
width: "auto",
alignItems: "center"
}}
/>
<button onClick={() => handleClick(item.id)}>i</button>
{item.viewComment && <span>{item.userComment}</span>}
</div>
);
Check this codesandbox to see how it works.
I'm attempting to create an animation in which one element fades based upon the scroll position of another. I was able to get the scrolling element to work using React Spring, but I'm struggling to wrap my head around how to leverage state hooks without conditionals and only being able to set state at a component top level.
SandBox
const HomeView = () => {
const ref = useRef();
const [isVisible, setVisible] = useState(true);
const [{ offset }, set] = useSpring(() => ({ offset: 0 }));
const calc = (o) => {
if (o < 1004) {
return `translateY(${o * 0.08}vh)`;
} else {
// this won't work b/c im trying to useState in a Fn
setVisible(false);
return `translateY(${1012 * 0.08}vh)`;
}
};
const handleScroll = () => {
const posY = ref.current.getBoundingClientRect().top;
const offset = window.pageYOffset - posY;
set({ offset });
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
});
return (
<div ref={ref} className="home-view" style={homeViewStyles}>
<div style={topSectionStyles} className="top-content-container"></div>
<animated.div
className="well-anim"
style={{
width: "100vw",
height: "500px",
transform: offset.interpolate(calc),
zIndex: 300,
top: "340px",
position: "absolute"
}}
>
<h1>Well,</h1>
</animated.div>
{/* Trying to fade this component when above animated.div is right above it */}
<h2 style={{ paddingTop: "90px" }} fade={isVisible}>
Hello There!
</h2>
{/***************************************/}
</div>
);
};
You are almost there. I think the problem here is with the fade attribute. The setVisible function is invoked all right. I would introduce a second spring to deal with the opacity with the h2 element, based on the state of the isVisible variable.
const {opacity} = useSpring({opacity: isVisible ? 1 : 0});
<animated.h2 style={{ paddingTop: "90px", opacity }} >
Hello There!
</animated.h2>
https://codesandbox.io/s/wild-dew-tyynk?file=/src/App.js
I'm trying to play a gsap animation on component did mount in a Gatsby site but my refs aren't being applied.
const PricingList = ({ classes }) => {
let pricingCard = useRef([]);
useEffect(() => {
console.log('start Animation', pricingCard.current);
TweenMax.staggerFrom(pricingCard.current, 0.4, { opacity: 0, y: 100 }, 0.5);
}, []);
return (
<StaticQuery
query={graphql`
{
Prices: contentfulConfig {
pricing {
priceBand {
title
price
}
priceBand2 {
price
title
}
priceBand3 {
price
title
}
}
}
}
`}
render={(data) => (
<Fragment>
<div className={classes.Container}>
<PricingItem
ref={(el) => {
pricingCard.current[0] = el;
}}
/>
<PricingItem
ref={(el) => {
pricingCard.current[1] = el;
}}
/>
<PricingItem
ref={(el) => {
pricingCard.current[2] = el;
}}
/>
</div>
</Fragment>
)}
/>
);
};
I have tried -
pricingCard.current.push(el);
without any luck, I just get an empty array in console.
I have also tried -
useEffect(() => {
console.log('start Animation', pricingCard.current);
TweenMax.staggerFrom(pricingCard.current, 0.4, { opacity: 0, y: 100 }, 0.5);
}, [pricingCard]);
Thinking it might need to wait to be updated after the component mounted, but no luck.
Any help would be appreciated.
Thanks in advance.
For animation you might want to use useLayoutEffect instead of useEffect.
Is it going to be always 3 PricingItems? In this case you can create 3 separate refs for each (const pricingItem1 = useRef(null)) and in render <PricingItem ref={pricingItem1} />.
Also can you confirm that the function from the render prop has been called?