how to differentiate elements in React - javascript

I have few images which I need to zoom little bit (using CSS i.e. transform : scale(1.2);) on onMouseEnter and revert on onMouseLeave.
I have below code which is working.
CSS:-
.style {
transform : scale(1.2);
transition: transform .5s ease-out;
}
.shrink {
transform : scale(1);
transition: transform .5s ease-out;
}
Variable Declared in constructor:-
this.state = {
isHovered: false
};
JS Method:-
handleHover(){
this.setState({
isHovered: !this.state.isHovered
});
}
logical op in render method:-
const imgClass = this.state.isHovered ? 'profile-pic style' : 'profile-pic shrink';
And two images:-
<Image title ='one' src="pics/pic1.png" circle className={imgClass}
onClick={ () => this.props.methodRef('one')} height="70" width="100"
onMouseEnter={this.handleHover} onMouseLeave={this.handleHover}/>
<Image title ='two' src="pics/pic2.png" circle className={imgClass}
onClick={ () => this.props.methodRef('two') } height="70" width="100"
onMouseEnter={this.handleHover} onMouseLeave={this.handleHover}/>
This code is working perfectly as I am expecting it to be. But the problem is both the images are zooming in and out at the same time.
How can I differentiate mouse movement over different-different elements in ReactJs.

You can wrap the Image components into one of your own that you can control and add states to. Or maybe another way would be to have an array in your parent component like:
state: {
images:[{id: "image1", isHovered: false},
{id:"image2", isHovered: false}]
}
So you can have your ids in the components:
<Image id={this.state.images[0].id} ...rest />
then handleHover:
handleHover(e) => {
const images = [...this.state.images]; //copy your array
const index = images.findIndex(el => (el.id === e.target.id));
images[index].isHovered = !images[index].isHovered;
this.setState({images});
}
This is overly complex, though. You should wrap it inside another component and manage state from there.

Related

React animate fade-in and fade-out with CSSTransitionGroup on conditionally rendered component

