I'm trying to create a flip card effect where if I click on a card it flips over. However, if I then click on another card, I'd like the original card to flip back. This means having some sort of global toggle on all of the cards but one that gets overridden by the local toggle. I'm not sure of the most React-y way to do this - I've tried implementing it with the useState hook at both levels, but I'm not having much luck.
I'm using styled-components, with the 'flipped' prop determining the Y-transform.
Here's the flip card component, and you can see what I've tried so far:
const PortfolioItem = props => {
const [flipped, setFlipped] = useState(false)
return (
<PortfolioItemStyles onClick={() => setFlipped(!flipped)}>
// what I'm trying to say here is, if the individual card's 'flipped' is set to true,
use that, otherwise use props.flipped which will be set to false
<PortfolioItemInnerStyle flipped={flipped ? flipped : props.flipped}>
<PortfolioItemFront >
{props.image}
<PortfolioImageCover className="img-cover" />
<PortfolioItemHeader>{props.title}</PortfolioItemHeader>
</PortfolioItemFront>
<PortfolioItemBack>
<h1>Hello there</h1>
</PortfolioItemBack>
</PortfolioItemInnerStyle>
</PortfolioItemStyles>
)
}
function PortfolioStyles() {
const [ allFlipped, setAllFlipped ] = useState(false);
return (
<PortfolioContainer>
{portfolioItems.map(item => {
return <PortfolioItem image={item.image} flipped={allFlipped} title={item.title} onClick={() => setAllFlipped(false)} />
})}
</PortfolioContainer>
)
}
The logic I'm using is clearly faulty, but I was wondering what would be the 'best practice' way of doing this? In vanilla JS you'd use a single event handler and use event.target on it to make sure you were isolating the element, but I'm not sure how to handle this in React. Any help would be much appreciated.
I would personally manage the state only on the container component. Let's say you will store an index of the flipped card instead of a true/false status. The onClick would then change the current index and the flipped is computed by checking index === currentIndex. Something like this:
const PortfolioItem = props => {
return (
<PortfolioItemStyles>
<PortfolioItemInnerStyle flipped={props.flipped}>
<PortfolioItemFront >
{props.image}
<PortfolioImageCover className="img-cover" />
<PortfolioItemHeader>{props.title}</PortfolioItemHeader>
</PortfolioItemFront>
<PortfolioItemBack>
<h1>Hello there</h1>
</PortfolioItemBack>
</PortfolioItemInnerStyle>
</PortfolioItemStyles>
)
}
function PortfolioStyles() {
const [ currentFlippedIndex, setCurrentFlippedIndex ] = useState(-1);
return (
<PortfolioContainer>
{portfolioItems.map((item, index) => {
return <PortfolioItem image={item.image} flipped={index === currentFlippedIndex} title={item.title} onClick={() => setCurrentFlippedIndex(index)} />
})}
</PortfolioContainer>
)
}
Related
I'm trying to scroll sync my mapped components in react.
I have tried couple scroll sync libraries but I don't think they work on mapped components.(I would appreciate it if someone could help me out using scroll sync libraries. I have tried placing scrollSyncNode / scrollSyncPane inside the map function wrapping the component but it doesn't work.)
The below are two functions which I made where ref1 and ref2 scroll at the same time. I have tested the function with two independent components(not mapped) and they work fine.
I'm not completely sure if its possible but I want all the mapped components to equally share ref2.
the onScroll event will trigger the function.
I would really appreciate it if someone could give me a thorough guide to how I could scroll sync an independent component with the mapped components.
below is my parent component's code where both the independent component and mapped components are located:
const refOne = useRef();
const refTwo = useRef();
const handleScrollFirst = scroll => {
refTwo.current.scrollLeft = scroll.target.scrollLeft;
};
const handleScrollSecond = scroll => {
refOne.current.scrollLeft = scroll.target.scrollLeft;
};
return (
<ContentsWrapper>
<ThisIsRefOneDiv
style={{
width: '5vw',
overflowX: 'scroll',
}}
onScroll={handleScrollFirst}
ref={div1}
>
testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
</ThisIsRefOneDiv>
{data.map((group, idx) => (
<Card key={idx} color={group.color}>
<Objective data={group} />
{state && (
<Content>
<GanttData
data={group}
**onScroll={handleScrollSecond}
ref={refTwo}
/>
</Content>
)}
</Card>
))}
</ContentsWrapper>
);
}
Say I have a React functional component with some simple state:
import React, { useState } from 'react'
import { makeStyles } from "#material-ui/core"
export default function Basket() {
const [itemCount, setItemCount] = useState<number>(0)
return (
<div>
<Count count={itemCount} />
<button onClick={() => setItemCount(itemCount + 1)}>
Add One
</button>
</div>
)
}
function Count({count}: {count: number}) {
const classes = useStyles()
return (
<div className={classes.count}>
{count}
</div>
)
}
const useStyles = makeStyles({
count: {
backgroundColor: "yellow",
transition: "backgroundColor 2s ease" // ???
}
}
I want the Count component to apply a property whenever the count changes and then remove it again; say, turn on backgroundColor: yellow for 2 seconds and then gradually fade it over 1 second. What's the simplest way to achieve this?
Note that presumably this could be either triggered by a state change on the parent or by a re-rendering of the child. Alternatively, I could add the key property to <Count/> to force a re-mount of the child:
<Count
key={itemCount}
count={itemCount}
/>
Any of those would be acceptable; I'm looking for the simplest, cleanest solution, hopefully one that doesn't require additional state and is compatible with Material-UI styling APIs.
Just an idea.
const Component = () => {
useEffect(() => {
// will trigger on component mount
return () => {
// will trigger on component umount
}
}, [])
}
...
document.getElementById('transition').classList.add('example')
You can use useEffect along with useRef containing a reference to the element or directly getting it with document.getElementById and then update the transition class that way in component mount/unmount. Not sure if it'll work, I haven't tested it myself.
I have following React code
Code
What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.
So respectively and for other pictures.
I will be very grateful if you help me solve this problem.
Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.
updated code
Your component tree is App -> SolutionBox -> SolutionItem.
You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.
Step 1
Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.
function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
let callOnHover = state => {
if (_.isFunction(onHover)) {
onHover(state);
}
};
return (
<div className="solutions-section-item-box">
<img
src={solutionIMG}
alt=""
onMouseEnter={() => {
callOnHover(true);
}}
onMouseLeave={() => {
callOnHover(false);
}}
className="solutions-section-item-img"
/>
</div>
);
}
Step 2
Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.
function SolutionsSectionBox({ onBGChanged }) {
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
return (
<div className="solutions-section-box-box">
<SolutionItem
solutionIMG={Ecommerce}
onHover={state => {
callBGChanged(state === true ? "Ecommerce" : "default");
}}
/>
<SolutionItem
solutionIMG={SalesMarketing}
onHover={state => {
callBGChanged(state === true ? "SalesMarketing" : "default");
}}
/>
<SolutionItem
solutionIMG={Analytics}
onHover={state => {
callBGChanged(state === true ? "Analytics" : "default");
}}
/>
<SolutionItem
solutionIMG={Middleware}
onHover={state => {
callBGChanged(state === true ? "Middleware" : "default");
}}
/>
</div>
);
}
Step 3
In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.
export default function App() {
const [bgImage, setbgImage] = useState(E);
const onBGChanged = menuName => {
setbgImage(menuName);
};
return (
<div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
<SolutionBox onBGChanged={onBGChanged} />
</div>
);
}
In CSS
Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.
.AppEcommerce {
background-image: url(https://placekitten.com/600/600);
}
.AppSalesMarketing {
background-image: url(https://placekitten.com/500/800);
}
.AppAnalytics {
background-image: url(https://placekitten.com/800/500);
}
.AppMiddleware {
background-image: url(https://placekitten.com/700/700);
}
Extra
I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.
let callBGChanged = menuName => {
if (_.isFunction(onBGChanged)) {
onBGChanged(menuName);
}
};
Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.
First thing to do is make the background image dynamic in the div's style and use context:
// Put this outside the component
export const BackgroundContext = React.createContext(null);
// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);
// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
<div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
{/* -- snip */}
</BackgroundContext.Provider>
Now in your SolutionsSectionBoxItem component you can import the background context:
import BackgroundContext from "../App";
Then using that context and react's mouseover api, update the selected background image:
const setBackgroundImage = useContext(BackgroundContext);
// -- snip
<img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />
You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down
I made a toggle component (Accordion to be exact)
I am mapping through an array of objects and listing them like:
{object.map((o) => (
<Accordion key={o.id} title={o.question} className="item">
<div className="text"> { o.answer } <div/>
</Accordion>
))}
It renders something like this:
> Question 1
> Question 2
> Question 3
Now, every time I click a question, it toggles down to show the answer. All this works fine(I used hooks).
I want to be able to change the opacity of all the un toggled elements in this list when ONE of the questions is opened.
So if I open question 2, it becomes the "current item" and the opacity of question 2 and its answer should be 100% and all others(question1 an question3) should dim out or turn 50% opacity.. I am able to do it using :hover using css but that only works on hover.
Basically in theory, I should be able to select an item and remove the base class from all other items except the selected one. I don't know how to do that in reality. Help.
I feel like I'm missing something obvious.
const Accordion = ({ title, children, opened = false }) => {
const [show, setShow] = useState(opened);
const rotation = classnames('icon', {
'rotate': show,
});
const content = classnames('contents', {
'closed': !show,
});
useEffect(() => {
setShow(opened);
}, [opened]);
const toggle = useCallback(() => {
setShow(!show);
}, [show]);
return (
<div className='titleContainer' onClick={toggle}>
<div className={rotations}>
<i className='icon' />
</div>
<h5 className='title'>{title}</h5>
</div>
<div className={content}>{children}</div>
);
};
I finally understand what you mean, I think this is the answer:
const questionColor = (questionIndex, activeQuestion) => {
if (activeQuestion !== null && activeQuestion !== questionIndex) {
return "rgba(0,0,0,0.1)";
} else return "rgba(0,0,0,1)";
};
Working solution here:
https://codesandbox.io/s/cocky-hellman-fxrmc
I'd like to set overflow-y: hidden for the html selector (not an element) based on whether a React class component state variable is true. Is that possible?
If you mean you want to apply the overflow-y to the actual HTML tag then putting this code in the render worked for me
...
render() {
let html = document.querySelector('html');
this.state.test === "test" ? html.style.overflowY = "hidden" : html.style.overflowY = "visible";
return (
....
)
};
You can do
function MyComponent() {
// Set your state somehow
const [something, setSomething] = useState(initialState)
// Use it in your className`
return <div className={!!something && 'class-name'} />
}
If you have multiple class names to work with, a popular package is (aptly named) classnames. You might use it like so:
import cx from 'classnames'
function MyComponent() {
const [something, setSomething] = useState(initialState)
return <div className={cx({
'some-class' : something // if this is truthy, 'some-class' gets applie
})} />
}
Yes, It's possible. You can do this.
function App() {
const [visible, setVisible] = useState(false);
useEffect(() => {
const htmlSelector = document.querySelector("html");
htmlSelector.style.overflowY = visible ? "unset" : "hidden";
}, [visible]);
return (
<button onClick={() => setVisible(prevState => !prevState)}>
Toggle overflow
</button>
);
}
See the full example on CodeSandbox
You can use the style property to set inline CSS:
<div style={{ overflowY: hide ? 'hidden' : 'auto' }}>