React useState() & useEffect: DOM element changed but not rendering - reactjs

I'm trying to achieve in react: click the heart to like/unlike a move, using useState() & useEffect(), but seems the element is applied with new class name, but the color didn't change when i clicked.
export function MovieCard2(props) {
const [heartClassName, setHeartClassName] = useState("heart heart-white")
useEffect(() => {
console.log(ref.current)
if(isLiked === false) {
setHeartClassName("heart heart-white")
} else {
setHeartClassName("heart heart-red")
}
}, [isLiked]);
return (
<>
<div
className={heartClassName}
onClick={function (ev) {
ev.stopPropagation();
setLiked(!isLiked)
>
❤
</div>
<style jsx>{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}</style>
</>
);
}

try this one:
const [heartClassName, setHeartClassName] = useState("heart heart-white");
const [isLiked,setLiked]=useState(false);
useEffect(() => {
if (isLiked === false) {
setHeartClassName("heart heart-white");
} else {
setHeartClassName("heart heart-red");
}
}, [isLiked]);
you HTML
<style jsx>{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}</style>
<>
<div
className={heartClassName}
onClick={function (ev) {
ev.stopPropagation();
setLiked(!isLiked);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
width="1em"
height="1em"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 1024 1024"
style={{ height: '10px', width:'10px' }}
>
<path
fill="currentColor"
d="M923 283.6a260.04 260.04 0 0 0-56.9-82.8a264.4 264.4 0 0 0-84-55.5A265.34 265.34 0 0 0 679.7 125c-49.3 0-97.4 13.5-139.2 39c-10 6.1-19.5 12.8-28.5 20.1c-9-7.3-18.5-14-28.5-20.1c-41.8-25.5-89.9-39-139.2-39c-35.5 0-69.9 6.8-102.4 20.3c-31.4 13-59.7 31.7-84 55.5a258.44 258.44 0 0 0-56.9 82.8c-13.9 32.3-21 66.6-21 101.9c0 33.3 6.8 68 20.3 103.3c11.3 29.5 27.5 60.1 48.2 91c32.8 48.9 77.9 99.9 133.9 151.6c92.8 85.7 184.7 144.9 188.6 147.3l23.7 15.2c10.5 6.7 24 6.7 34.5 0l23.7-15.2c3.9-2.5 95.7-61.6 188.6-147.3c56-51.7 101.1-102.7 133.9-151.6c20.7-30.9 37-61.5 48.2-91c13.5-35.3 20.3-70 20.3-103.3c.1-35.3-7-69.6-20.9-101.9z"
/>
</svg>
</div>
</>

From what i see on your browser you are using Next.js which automatically updates and renders out when a variable changes, and you are also not initializing the variable isLiked
This should fix your issue.
import {useState} from "react";
export function MovieCard2(props) {
const [heartClassName, setHeartClassName] = useState("heart heart-white");
const [isLiked, setIsLiked] = useState(false);
function handleLike() {
setIsLiked(!isLiked);
if (isLiked) {
setHeartClassName("heart heart-red");
} else {
setHeartClassName("heart heart-white");
}
}
return (
<>
<div className={heartClassName} onClick={handleLike}>
❤
</div>
<style jsx>
{`
.heart {
padding: 16px 0 auto auto;
}
.heart-white {
color: white;
}
.heart-red {
color: red;
}
`}
</style>
</>
);
}

Related

How can I change two different buttons' text if I switch between two language buttons?

My main problem is that when I switch from "ENG" to "HUN" button, the "Home" and the "New Game" text button don't transform to the Hungarian equivalent.
{ LangButton } component switches "ENG" to "HUN". When the "HUN" button active, should transform any innerText. Those innerText changes are stored in { languages } object, what is also a different js file.
Buttons
Current codes are here, LangButton used as a Child component in Header.js file;
LangButton.js file:
import React from 'react';
import ".././Header_module.css";
export const LangButton = ({ hunLang, setHunLang, num, text, onClick }) => (
<button
onClick={() => setHunLang(num)}
className="button"
style={{
backgroundColor: hunLang === num ? 'white' : 'black',
color: hunLang === num ? 'red' : 'rgb(112, 255, 0)',
borderRadius: num === 1 ? '5px 0 0 5px' : '0 5px 5px 0',
marginRight: num === 2 ? '10px' : '0px',
onClick: {onClick}
}}
>
{text}
</button>
);
Header.js file:
import "./Header_module.css";
import React, { useState } from "react";
import { LangButton } from "./Language/LangButton";
import { languages } from ".././languages";
const Header = (props) => {
const [hunLang, setHunLang] = useState(1);
const [homeButton, setHomeButton] = useState(languages.en.home_btn);
const [newGameButton, setNewGameButton] = useState(languages.en.new_game_btn);
const engButtonClick = () => {
setHomeButton(languages.en.home_btn);
setNewGameButton(languages.en.new_game_btn);
};
const hunButtonClick = () => {
setHomeButton(languages.hu.home_btn);
setNewGameButton(languages.hu.new_game_btn);
};
return (
<div className="header-bg">
<div className="btn-container">
<button className="button">{homeButton}</button>
<button className="button">{newGameButton}</button>
<LangButton
hunLang={hunLang}
setHunLang={setHunLang}
num={1}
text="ENG"
onClick={engButtonClick}
/>
<LangButton
hunLang={hunLang}
setHunLang={setHunLang}
num={2}
text="HUN"
onClick={hunButtonClick}
/>
</div>
</div>
);
};
export default Header;
You have added two onClick handlers including one inside a style block of LangButton component.
It works after fixing it:
const languages = {
en: {
home_btn: "Home",
new_game_btn: "Game"
},
hu: {
home_btn: "Home hu",
new_game_btn: "Game hu"
}
};
const LangButton = ({ hunLang, num, text, onClick }) => (
<button
onClick={onClick}
className="button"
style={{
backgroundColor: hunLang === num ? "white" : "black",
color: hunLang === num ? "red" : "rgb(112, 255, 0)",
borderRadius: num === 1 ? "5px 0 0 5px" : "0 5px 5px 0",
marginRight: num === 2 ? "10px" : "0px"
}}
>
{text}
</button>
);
const Header = (props) => {
const [hunLang, setHunLang] = React.useState(1);
const [homeButton, setHomeButton] = React.useState(languages.en.home_btn);
const [newGameButton, setNewGameButton] = React.useState(
languages.en.new_game_btn
);
const engButtonClick = () => {
setHunLang(1);
setHomeButton(languages.en.home_btn);
setNewGameButton(languages.en.new_game_btn);
};
const hunButtonClick = () => {
setHunLang(2);
setHomeButton(languages.hu.home_btn);
setNewGameButton(languages.hu.new_game_btn);
};
return (
<div className="header-bg">
<div className="btn-container">
<button className="button">{homeButton}</button>
<button className="button">{newGameButton}</button>
<LangButton
hunLang={hunLang}
setHunLang={setHunLang}
num={1}
text="ENG"
onClick={engButtonClick}
/>
<LangButton
hunLang={hunLang}
setHunLang={setHunLang}
num={2}
text="HUN"
onClick={hunButtonClick}
/>
</div>
</div>
);
};
ReactDOM.render(<Header />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>

React Transition Group Dynamically Change Slide Direction

I'm creating a mobile navigation menu and using CSSTransition component from React Transition Group to handle animating in the different levels of my navigation.
I am able to successfully animate in different levels but only in one direction. For instance, the content will enter in from the right and exit to the left. The part that is confusing to me is when i need to change the direction that the content animates in or out.
For my example lets say I have 3 slides
Initially slide #2 would enter in from the right If we go to slide #3 it would exit left.
If we are on slide #3 and want to go back to slide #2 then I want slide #2 to enter in from the right.
import { useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './App.css';
const App = () => {
const [page, setPage] = useState(1);
return (
<>
Page: {page}
<nav>
<button onClick={() => page !== 1 && setPage(page - 1)}>Prev</button>
<button onClick={() => page >= 1 && page <= 2 && setPage(page + 1)}>
Next
</button>
</nav>
<div className='content'>
<CSSTransition
in={page === 1}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 0.5s;
}
.slide-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 0.5s;
}
import { useState } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import "./App.css";
const App = () => {
const [page, setPage] = useState(1);
const [direction, setDirection] = useState("left");
return (
<>
Page: {page}
<nav>
<button
onClick={() => {
page !== 1 && setPage(page - 1);
direction !== "right" && setDirection("right");
}}
>
{" "}
Prev
</button>
<button
onClick={() => {
page >= 1 && page <= 2 && setPage(page + 1);
direction !== "left" && setDirection("left");
}}
>
Next
</button>
</nav>
<div className="content">
{/* <TransitionGroup> */}
<CSSTransition
in={page === 1}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
{/* </TransitionGroup> */}
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-left-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-left-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-left-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-left-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 2000ms;
}
.slide-right-enter {
opacity: 0;
transform: translateX(-100%);
}
.slide-right-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-right-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-right-exit-active {
opacity: 0;
transform: translateX(100%);
transition: all 2000ms;
}
the trick is to reverse the translation on every key button

How do I make the slideshow buttons scroll through the images?

I have been working on a project and while trying to create a slideshow I found somewhere, wanted to edit it so that instead of using the setTimeout to scroll through the images or the circles, I want to be able to use the buttons to go left and right but just cant seem to figure it out.
Here is the link to what it looks like:
https://codesandbox.io/s/throbbing-bash-ir9g0?fontsize=14&hidenavigation=1&theme=dark
Anyone able to point me in the right direction?
In order to achieve what you are looking for, we just need to add click handlers for left and right buttons.
Below are the respective functions in order to display the next/previous slide based on the current index.
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
Adding these functions to slideshow.js & updating the buttons as shown will help you in achieving the expected functionality.
Here is the demo
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
const delay = 5000;
function Slideshow() {
const [index, setIndex] = React.useState(0);
const timeoutRef = React.useRef(null);
function resetTimeout() {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}
React.useEffect(() => {
resetTimeout();
timeoutRef.current = setTimeout(
() =>
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
),
delay
);
return () => {
resetTimeout();
};
}, [index]);
//Handler to update the current index on click of right button
const onNextClick = () => {
if(index !== colors.length - 1) {
setIndex(idx => idx + 1);
}
}
//Handler to update the current index on click of left button
const onPreviousClick = () => {
if(index !== 0) {
setIndex(idx => idx - 1);
}
}
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={onPreviousClick}>‹</button>
<button className="right" onClick={onNextClick}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
ReactDOM.render(<Slideshow />, document.getElementById("react"));
.slideshow {
margin: 0 auto;
overflow: hidden;
width: 30%;
position: relative;
}
.slideshow-slider {
white-space: nowrap;transition: ease 1000ms;
}
.slide {
display: inline-block;
height: 240px;
width: 100%;
}
button {
width: 25px;
height: 240px;
position: absolute;
cursor: pointer;
}
.left {
left: 0;
top: 0;
}
.right {
right: 0;
top: 0;
}
.slideshow-tabs {
text-align: center;
position: relative;
bottom: 50px;
}
.slideshow-tab {
display: inline-block;
height: 20px;
width: 20px;
border-radius: 50%;
cursor: pointer;
margin: 15px 7px 0px;
background-color: #c4c4c4;
}
.active {
background-color: #6a0dad;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
-- Update --
As per you requirement, if you wish to go back to the beginning at the end of all the slides, you can make the changes to onNextClick like below
const onNextClick = () => {
setIndex(idx => idx !== colors.length - 1? idx + 1: 0);
}
you can do that by handling the onClick event
here's the link of the code you could check this demo
import React from "react";
import "./slideshow.scss";
const colors = ["#0088fE", "#00C49F", "#FFBB28"];
function Slideshow() {
const [index, setIndex] = React.useState(0);
const handleChange=()=>{
setIndex((prevIndex) =>
prevIndex === colors.length - 1 ? 0 : prevIndex + 1
)
}
React.useEffect(() => {
}, [index]);
return (
<div className="slideshow">
<div
className="slideshow-slider"
style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
>
{colors.map((backgroundColor, index) => (
<div className="slide" key={index} style={{ backgroundColor }} />
))}
</div>
<button className="left" onClick={()=>handleChange} >‹</button>
<button className="right" onClick={()=>handleChange}>›</button>
<div className="slideshow-tabs">
{colors.map((_, idx) => (
<div
key={idx}
className={`slideshow-tab${index === idx ? " active" : ""}`}
onClick={() => {
setIndex(idx);
}}
></div>
))}
</div>
</div>
);
}
export default Slideshow;

CSSTransition from react-transition-group not applying classes

I'm trying to integrate CSSTransition to my Gatsby site, but it is not applying any of the classes. I'm utilizing CSS modules, and I've got a <div> that serves as the parent that fades in and out, essentially applying the fade effect to this and covering the content while it changes. It's got the class fadEffect. Here is my app-layout component, and the SASS.
AppLayout.tsx
import React, { ReactNode, useState } from 'react';
import { ApiContext } from 'contexts/ApiContext';
import { graphql, StaticQuery } from 'gatsby';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { Devtools } from '../devtools/Devtools';
import { Footer } from '../footer/Footer';
import { Header } from '../header/Header';
import s from './AppLayout.scss';
interface AppLayoutProps {
children: ReactNode;
location: string;
}
const isDev = process.env.NODE_ENV === 'development';
// tslint:disable no-default-export
export default ({ children, location }: AppLayoutProps) => {
const [fadeEffectVisible, setFadeEffectVisible] = useState(false);
const handleFadeEffectEntered = () => {
setTimeout(() => {
setFadeEffectVisible(false);
}, 50);
};
return (
<StaticQuery
query={`${NavQuery}`}
render={(data) => (
<>
<ApiContext>
<Header navigationContent={data.prismic.allNavigations.edges[0].node} />
<CSSTransition
in={fadeEffectVisible}
timeout={150}
classNames={{
enter: s.fadeEffectEnter,
enterActive: s.fadeEffectEnterActive,
enterDone: s.fadeEffectEnterDone,
exit: s.fadeEffectExit,
exitActive: s.fadeEffectExitActive,
}}
onEntered={handleFadeEffectEntered}
>
<div className={s.fadeEffect} aria-hidden="true" />
</CSSTransition>
<TransitionGroup component={null}>
<CSSTransition
key={location}
timeout={150}
classNames={{
enter: s.pageEnter,
}}
>
<div className={s.layout}>
{children}
<Footer navigationItems={data.prismic.allNavigations.edges[0].node} />
{isDev && <Devtools />}
</div>
</CSSTransition>
</TransitionGroup>
</ApiContext>
</>
)}
/>
);
};
const NavQuery = graphql`
query NavQuery {
prismic {
allNavigations {
edges {
node {
...NotificationBar
...NavigationItems
...FooterNavigationItems
}
}
}
}
}
`;
AppLayout.scss
#import '~styles/config';
:global {
#import '~styles/base';
}
.layout {
display: block;
min-height: 100vh;
}
.pageEnter {
display: none;
}
.fadeEffect {
display: none;
position: fixed;
z-index: 9;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
transition: opacity 0.15s linear;
&Enter {
display: block;
opacity: 0;
}
&Active,
&Done,
&Exit {
display: block;
opacity: 1;
}
&ExitActive {
opacity: 0;
}
}
I'm happy to provide more details/code if this isn't enough. I'm newish to React and Gatsby, so I'm still learning the lingo. Thanks in advance.
I don't see part of your code where you are updating fadeEffectVisible to true for first CSSTransition and I don't see in property at all on second CSSTransition and I would bet that is your issue. Please take a look at this example from React Transition Group for understanding usage of properties.
App.js
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<CSSTransition in={inProp} timeout={200} classNames="my-node">
<div>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
);
}
Style.css
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 200ms;
}
When the in prop is set to true, the child component will first receive the class example-enter, then the example-enter-active will be added in the next tick.

React-select, open sub-menu when hover over an option

I'm trying to build a submenu inside a main menu with React-select, it should be something like this:
When hovering over an option from the main menu, it triggers the submenu to open at the side.
Is there a way to do this using react-select? I couldn't find any example or documentation on this, is there a function like ```optionOnMouseover`` for this? Thank you in advance!
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
...
<Select
value={...}
onChange={...}
options={options}
/>```
This is on click, but if you need on hover,
just modify it
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select, { components } from "react-select"
const CustomOption = (props) => {
const [submenu, setSubmenu] = useState(false)
const [height, setHeight] = useState(0)
const handleOption = (e) => {
if(submenu) {
setSubmenu(false)
} else {
setHeight(e.clientY)
setSubmenu(true)
}
}
const handleSubOption = (e) => {
console.log('clicked')
}
const { data } = props;
return data.custom ? (
<>
<div onClick={handleOption} className="customs">
{data.label} <span className="caret"/>
{
submenu && (
<div className="dropdown-submenu">
<div className="drops" onClick={handleSubOption}>
Test dropdown 1
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 2
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 3
</div>
</div>
)
}
</div>
<style jsx>{`
.customs {
height: 36px;
padding: 8px;
position: relative;
}
.drops {
height: 36px;
padding: 8px;
}
.customs:hover, .drops:hover {
background-color: #17cf76;
}
.dropdown-submenu {
position: fixed;
top: ${height - 10}px;
left: 410px;
min-height: 36px;
overflow: auto;
border: 1px solid hsl(0,0%,80%);
border-radius: 4px;
color: #212529;
}
`}</style>
</>
) : (
<components.Option {...props} />
);
};
const options = [
{ custom: true, label: "I'm a custom link", value: "cust" }
];
function App() {
return (
<>
<Select classNamePrefix="category-select" className="w-30" components={{ Option: CustomOption }} options={options} />
<style jsx global>{`
* {
font-family: sans-serif;
text-align: center;
}
.w-30 {
width: 30% !important;
}
`}</style>
</>
)
}
export default App

Resources