I have a card component that conditionally renders a check icon when this.state.isSelected is true. I want to animate the check icon when it renders. I also want to animate it when it leaves.
I have the following class component:
import { CSSTransitionGroup } from 'react-transition-group';
export default class AdoptablesFilterCard extends Component {
constructor(props) {
super(props);
this.state = {
isSelected: false,
cardHeader: props.cardHeader,
cardType: props.cardType,
}
//Click handler binding
this.handleClick = this.handleClick.bind(this);
}
handleClick = (e) => { //switches the state 'isSelected' when clicked
this.setState((prevState) => ({
isSelected: !prevState.isSelected
}))
}
render() {
const {isSelected} = this.state;
// let check;
// {isSelected ?
const check = <i className="far fa-check-circle"
key={this.state.cardHeader}></i>;
//:
//check = <div key={this.state.cardHeader}></div>}
return (
<div className="adoptables-filter-card" onClick={this.handleClick} ref={this.myRef}>
<div className="adoptables-filter-card-header">
{this.props.cardHeader}
</div>
<div className="adoptables-filter-card-body">
{(() => {
switch (this.props.cardType) {
case "animal": return(<i className={`fas fa-${this.props.cardHeader}`}></i>)
case "color": return(<div className="color-splotch" style={{background: this.props.cardHeader}}></div>)
}
})()}
</div>
{isSelected ? <CSSTransitionGroup
transitionName="icon"
transitionAppear={true}
transitionAppearTimeout={3000}
transitionEnter={false}
transitionLeave={false}>
{check}
</CSSTransitionGroup>
: null}
</div>
)
}
}
and the CSS classes for CSSTransitionGroup:
.icon-appear {
opacity: 0.01;
}
.icon-appear.icon-appear-active {
opacity: 1;
// transform: rotateY(360deg);
transition: opacity 3000ms ease-in;
}
This code works for rendering the check icon when the card is clicked and applying the animation (the 3000ms is so I can make sure it's there and activating). When the card is clicked again (de-selected) the check icon immediately disappears. I'd like to fade-out the check icon at this stage.
I found this while searching for an answer:
https://stackoverflow.com/questions/46916118/conditional-rendering-and-reactcsstransitiongroup-animation#=. You can see where I've commented out the conditional assignment of 'check'. It renders/hides the check icon, but the animation is not applied.
You can use CSSTransition. Replace this
{isSelected ? <CSSTransitionGroup
transitionName="icon"
transitionAppear={true}
transitionAppearTimeout={3000}
transitionEnter={false}
transitionLeave={false}>
{check}
</CSSTransitionGroup>
: null}
by
<CSSTransition
unmountOnExit
in={isSelected}
timeout={2000}
classNames="icon">
{check}
</CSSTransition>
style.css
.icon-enter {
opacity: 0;
}
.icon-enter-active {
opacity: 1;
transition: opacity 2000ms;
}
.icon-exit {
opacity: 1;
}
.icon-exit-active {
opacity: 0;
transition: opacity 2000ms;
}
You can see in CodeSandBox. Hope it helps

Reusable parametric css with styled components

I'm using styled-components library in my react app and I've come across an interesting problem I wasn't able to find an elegant solution to online. What I want to achieve is to have a reusable piece of code, maybe similar to sass mixins, that would allow me to extend all my buttons with code that animates background darken on hover.
const DarkenHover = css<{ active?: boolean; normalColor: string; activeColor: string }>`
background-color: ${p => (p.active ? p.normalColor : p.activeColor)};
&:hover {
background-color: ${p => darken(0.1)(p.active ? p.normalColor : p.activeColor)};
}
transition: background-color .1s ease-in;
`;
const FooButton = styled.div<{ active?: boolean }>`
${p => DarkenHover({
active: p.active,
normalColor: "red",
activeColor: "blue",
})}
`;
const FooButton = styled.div<{ active?: boolean }>`
${p => DarkenHover({
active: p.active,
normalColor: "white",
activeColor: "green",
})}
`;
This obviously is not valid syntax but it demonstrates my use case. How can I use this DarkenHover css object with attributes?
You can save the styles in a var and reuse later.
const animation = css`
background-color: ${p => p.active ? ThemeColors.backgroundDark : "white"};
&:hover {
background-color: ${p => darken(0.1)(p.active ? p.normalColor : p.activeColor)};
}
transition: background-color .1s ease-in;
}
`;
The when you use it in another component, it should be able to access its props:
const FooButton = styled.div`
${animation};
`
Also to be able to have separate props per each styled components, those can be passed via attrs method:
const FooButton = styled.div.attrs({normalColor: '#000000' })`
${animation}
`;

Problem with exit and enter animations using react-transition-group

I'm new to React and I'm looking for some help.
I'm trying to build a simple login page. The page gives users three options ('sign in with email', 'sign in with Google', or 'sign up') and displays a component based on the option chosen.
I wanted to add a simple cross-fade as these components enter and exit the DOM so I turned to react-transition-group.
The problem, however, is my components don't exit and enter properly. The old component appears to disappear instantly rather than fade out, two copies of the 'new' component are stacked on top of each other. One fades out and the other fades in. The old and new don't elegantly cross-fade into each other as I would like.
Example:
Initial login page
On click, two copies of the 'new' component appear, one fades in and the other fades out
transition completes and container now holds the new component as desired
I've looked over the docs for react-transition-group but I can't quite figure out what I'm doing wrong.
Here's my main component:
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
display: "loginInitial"
}
};
handleEmailLogin = () => {
this.setState(() => ({
display: "emailLogin"
}))
};
handleGoogleLogin = () => {
console.log("Logging in with google!")
this.props.startGoogleLogin()
};
handleEmailSignUp= () => {
this.setState(() => ({
display: "emailSignUp"
}))
};
handleBackToStart = () => {
this.setState(() => ({
display: "loginInitial",
}))
}
displayBox = () => {
if (this.state.display === "loginInitial") {
return <LoginPageInitial
handleEmailLogin={this.handleEmailLogin}
handleGoogleLogin={this.handleGoogleLogin}
handleEmailSignUp={this.handleEmailSignUp}
/>
} else if (this.state.display === "emailSignUp") {
return <LoginPageEmailSignUp
handleBackToStart={this.handleBackToStart}
/>
} else if (this.state.display === "emailLogin") {
return <LoginPageEmailLogin
handleBackToStart={this.handleBackToStart}
/>
}
}
render() {
return (
<div className="box-layout">
<div className="box-layout__box">
<h1 className="box-layout__title">My app</h1>
<p>Subtitle goes here blah blah blah blah.</p>
<TransitionGroup>
<CSSTransition
classNames="fade"
timeout={600}
key={this.state.display}
>
{this.displayBox}
</CSSTransition>
</TransitionGroup>
</div>
</div>
)
};
}
const mapDispatchToProps = (dispatch) => ({
startGoogleLogin: () => dispatch(startGoogleLogin())
})
export default connect(undefined, mapDispatchToProps)(LoginPage)
And here's the relevant CSS:
.box-layout {
align-items: center;
background: url('/images/bg.jpg');
background-size: cover;
display: flex;
justify-content: center;
height: 100vh;
width: 100vw;
}
.box-layout__box {
background: fade-out(white,.15);
border-radius: 3px;
text-align: center;
width: 30rem;
padding: $l-size $m-size
}
.box-layout__title {
margin: 0 0 $m-size 0;
line-height: 1;
}
// FADE
// appear
.fade-appear {
opacity: 0;
z-index: 1;
}
.fade-appear.fade-appear-active {
opacity: 1;
transition: opacity 600ms linear;
}
// enter
.fade-enter {
opacity: 0;
z-index: 1;
}
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 600ms linear;
}
// exit
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0;
transition: opacity 600ms linear;
}
I would be extremely appreciative if anyone can offer some advice. I've done a lot of googling but I can't find a solution to my problem.
[EDIT: MOSTLY SOLVED - I managed to solve the bulk of my problem. It appears I needed to place my 'key' prop in my component rather than component. Two versions of the 'new' component no longer appear and the container doesn't stretch. It does appear, however, that the 'old' component still doesn't fade out as I expected. It looks a lot better than it did though. I welcome any further insight on what I was/am doing wrong or how I can improve my code.)

