Material UI: bug with react transition group v 2.2.0 - reactjs

I'm using <Transition> as explained in main documentation of React Transition Group.
import React from 'react';
import PropTypes from 'prop-types';
import Transition from 'react-transition-group/Transition';
const defaultStyle = {
transition: `opacity 300ms ease-in-out`,
opacity: 0,
};
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
};
const Fade = ({
in: inProp,
children,
}) => (
<Transition in={inProp} timeout={300}>
{state => (
<div
style={{
...defaultStyle,
...transitionStyles[state],
}}
>
{children}
</div>
)}
</Transition>
);
Fade.propTypes = {
in: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
};
export default Fade;
It works, but not so well with Material UI, especially with Buttons, everywhere on my app: when I click on them, appears a white div behind them:
<div in="false" style="position: absolute; top: -88.218px; left: -97.218px; height: 220.436px; width: 220.436px; border-radius: 50%; background-color: rgb(255, 255, 255);"></div>
and this weird error in console:
Warning: Unknown props `onExited`, `appear`, `enter`, `exit` on <div> tag. Remove these props from the element.
Those props are about Transition, but I can't understand the problem.
I'm using React 15.6.1, Material ui 0.18.7 and React Transition Group 2.2.0

I encountered this bug today, and I logged an issue + repro case on their github.
https://github.com/callemall/material-ui/issues/8046
(repro: https://codesandbox.io/s/q9o5q0l5nw)
I have tested in v1.0.0-beta.8 and it looks like it's working fine (https://codesandbox.io/s/r5nplz89nn).
The project's stance is essentially "material-ui v0.x is legacy code".
So your options are either; disable ripples across your app, fix it for them via a PR, or move to the unfinished v1 beta branch.

Related

Framer-Motion AnimatePresence page transition not firing exit animation on production build of Gatsby Site

I'm adding page transitions to a Gatsby site I'm working on using the framer-motion library. Currently, it works great on my local setup, but when I push to production, the exit animation no longer fires.
I'm wrapping my pages in the AnimatePresence component using gatsby-browser.js:
import React from 'react';
import {motion, AnimatePresence} from 'framer-motion';
export const wrapPageElement = ({element}) => (
<AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
);
And then have a motion.main component around the page content in my layout.js file:
/** #jsx jsx */
import * as React from "react"
import { Global } from "#emotion/react"
import { Box, Container, jsx } from "theme-ui"
import Seo from "./seo"
import Header from "./header"
import Footer from "./footer"
import CodeStyles from "../styles/code"
import SkipNavLink from "./skip-nav"
import { motion, AnimatePresence } from 'framer-motion'
type LayoutProps = { children: React.ReactNode; className?: string }
const Layout = ({ children, className = `` }: LayoutProps) => (
<React.Fragment>
<Global
styles={(theme) => ({
"*": {
boxSizing: `inherit`,
},
html: {
WebkitTextSizeAdjust: `100%`,
},
img: {
borderStyle: `none`,
},
pre: {
fontFamily: `monospace`,
fontSize: `1em`,
},
"[hidden]": {
display: `none`,
},
"::selection": {
backgroundColor: theme.colors.text,
color: theme.colors.background,
},
a: {
transition: `all 0.3s ease-in-out`,
color: `text`,
},
})}
/>
<Seo />
<SkipNavLink>Skip to content</SkipNavLink>
<Container>
<Header />
<motion.main
key={location.pathname}
initial={{ opacity: 0, y: 2.5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -2.5 }}
transition={{
type: "spring",
damping: 8,
mass: .6,
stiffness: 70,
}}
>
<Box id="skip-nav" sx={{ ...CodeStyles }} className={className}>
{children}
</Box>
</motion.main>
<Footer />
</Container>
</React.Fragment>
)
export default Layout
Does anyone have ideas as to why this might not be working when I deploy? (FYI I am using Netlify). I've tried many different semantic variations of this idea to no avail. Could it be how I'm declaring the key for the motion component? How I'm wrapping the AnimatePresence and motion components?
wrapPageElement is a shared API between Gatsby Browser (gatsby-browser.js) and Gatsby SSR (gatsby-ssr.js). Try adding the same wrapping component in the gatsby-ssr.js:
import React from 'react';
import {motion, AnimatePresence} from 'framer-motion';
export const wrapPageElement = ({element}) => (
<AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
);
In the docs:
Note: [...] It is recommended to use both APIs together.
The fact that works in the local environment and not in Netlify leads me to think that you can potentially have mismatching Node versions between both environments, causing a different dependency tree. If building the site locally makes your animation work, the issue is on Netlify's side (or in the Node versions installed, configuration). If the local built site doesn't (same behavior as Netlify's) the issue is on your code.
You can try setting in both environments the same Node version. To do so automatically, you can run:
node -v > .nvmrc
This will create a .nvmrc file containing the Node version (node -v). When Netlify finds this file at the root of your project, it uses the Node version as a base for installing the dependencies (docs: https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript)
Try both workarounds and see how's the debugging. As I said, if both environments has the exact same configuration the built shouldn't work either locally so the issue should be in your code (missing wrapPageElement wrapping in gatsby-ssr.js, etc).
Try making motion.main a direct descendent of AnimatePresence.
E.g.
<AnimatePresence exitBeforeEnter>
<motion.main
key={location.pathname}
initial={{ opacity: 0, y: 2.5 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -2.5 }}
transition={{
type: "spring",
damping: 8,
mass: .6,
stiffness: 70,
}}
>
<Box id="skip-nav" sx={{ ...CodeStyles }} className={className}>
{children}
</Box>
</motion.main>
</AnimatePresence>
This is written in the docs. Probably because there are too many wrappers before your motion.main component.
The children of AnimatePresence can also be custom components. The
only requirement is that somewhere within this component is at least
one motion component with an exit prop.
Note: The custom component being removed from the DOM must still be a
direct descendant of AnimatePresence for the exit animation(s) it
contains to trigger.

React Customize Google calendar's tooltip does't appear

I am using this api to create customize google calendar.
https://github.com/ericz1803/react-google-calendar
Here are the codeSandbox for demo, the tooltip is perfectly working.
https://codesandbox.io/s/cocky-rgb-2y3t7?file=/src/App.js
Then, I copied the same code into mine project with all path, API ... changed.
Every things works well, but the tooltip doesn't appear any more.
import React from "react";
import "./styles.css";
import Calendar from "#ericz1803/react-google-calendar";
import { css } from "#emotion/react";
const API_KEY = "AIzaSyAKzScoADeBmp6qUsEzrwZhLqb6WARNFOo";
//replace calendar id with one you want to test
let calendars = [
{ calendarId: "c_7q0ai3mn1p9b880f7llhbnv364#group.calendar.google.com" }
];
let styles = {
//you can use object styles
calendar: {
borderWidth: "3px" //make outer edge of calendar thicker
},
today: css`
/* highlight today by making the text red and giving it a red border */
color: red;
border: 1px solid red;
`
};
export default function App() {
return (
<div className="App">
<body>
<div
style={{
width: "90%",
paddingTop: "50px",
paddingBottom: "50px",
margin: "auto",
maxWidth: "1200px"
}}
>
<Calendar apiKey={API_KEY} calendars={calendars} styles={styles} />
</div>
</body>
</div>
);
}
There is open issue for same on github.
Till it gets fixed, use the same version of the package which is used in the sandbox demo, it doesn't have that bug.
"#ericz1803/react-google-calendar": "4.0.1"

Jest and React Testing Library, how to test if an element is hidden?

I have a popover showing on Hover and I want to test it with Jest and React Testing Library to see if the element is hidden by default.
When I test manualy everything is ok by not when I test it with RTL.
I tried using not.toBeInTheDocument and not.toBeVisible but it seems that the element is always present in the DOM, any idea of how could I remove it from the DOM
JSX code:
<label
htmlFor="terms_and_conditions"
className="terms_and_conditions_box"
>
I agree with{" "}
<span className="terms_and_conditions_text" style={{ color: "blue" }}>
Terms and conditions
</span>
<div className="pop_over">No ice cream will be delivered</div>
</label>
CSS code:
.terms_and_conditions_text:hover + .pop_over {
display: block;
}
.pop_over {
background: rgb(199, 196, 196);
padding: 2rem;
width: 14rem;
border-radius: 15px;
position: absolute;
right: -18rem;
top: -2rem;
display: none;
}
TEST code:
test("popover responds to hover", () => {
render(<SummaryForm />);
//* popover initially is hidden
const nullPopover = screen.queryByText(/No ice cream will be delivered/i);
expect(nullPopover).not.toBeInTheDocument();
});
I have reproduced your code and for me the test does not work with toBeInTheDocument, but works with toBeVisible as long as display: none is there.
Here is the code of my test file, the first test does not pass, the second passes:
import React from "react";
import { render, screen } from "#testing-library/react";
import "#testing-library/jest-dom/extend-expect";
import SummaryForm from "./App";
describe("popover responds to hover", () => {
test("popover initially is hidden (test with toBeInTheDocument)", () => {
render(<SummaryForm />);
const nullPopover = screen.queryByText(/No ice cream will be delivered/i);
expect(nullPopover).not.toBeInTheDocument();
});
test("popover initially is hidden (test with toBeVisible)", () => {
render(<SummaryForm />);
const nullPopover = screen.queryByText(/No ice cream will be delivered/i);
expect(nullPopover).not.toBeVisible();
});
});
And here is the codesandbox where you can see it in action.

With React Styled Components, how do I declare the colour based on state collected via useSelector?

I'm using Styled Components in my app. I am collecting state via useSelector using React-Redux, this state returns either a 0 or 1 - 0 telling me the app is currently in light mode, 1 telling me the app is currently in dark mode.
How do I edit the style based on the value? I have tried the following...
const Logo = styled(motion.h1)`
color: ${themeColour ? "white" : "black"};
`;
However, this didn't work as the variable exists inside a function. I then tried to style the h1 component directly in the JSX like this...
<h1 style={{color: ${themeColour ? "white" : "black"}}}>
I got the following error...
"./src/components/Nav.js
SyntaxError: C:\Users\james\Documents\Web Development\Portfolio\Project 1 - RelaxStation\Code\relax-station\src\components\Nav.js: Unexpected token, expected "," (14:26)"
Could someone help me figure this out please? Below is my full code for the component (please note I couldn't indent it correctly when pasting the code in here, but it is correct in Visual Studio Code)...
// Libraries
import styled from "styled-components";
import { motion } from "framer-motion";
// Redux
import { useSelector } from "react-redux";
const Nav = () => {
// Get current theme colour code - Gives a 0 for light mode or a 1 for dark mode
const themeColour = useSelector((state) => state.theme);
return (
<StyledNav>
<Logo>
Relax<span>Station</span>
</Logo>
<div className="main">
<h4>MAIN</h4>
<ul className="main-links">
<li>Home</li>
<li>Artists</li>
<li>Albums</li>
</ul>
</div>
<div className="playlists">
<h4>PLAYLISTS</h4>
<ul className="playlist-links">
<li>Early morning</li>
<li>Studying</li>
<li>Driving</li>
<li>Ambience</li>
</ul>
</div>
</StyledNav>
);
};
const StyledNav = styled(motion.nav)`
position: static;
width: 10rem;
top: 0;
left: 0;
min-height: 95vh;
border: 2px solid rgba(255, 255, 255, 0.125);
`;
const Logo = styled(motion.h1)`
color: ${themeColour ? "white" : "black"};
`;
export default Nav;
Thanks in advance
You need to pass on the themeColor as a prop to tthe Logo component while render
<Logo themeColor={themeColor}>
Relax<span>Station</span>
</Logo>
and then use it with styled component like
const Logo = styled(motion.h1)`
color: ${props => props.themeColour? "white" : "black"};
`;
For the h1 tag your code didn't work because your syntax was incorrect
Below is the correct usage
<h1 style={{color: themeColour ? "white" : "black"}}>

Inline CSS styles in React: how to implement a:hover?

I quite like the inline CSS pattern in React and decided to use it.
However, you can't use the :hover and similar selectors. So what's the best way to implement highlight-on-hover while using inline CSS styles?
One suggestion from #reactjs is to have a Clickable component and use it like this:
<Clickable>
<Link />
</Clickable>
The Clickable has a hovered state and passes it as props to the Link. However, the Clickable (the way I implemented it) wraps the Link in a div so that it can set onMouseEnter and onMouseLeave to it. This makes things a bit complicated though (e.g. span wrapped in a div behaves differently than span).
Is there a simpler way?
I think onMouseEnter and onMouseLeave are the ways to go, but I don't see the need for an additional wrapper component. Here is how I implemented it:
var Link = React.createClass({
getInitialState: function(){
return {hover: false}
},
toggleHover: function(){
this.setState({hover: !this.state.hover})
},
render: function() {
var linkStyle;
if (this.state.hover) {
linkStyle = {backgroundColor: 'red'}
} else {
linkStyle = {backgroundColor: 'blue'}
}
return(
<div>
<a style={linkStyle} onMouseEnter={this.toggleHover} onMouseLeave={this.toggleHover}>Link</a>
</div>
)
}
You can then use the state of hover (true/false) to change the style of the link.
Late to party but come with solution. You can use "&" to defines styles for hover nth Child etc:
day: {
display: "flex",
flex: "1",
justifyContent: "center",
alignItems: "center",
width: "50px",
height: "50px",
transition: "all 0.2s",
borderLeft: "solid 1px #cccccc",
"&:hover": {
background: "#efefef"
},
"&:last-child": {
borderRight: "solid 1px #cccccc"
}
},
I'm in the same situation. Really like the pattern of keeping the styling in the components but the hover states seems like the last hurdle.
What I did was writing a mixin that you can add to your component that needs hover states.
This mixin will add a new hovered property to the state of your component. It will be set to true if the user hovers over the main DOM node of the component and sets it back to false if the users leaves the element.
Now in your component render function you can do something like:
<button style={m(
this.styles.container,
this.state.hovered && this.styles.hover,
)}>{this.props.children}</button>
Now each time the state of the hovered state changes the component will rerender.
I've also create a sandbox repo for this that I use to test some of these patterns myself. Check it out if you want to see an example of my implementation.
https://github.com/Sitebase/cssinjs/tree/feature-interaction-mixin
You can use Radium - it is an open source tool for inline styles with ReactJS. It adds exactly the selectors you need. Very popular, check it out - Radium on npm
Here's my solution using React Hooks. It combines the spread operator and the ternary operator.
style.js
export default {
normal:{
background: 'purple',
color: '#ffffff'
},
hover: {
background: 'red'
}
}
Button.js
import React, {useState} from 'react';
import style from './style.js'
function Button(){
const [hover, setHover] = useState(false);
return(
<button
onMouseEnter={()=>{
setHover(true);
}}
onMouseLeave={()=>{
setHover(false);
}}
style={{
...style.normal,
...(hover ? style.hover : null)
}}>
MyButtonText
</button>
)
}
Full CSS support is exactly the reason this huge amount of CSSinJS libraries, to do this efficiently, you need to generate actual CSS, not inline styles. Also inline styles are much slower in react in a bigger system. Disclaimer - I maintain JSS.
Made Style It -- in part -- because of this reason (others being disagreements with implementation of other libs / syntax and inline stylings lack of support for prefixing property values). Believe we should be able to simply write CSS in JavaScript and have fully self contained components HTML-CSS-JS. With ES5 / ES6 template strings we now can and it can be pretty too! :)
npm install style-it --save
Functional Syntax (JSFIDDLE)
import React from 'react';
import Style from 'style-it';
class Intro extends React.Component {
render() {
return Style.it(`
.intro:hover {
color: red;
}
`,
<p className="intro">CSS-in-JS made simple -- just Style It.</p>
);
}
}
export default Intro;
JSX Syntax (JSFIDDLE)
import React from 'react';
import Style from 'style-it';
class Intro extends React.Component {
render() {
return (
<Style>
{`
.intro:hover {
color: red;
}
`}
<p className="intro">CSS-in-JS made simple -- just Style It.</p>
</Style>
);
}
}
export default Intro;
I found a clean way to do this with a wrapper around useState, which I call useHover.
No additional libraries/frameworks needed.
const App = () => {
const hover = useHover({backgroundColor: "LightBlue"})
return <p {...hover}>Hover me!</p>
}
Code for the wrapper:
function useHover(styleOnHover: CSSProperties, styleOnNotHover: CSSProperties = {})
{
const [style, setStyle] = React.useState(styleOnNotHover);
const onMouseEnter = () => setStyle(styleOnHover)
const onMouseLeave = () => setStyle(styleOnNotHover)
return {style, onMouseEnter, onMouseLeave}
}
Note that useHover has an optional second parameter for a style when the component is not hovered.
Try it out here
Heres is another option using CSS variables . This requires a css hover definition ahead of time so I guess its not pure inline, but is very little code and flexible.
css (setup a hover state) :
.p:hover:{
color:var(--hover-color) !important,
opacity:var(--hover-opacity)
}
react:
<p style={{'color':'red','--hover-color':'blue','--hover-opacity':0.5}}>
Adding on to Jonathan's answer, here are the events to cover the focus and active states, and a using onMouseOver instead of onMouseEnter since the latter will not bubble if you have any child elements within the target the event is being applied to.
var Link = React.createClass({
getInitialState: function(){
return {hover: false, active: false, focus: false}
},
toggleHover: function(){
this.setState({hover: !this.state.hover})
},
toggleActive: function(){
this.setState({active: !this.state.active})
},
toggleFocus: function(){
this.setState({focus: !this.state.focus})
},
render: function() {
var linkStyle;
if (this.state.hover) {
linkStyle = {backgroundColor: 'red'}
} else if (this.state.active) {
linkStyle = {backgroundColor: 'blue'}
} else if (this.state.focus) {
linkStyle = {backgroundColor: 'purple'}
}
return(
<div>
<a style={linkStyle}
onMouseOver={this.toggleHover}
onMouseOut={this.toggleHover}
onMouseUp={this.toggleActive}
onMouseDown={this.toggleActive}
onFocus={this.toggleFocus}>
Link
</a>
</div>
)
}
onMouseEnter={(e) => {
e.target.style.backgroundColor = '#e13570';
e.target.style.border = '2px solid rgb(31, 0, 69)';
e.target.style.boxShadow = '-2px 0px 7px 2px #e13570';
}}
onMouseLeave={(e) => {
e.target.style.backgroundColor = 'rgb(31, 0, 69)';
e.target.style.border = '2px solid #593676';
e.target.style.boxShadow = '-2px 0px 7px 2px #e13570';
}}
Set default properties in the style or class then call onMouseLeave() and onMouseEnter() to create a hover functionality.
Checkout Typestyle if you are using React with Typescript.
Below is a sample code for :hover
import {style} from "typestyle";
/** convert a style object to a CSS class name */
const niceColors = style({
transition: 'color .2s',
color: 'blue',
$nest: {
'&:hover': {
color: 'red'
}
}
});
<h1 className={niceColors}>Hello world</h1>
In regards to styled-components and react-router v4 you can do this:
import {NavLink} from 'react-router-dom'
const Link = styled(NavLink)`
background: blue;
&:hover {
color: white;
}
`
...
<Clickable><Link to="/somewhere">somewhere</Link></Clickable>
This can be a nice hack for having inline style inside a react component (and also using :hover CSS function):
...
<style>
{`.galleryThumbnail.selected:hover{outline:2px solid #00c6af}`}
</style>
...
The simple way is using ternary operator
var Link = React.createClass({
getInitialState: function(){
return {hover: false}
},
toggleHover: function(){
this.setState({hover: !this.state.hover})
},
render: function() {
var linkStyle;
if (this.state.hover) {
linkStyle = {backgroundColor: 'red'}
} else {
linkStyle = {backgroundColor: 'blue'}
}
return(
<div>
<a style={this.state.hover ? {"backgroundColor": 'red'}: {"backgroundColor": 'blue'}} onMouseEnter={this.toggleHover} onMouseLeave={this.toggleHover}>Link</a>
</div>
)
}
You can use css modules as an alternative, and additionally react-css-modules for class name mapping.
That way you can import your styles as follows and use normal css scoped locally to your components:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';
class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}
export default CSSModules(Table, styles);
Here is a webpack css modules example
onMouseOver and onMouseLeave with setState at first seemed like a bit of overhead to me - but as this is how react works, it seems the easiest and cleanest solution to me.
rendering a theming css serverside for example, is also a good solution and keeps the react components more clean.
if you dont have to append dynamic styles to elements ( for example for a theming ) you should not use inline styles at all but use css classes instead.
this is a traditional html/css rule to keep html / JSX clean and simple.
This can be easily achived with material-ui makeStyles invocation:
import { makeStyles } from '#material-ui/core/styles';
makeStyles({
root: {
/* … */
'&:hover': { /* … */ }
},
});
This is a universal wrapper for hover written in typescript. The component will apply style passed via props 'hoverStyle' on hover event.
import React, { useState } from 'react';
export const Hover: React.FC<{
style?: React.CSSProperties;
hoverStyle: React.CSSProperties;
}> = ({ style = {}, hoverStyle, children }) => {
const [isHovered, setHovered] = useState(false);
const calculatedStyle = { ...style, ...(isHovered ? hoverStyle : {}) };
return (
<div
style={calculatedStyle}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{children}
</div>
);
};
I did something similar to this, but I do not use material-ui or makeStyles. I added the hover as a condition in my css in a style object:
const styles = {
hoverStyle: {
color: 'grey',
'&:hover': { color: 'blue !important' },
}
};
var NavBar = (props) => {
const menuOptions = ['home', 'blog', 'projects', 'about'];
return (
<div>
<div>
{menuOptions.map((page) => <div style={styles.hoverStyle} key={page}>{page}</div> )}
</div>
</div>
);
};
This worked for me.
You can just create an abstract hovering class e.g. for the color.
.hoverClassColor:hover {
color:var(--hover-color) !important;
}
Then for all Elements you wanna changes the color to red on hovering:
render() {
return <a className={'hoverClassColor'} style={{'--hover-color':'red'}}>Test</a>
}
For me its like inline, cause the classes are abstract and can be reused for all of your elements you wanna implement a color hovering.
I use this trick, a mix between inline-style and css:
//inline-style:
const button = {
fontSize: "2em",
};
return (
<div style={button} data-hover="button">
<style>{`[data-hover="button"]:hover {
font-size: 2.1em !important;
}`}</style>
{this.props.text}
</div>
);
Easiest way 2022:
useRef + inline onMouseOver/onMouseOut
example:
var bottomAtag = useRef(null)
...then inside return (
<a ref={bottomAtag} onMouseOver={() => bottomAtag.current.style.color='#0F0'} ...></a>
With a using of the hooks:
const useFade = () => {
const [ fade, setFade ] = useState(false);
const onMouseEnter = () => {
setFade(true);
};
const onMouseLeave = () => {
setFade(false);
};
const fadeStyle = !fade ? {
opacity: 1, transition: 'all .2s ease-in-out',
} : {
opacity: .5, transition: 'all .2s ease-in-out',
};
return { fadeStyle, onMouseEnter, onMouseLeave };
};
const ListItem = ({ style }) => {
const { fadeStyle, ...fadeProps } = useFade();
return (
<Paper
style={{...fadeStyle, ...style}}
{...fadeProps}
>
{...}
</Paper>
);
};
<Hoverable hoverStyle={styles.linkHover}>
<a href="https://example.com" style={styles.link}>
Go
</a>
</Hoverable>
Where Hoverable is defined as:
function Hoverable(props) {
const [hover, setHover] = useState(false);
const child = Children.only(props.children);
const onHoverChange = useCallback(
e => {
const name = e.type === "mouseenter" ? "onMouseEnter" : "onMouseLeave";
setHover(!hover);
if (child.props[name]) {
child.props[name](e);
}
},
[setHover, hover, child]
);
return React.cloneElement(child, {
onMouseEnter: onHoverChange,
onMouseLeave: onHoverChange,
style: Object.assign({}, child.props.style, hover ? props.hoverStyle : {})
});
}
I use a pretty hack-ish solution for this in one of my recent applications that works for my purposes, and I find it quicker than writing custom hover settings functions in vanilla js (though, I recognize, maybe not a best practice in most environments..) So, in case you're still interested, here goes.
I create a parent element just for the sake of holding the inline javascript styles, then a child with a className or id that my css stylesheet will latch onto and write the hover style in my dedicated css file. This works because the more granular child element receives the inline js styles via inheritance, but has its hover styles overridden by the css file.
So basically, my actual css file exists for the sole purpose of holding hover effects, nothing else. This makes it pretty concise and easy to manage, and allows me to do the heavy-lifting in my in-line React component styles.
Here's an example:
const styles = {
container: {
height: '3em',
backgroundColor: 'white',
display: 'flex',
flexDirection: 'row',
alignItems: 'stretch',
justifyContent: 'flex-start',
borderBottom: '1px solid gainsboro',
},
parent: {
display: 'flex',
flex: 1,
flexDirection: 'row',
alignItems: 'stretch',
justifyContent: 'flex-start',
color: 'darkgrey',
},
child: {
width: '6em',
textAlign: 'center',
verticalAlign: 'middle',
lineHeight: '3em',
},
};
var NavBar = (props) => {
const menuOptions = ['home', 'blog', 'projects', 'about'];
return (
<div style={styles.container}>
<div style={styles.parent}>
{menuOptions.map((page) => <div className={'navBarOption'} style={styles.child} key={page}>{page}</div> )}
</div>
</div>
);
};
ReactDOM.render(
<NavBar/>,
document.getElementById('app')
);
.navBarOption:hover {
color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Notice that the "child" inline style does not have a "color" property set. If it did, this would not work because the inline style would take precedence over my stylesheet.
I'm not 100% sure if this is the answer, but its the trick i use to simulate the CSS :hover effect with colours and images inline.
`This works best with an image`
class TestHover extends React.PureComponent {
render() {
const landingImage = {
"backgroundImage": "url(https://i.dailymail.co.uk/i/pix/2015/09/01/18/2BE1E88B00000578-3218613-image-m-5_1441127035222.jpg)",
"BackgroundColor": "Red", `this can be any color`
"minHeight": "100%",
"backgroundAttachment": "fixed",
"backgroundPosition": "center",
"backgroundRepeat": "no-repeat",
"backgroundSize": "cover",
"opacity": "0.8", `the hove trick is here in the opcaity slightly see through gives the effect when the background color changes`
}
return (
<aside className="menu">
<div className="menu-item">
<div style={landingImage}>SOME TEXT</div>
</div>
</aside>
);
}
}
ReactDOM.render(
<TestHover />,
document.getElementById("root")
);
CSS:
.menu {
top: 2.70em;
bottom: 0px;
width: 100%;
position: absolute;
}
.menu-item {
cursor: pointer;
height: 100%;
font-size: 2em;
line-height: 1.3em;
color: #000;
font-family: "Poppins";
font-style: italic;
font-weight: 800;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}
Before hover
.menu-item:nth-child(1) {
color: white;
background-color: #001b37;
}
On hover
.menu-item:nth-child(1):hover {
color: green;
background-color: white;
}
Example: https://codepen.io/roryfn/pen/dxyYqj?editors=0011
Here is how I do it with hooks in functional components. With onMouseEnter/Leave, im setting the color as state directly and consume it in the style prop of element (instead of setting hover state and using ternaries to change the state as shown in previous answers).
function App() {
const [col, setCol] = React.useState('white');
return (
<div className="App">
<button
style={{background: `${col}`}}
onMouseEnter={() => setCol("red")}
onMouseLeave={() => setCol("white")}
>
Red
</button>
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js" integrity="sha256-3vo65ZXn5pfsCfGM5H55X+SmwJHBlyNHPwRmWAPgJnM=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js" integrity="sha256-qVsF1ftL3vUq8RFOLwPnKimXOLo72xguDliIxeffHRc=" crossorigin="anonymous"></script>
<div id='root'></div>
This solution does use stylesheets. However, If your application uses an index.css - i.e. has a stylesheet that gets imported into your top-level component, you could just write the following code in there
.hoverEffect:hover {
//add some hover styles
}
Then in your React component, just add the className "hoverEffect" to apply the hover effect "inline".
If the hover state is being passed down as a prop, and you only want it to be applied to the child component, remove the :hover in your index.css, and do this instead.
function Link(props) {
return (
<a className={props.isHovered ? "hoverEffect" : ""}>Hover me<a/>
)
}
Directly use tag in your component like this:
<Wrapper>
<style>
.custom-class{
// CSS here
}
.custom-class:hover{
//query selector
}
</style>
<div className="custom-class"></div>
</Wrapper>

Resources