React js hover an element and disable hover when leave the element - reactjs

I have an application in react js, there is a list of elements and when the user hover over the element the background color should change to red. At the moment it works. Also i want when i leave the element unhover, the element should be without red background, but now when i leave the element the color does not dissappear. Basically the hover should affect only that element where the user hover.
import "./styles.css";
import React, { memo, useEffect, useState } from "react";
const Element = ({ id }) => {
const styles = {
background: "red"
};
const [isHovered, setIsHovered] = useState(false);
return (
<div
className={isHovered ? "hovered" : ""}
onMouseOver={() => {
setIsHovered(true);
}}
>
hello {id}
</div>
);
};
export default function App() {
return (
<div className="App">
{[0, 1, 2].map((el) => {
return <Element key={el} id={el} />;
})}
</div>
);
}
demo: https://codesandbox.io/s/compassionate-einstein-9v1ws?file=/src/App.js:0-557 Question: Who can help to solve the issue?

Instead of onMouseOver, you can use onMouseEnter and onMouseLeave to apply the styles based on the isHovered state.
Here is your updated code.
import "./styles.css";
import React, { memo, useEffect, useState } from "react";
const Element = ({ id }) => {
// const styles = {
// background: "red"
// };
const [isHovered, setIsHovered] = useState(false);
console.log({ isHovered });
return (
<div
// style={{ backgroundColor: isHovered ? "red" : "transparent" }}
className={"hovered"}
// onMouseEnter={() => setIsHovered(true)}
// onMouseLeave={() => setIsHovered(false)}
>
hello {id}
</div>
);
};
export default function App() {
return (
<div className="App">
{[0, 1, 2].map((el) => {
return <Element key={el} id={el} />;
})}
</div>
);
}
You can also do the same thing using the className without using the state variable, with just targeting the hover class. I've used the both in the sandbox.
In case of doing only with className, you can just add a className to the div
className={"hovered"}
then, update the styles of the class in the css file.
.hovered:hover {
background: red;
}
Updated sandbox

Related

I want split one component in 2 components React JS