change opacity after 200ms in component style ReactJS

I using ReactJS framework and I try that, this component to change its style after 200ms from opacity 0 to opacity 1. Is it possible to do such a setTimeout?
<GreetingHeadline styles={?} id={this.props.user.id} />
Here's a working example that uses toggles between hidden/visible classes. I've added the transition so the effect can be more easily seen (200ms is a very short time) but you can remove it in your code.
class Test extends React.Component {
constructor() {
super();
this.state = { classes: 'hidden' };
}
componentDidMount() {
setTimeout(() => this.setState({ classes: 'visible' }), 200);
}
render() {
const { classes } = this.state;
return <div className={classes}>Text to be rendered</div>;
}
}
ReactDOM.render(<Test />, document.getElementById('container'));
.hidden { opacity: 0; }
.visible { opacity: 1; transition: opacity 1s linear;}
<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>
<div id="container"></div>
Setting a class is the easiest way to change the opacity. Here's an example that also uses an animation to "smooth" the transition.
https://codesandbox.io/s/j371123nq9
You can put a variable to the state of the GreetingHeadline's parent component:
constructor() {
this.state = {
transparent: true;
}
}
Then you can use setTimeout in the componentDidMount lifetime's method:
componentDidMount() {
this.setTimeout(() => {
this.setState({ transparent: false });
}, 200);
}
Finally you can use variable from the state within your GreetingHeadline component's props:
<GreetingHeadline
styles={{ opacity: this.state.transparent ? '0.7' : '1' }}
id={this.props.user.id}
/>

Marquee Animation In Safari

I'm trying to recreate a marquee tag using CSS animations. Works fine in Chrome and Firefox, but for some reason in Safari(12.0.1), the animation either won't play or won't play correctly until I switch tabs.
Here's my CSS:
.marquee{
width:100%;
background-color:rgba(56, 38, 48, .8);
white-space:nowrap;
overflow:hidden;
box-sizing:border-box;
height:51px;
}
.marquee p {
display: inline-block;
padding-left: 100%;
animation: marquee 120s linear -3s infinite;
-webkit-animation: marquee 120s linear -3s infinite;
color: white;
}
#-keyframes marquee {
0% { transform: translate(0,0); }
100% { transform translate(-100%, 0); }
}
#-webkit-keyframes marquee {
0% { -webkit-transform: translate(0, 0); }
100% { -webkit-transform: translate(-100%, 0); }
}
HTML: This is a React application. Before the component mounts, it fetches a group of tickers and their values, then feeds them into a component that renders them:
componentWillMount(){
const params = {
method:'POST',
body:'key=1234567',
headers: {
"Content-type":"application/x-www-form-urlencoded; charset=UTF-8"
},
}
fetch('/tickers', params)
.then(res => res.json())
.then(data => {
const tickers = Object.assign({}, data)
this.setState({tickers})
console.log(this.state.tickers)
}
)
.catch(err => {
console.log(`could not fetch: ${err}`)
})
}
The tickers then get fed into this component where the marquee effect is being applied:
const Tickerbar = (props) => {
const displayNameStyle = {
color:'white',
}
const midStyle = {
color:'#CDBFBF',
marginRight:'20px'
}
const displayNames = Object.keys(props.tickers)
const displayNameAndMid = displayNames.map(cusip => {
return(
<span key={cusip}>
<span style={displayNameStyle}>
<b>{cusip.split('/').join('.')}:</b>
</span>
<span style={midStyle}>
<b>{(props.tickers[cusip]).toFixed(2)}</b>
</span>
</span>
)
})
return(
<div className="marquee">
<p>
{displayNameAndMid}
</p>
</div>
)
}
For reference, I am running macOS High Sierra 10.13.6
EDIT: I made a separate HTML markup using the same CSS, and it works fine. So I believe the problem has to do with getting the data from the database and rendering it to the component
EDIT: I seem to have some success adding by adding a 1 second delay to the animation time. I guess this gives the component time to fetch the tickers and render the animation.

Categories