I'v a problem because I would split one component in two components.
I build my component with a search bar with autocomplete and the map with a pin chosen with the search bar. And me, I would one component with searcbar and an other with map who comunicate the date between them.
But I don't no how make this ..
Somebody could help me ?
Sorry for my Enlish, I'm French
Here is the code of my component:
import React, { useState, createContext } from 'react';
import {Map, Marker, GoogleApiWrapper} from 'google-maps-react';
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng,
} from 'react-places-autocomplete';
import Child2 from '../Child2';
const Location = createContext();
function Searchbar() {
const [address, setAdress] = useState("")
const [coordinates, setCoordinates] = useState({
lat: null,
lng: null
})
const handleSelect = (value, props) => {
const results = await geocodeByAddress(value);
const ll = await getLatLng(results[0])
console.log(ll)
setAdress(value)
setCoordinates(ll)
props.OnSelectPlace(ll)
}
return (
<div className="Searchbar">
<p>{coordinates.lat}</p>
<p>{coordinates.lng}</p>
<p>{address}</p>
<PlacesAutocomplete
value={address}
onChange={setAdress}
onSelect={handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'location-search-input',
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#E4E4E4', cursor: 'pointer' }
: { backgroundColor: '#F8F8F8', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
</div>
);
}
export {Location};
export default GoogleApiWrapper({
apiKey: ("secret_code_api_google_map")
})(Searchbar)
Build two separate components one for the map and other for the searchbar. Then pass date as a prop to the second component.

Change the state of one item in a map in react

I have a page that displays images fetched with an api call using the map method. Now what I want to do is to toggle the like or unlike state of FavoriteIcon by clicking it.
When I try to implement that using the useState hook, if I click on the FavoriteIcon in a single image, all the images are set to the same state. I want only the clicked image to change its state.
import React, { useState, useEffect } from "react";
import FavoriteIcon from "#material-ui/icons/Favorite";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
// services
import medias from "../../services/mediaServices";
function Favorites({ fetchUrl, catagory }) {
const classes = useStyles();
const [images, setImages] = useState([{}]);
const [favIcon, setFavIcon] = useState();
useEffect(() => {
async function getData() {
const request = await medias.getMedias(fetchUrl);
setImages(request.data.message);
}
getData();
}, [fetchUrl, enqueueSnackbar, closeSnackbar]);
return (
<div className={classes.favorites}>
<div className={classes.favorites__items}>
{images &&
images.map(image => {
return (
<div
elevation={3}
className={classes.favorites__posters}
style={
{
// border: "2px solid black"
}
}
>
<img
key={image.id}
src={image.thumbnailUrl}
alt={image.title}
/>
{favIcon ? (
<FavoriteIcon onClick={() => setFavIcon(false)} />
) : (
<FavoriteBorderIcon onClick={() => setFavIcon(true)} />
)}
</div>
);
})}
</div>
</div>
);
}
export default Favorites;
Without breaking it into more components, your favIcon state needs to contain the state of all your icons. It should be initialized with something like
images.map(image => {id: image.id, state: false})
and then you could change your onClick to just update the relevant part of state
setFavIcon([...favIcon].map(x => {if(x.id === image.id) {x.state = !x.state}; return x}))

Icon className doesn't update on state change

I am trying simplest ever theme toggle in react with context and don't seem to be able to get the icon re-render when context changes. Everything else works just fine: colors, background image... It renders either of icons depending on initial state, but icon doesn't update when theme is toggled.
import React, { useContext } from "react"
import { ThemeContext } from "../../contexts/ThemeContext"
const ThemeToggle = () => {
const { isDarkMode, dark, light, toggleTheme } = useContext(ThemeContext)
const theme = isDarkMode ? dark : light
return (
<li
style={{ background: theme.bgPrimary, color: theme.text }}
onClick={toggleTheme}
>
<i className={theme.ico} />
</li>
)
}
export default ThemeToggle
Context:
import React, { Component, createContext } from "react"
export const ThemeContext = createContext()
class ThemeContexProvider extends Component {
state = {
isDarkMode: false,
light: {
text: "#333",
bgPrimary: "#eee",
bgSecondary: "#333",
ico: "fas fa-moon"
},
dark: {
text: "#ddd",
bgPrimary: "#000003",
bgSecondary: "#bbb",
ico: "fas fa-sun"
}
}
toggleTheme = () => {
this.setState({ isDarkMode: !this.state.isDarkMode })
}
render() {
return (
<ThemeContext.Provider
value={{ ...this.state, toggleTheme: this.toggleTheme }}
>
{this.props.children}
</ThemeContext.Provider>
)
}
}
export default ThemeContexProvider
I fixed this installing dedicated react fa package, still don't know why above doesn't work. This works though:
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
import { faMoon } from "#fortawesome/free-solid-svg-icons"
import { faSun } from "#fortawesome/free-solid-svg-icons"
//.....
return (
<li
style={{ background: theme.bgPrimary, color: theme.text }}
onClick={toggleTheme}
>
{isDarkMode ? (
<FontAwesomeIcon icon={faSun} />
) : (
<FontAwesomeIcon icon={faMoon} />
)}
</li>
I suspect the spaces in your ico property could be the issue. Normally this isn't an issue for state/props. Context is possibly to blame for this. This might fix it:
<i className={`fas ${theme.ico}`} />
Replace your context ico properties with just the class that changes fa-moon and fa-sun
You could have used key props in the icon tag. Keys help React identify which items have changed. So it can rerender your icon.
{isDarkMode ? (
<i key={1} icon={faSun} />
) : (
<i key={2} icon={faMoon} />
)}

State managed in Context is showing undefined

I'm implementing a dark theme to understand React context. I successfully created a dark mode that saves mode to local storage and determined if the user prefers dark mode from local storage.
However now I want to refactor to keep the theme state in context.
I have moved the code in a theme context however I get the error stating
ERROR: TypeError: Cannot read property 'darkMode' of undefined
I can't seem to work it out. I was under the impression that I could pass the state in this case darkMode and setDarkMode into my App component using useContext?
import React, { useContext } from 'react';
import ThemeContextProvider, { ThemeContext } from './contexts/ThemeContext';
function App() {
const { darkMode, setDarkMode } = useContext(ThemeContext);
return (
<div className='App'>
<ThemeContextProvider>
<div className={darkMode ? 'dark-mode' : 'light-mode'}>
<nav>
<div className='toggle-container'>
<span style={{ color: darkMode ? 'grey' : 'yellow' }}>☀︎</span>
<span className='toggle'>
<input
checked={darkMode}
onChange={() => setDarkMode((prevMode) => !prevMode)}
type='checkbox'
className='checkbox'
id='checkbox'
/>
<label htmlFor='checkbox' />
</span>
<span style={{ color: darkMode ? '#9c27b0' : 'grey' }}>☽</span>
</div>
</nav>
<main>
<h1>{darkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<h2>Toggle the switch to change theme</h2>
</main>
</div>
</ThemeContextProvider>
</div>
);
}
and the ThemeContext
import React, { createContext, useState, useEffect } from 'react';
export const ThemeContext = createContext();
const ThemeContextProvider = (props) => {
const [darkMode, setDarkMode] = useState(getInitialMode);
useEffect(() => {
localStorage.setItem('dark', JSON.stringify(darkMode));
getPrefColourScheme();
}, [darkMode]);
function getInitialMode() {
const isReturningUser = 'dark' in localStorage;
const savedMode = JSON.parse(localStorage.getItem('dark'));
const userPrefersDark = getPrefColourScheme();
// if mode was saved -> dark / light
if (isReturningUser) {
return savedMode;
// if preferred colour scheme is dark -> dark
} else if (userPrefersDark) {
return true;
// otherwise -> light
} else {
return false;
}
}
function getPrefColourScheme() {
if (!window.matchMedia) return;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
return (
<ThemeContext.Provider value={{ darkMode, setDarkMode }}>
{props.children}
</ThemeContext.Provider>
);
};
export default ThemeContextProvider;
Forgive my ignorance i'm struggling to wrap my head around this problem.
Any help would be grateful thanks.
I think you can only use
const { darkMode, setDarkMode } = useContext(ThemeContext);
whenever some component above the one that uses this hook has a <Context.Provider>
However, you're using this hook inside your App component - it's not a child of your Provider.
What you can do is separate the children to a new component and use the hook there, or render your <App /> as a child of your <ThemeContextProvider> (which means moving your <ThemeContextProvider> to another place)
Option 1
const FooComp = () => {
const { darkMode, setDarkMode } = useContext(ThemeContext);
return (
<div className={darkMode ? 'dark-mode' : 'light-mode'}>
<nav>
<div className='toggle-container'>
<span style={{ color: darkMode ? 'grey' : 'yellow' }}>☀︎</span>
<span className='toggle'>
<input
checked={darkMode}
onChange={() => setDarkMode((prevMode) => !prevMode)}
type='checkbox'
className='checkbox'
id='checkbox'
/>
<label htmlFor='checkbox' />
</span>
<span style={{ color: darkMode ? '#9c27b0' : 'grey' }}>☽</span>
</div>
</nav>
<main>
<h1>{darkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<h2>Toggle the switch to change theme</h2>
</main>
</div>
)
}
then in App
function App() {
return (
<div className='App'>
<ThemeContextProvider><FooComp /></ThemeContextProvider>
)
}
or Option 2
in the place you're rendering App you do
<ThemeContextProvider><App /></ThemeContextProvider>
and you remove ThemeContextProvider from App
If you are using a class component and getting this issue
context is stored in a variable called context in a class component
import React, {Component, useContext} from 'react';
import {ThemeContext} from '../contexts/ThemeContext';
class Navbar extends Component {
static contextType = ThemeContext
render() {
console.log(this.context);
return (
<nav>
<h1>Content Apps</h1>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
);
}
}
export default Navbar;

My state changes, but does not add class when useEffect, when I scroll

I need to change the background of a JSX element when the page goes down by 320 px, all with useEffect and useState. So far I managed to change the state, but does not add background class of another color.
I am using NODE 8.9.3, NPM 5.5.1 and REACT JS 16.9.0
import React, { useEffect, useState } from 'react'
import { useScrollYPosition } from 'react-use-scroll-position'
import { Container } from '../../styles/Container'
import { ContainerCustom, HeaderComp } from './styles'
import Logo from './Logo'
import Menu from './Menu'
import Icons from './Icons'
const ContainerBox = () => {
return (
<ContainerCustom fluid>
<Container>
<HeaderComp>
<Logo />
<Menu />
<Icons />
</HeaderComp>
</Container>
</ContainerCustom>
)
}
const Header = () => {
const [back, setBack] = useState(0)
const handleBackState = () => {
const scrollY = window.scrollY
if (scrollY > 320) {
setBack(1)
console.log(`Estado: ${back}`)
} else {
setBack(0)
console.log(`Estado após remover: ${back}`)
}
}
useEffect(() => {
window.addEventListener('scroll', handleBackState)
return () => {
window.removeEventListener('scroll', handleBackState)
}
}, [handleBackState])
return <ContainerBox className={back === 1 ? 'removeGradients' : ''} />
}
On console has the output State: 0, and after 320, State after remove:
1
Not every component also has a representation in the DOM. You need to apply the className to a component that actually has a corresponding DOM element to have your styles take any effect:
// className will not affect the DOM as this component does not render a DOM element
const WrappingComponent = ({className}) => (
<WrappedComponent className={className} />
);
// this className will be applied to the div in the DOM
const WrappedComponent = ({className}) => (
<div className={className}>Content here</div>
);

Resources