Change state in function component react - css

I am new to React and being held back by a seemingly simple task.
I've got a Header component nested within which is a HamburgerButton component. Clicking the latter should make a sidenav appear but for now I would like the icon to change from the 'hamburger' to the big 'X'.
Here is my parent component:
import { MyMoviesLogo } from 'components/Icons';
import HamburgerButton from 'components/HamburgerButton/HamburgerButton';
import styles from './Header.module.css';
const Header = (): JSX.Element => {
const [isActive, setIsActive] = useState(false);
return (
<header className={styles.header}>
<MyMoviesLogo className={styles.headerIcon} />
<HamburgerButton
isActive={false}
/>
</header>
);
};
export default Header;
And here is the HamburgerButton
import styles from './HamburgerButton.module.css';
type HamburgerButtonProps = {
isActive: boolean;
onClick?: () => void;
};
const addMultipleClassNames = (classNames: string[]): string => classNames.join(' ');
const HamburgerButton = ({ isActive, onClick }: HamburgerButtonProps): JSX.Element => {
return (
<div className={isActive ? addMultipleClassNames([styles.hamburger, styles.active]) : styles.hamburger} onClick={onClick}>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
<div className={styles.bar}></div>
</div>
);
}
export default HamburgerButton;
Here's my HamburgerButton.module.css file:
.hamburger {
cursor: pointer;
display: block;
width: 25px;
}
.bar {
background-color: var(--hamburger-button-global);
display: block;
height: 3px;
margin: 5px auto;
transition: all 0.3s ease-in-out;
width: 25px;
}
.hamburger.active .bar:nth-child(2) {
opacity: 0;
}
.hamburger.active .bar:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.hamburger.active .bar:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
Manually changing the isActive prop to false verifies that the styling is applied as required.
My question is, how could I make it so when I click the icon its state gets toggled? I am familiar with React hooks like useState but can't quite put something together.
Any help would be greatly appreciated.
Thank you.
P.S.: It's probably obvious but I am using TypeScript.

You should use your onClick prop from your <HamburgerButton /> to change the parent state.
<HamburgerButton isActive={isActive} onClick={() => { setIsActive(oldState => !oldState) } />

Related

Radial animated focus effect with mask-image in React TS

I am recreating this Radial animated focus effect with mask-image: Codepen I know I can just copy&paste the CSS into a .css file but I want to achieve the same result with a styled component. For that, I declared the CSS in my styled component and apply it. But I am not sure why nothing happens at all and what should I use instead of getElementById as manual DOM manipulation is bad practice?
App.tsx
import React from "react";
import styled from "styled-components";
const Property = styled.div`
#property --focal-size {
syntax: "<length-percentage>";
initial-value: 100%;
inherits: false;
}
`;
const FocusZoom = styled.div`
--mouse-x: center;
--mouse-y: center;
--backdrop-color: hsl(200 50% 0% / 50%); /* can't be opaque */
--backdrop-blur-strength: 10px;
position: fixed;
touch-action: none;
inset: 0;
background-color: var(--backdrop-color);
backdrop-filter: blur(var(--backdrop-blur-strength));
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent var(--focal-size),
black 0%
);
transition: --focal-size .3s ease;
/* debug/grok the gradient mask image here */
/* background-image: radial-gradient(
circle,
transparent 100px,
black 0%
); */
}
`;
function App(bool: boolean) {
const zoom: Element = document.querySelector("focus-zoom");
const toggleSpotlight = (bool) =>
zoom.style.setProperty("--focal-size", bool ? "15vmax" : "100%");
window.addEventListener("pointermove", (e) => {
zoom.style.setProperty("--mouse-x", e.clientX + "px");
zoom.style.setProperty("--mouse-y", e.clientY + "px");
});
window.addEventListener("keydown", (e) => toggleSpotlight(e.altKey));
window.addEventListener("keyup", (e) => toggleSpotlight(e.altKey));
window.addEventListener("touchstart", (e) => toggleSpotlight(true));
window.addEventListener("touchend", (e) => toggleSpotlight(false));
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom></FocusZoom>
</>
);
}
export default App;
Check out solution with styled components
Code sandbox
import React, { useEffect } from "react";
import styled, { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
body {
display: flex;
align-items: center;
justify-content: center;
}
/* custom properties */
:root {
--focal-size: {
syntax: "<length-percentage>";
initial-value: 100%;
inherits: false;
}
--mouse-x: center;
--mouse-y: center;
--backdrop-color: hsl(200 50% 0% / 50%);
--backdrop-blur-strength: 10px;
}
`;
const Wrapper = styled.div`
height: 400px;
width: 400px;
background: conic-gradient(
from -0.5turn at bottom right,
deeppink,
cyan,
rebeccapurple
);
`;
const FocusZoom = styled.div`
position: fixed;
touch-action: none;
inset: 0;
background-color: var(--backdrop-color);
backdrop-filter: blur(var(--backdrop-blur-strength));
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent var(--focal-size),
black 0%
);
transition: --focal-size 0.3s ease;
`;
function App(bool) {
useEffect(() => {
const zoom = document.getElementById("zoomId");
const toggleSpotlight = (bool) =>
zoom.style.setProperty("--focal-size", bool ? "15vmax" : "100%");
window.addEventListener("pointermove", (e) => {
zoom.style.setProperty("--mouse-x", e.clientX + "px");
zoom.style.setProperty("--mouse-y", e.clientY + "px");
});
window.addEventListener("keydown", (e) => toggleSpotlight(e.altKey));
window.addEventListener("keyup", (e) => toggleSpotlight(e.altKey));
window.addEventListener("touchstart", (e) => toggleSpotlight(true));
window.addEventListener("touchend", (e) => toggleSpotlight(false));
toggleSpotlight();
}, []);
return (
<Wrapper>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom id="zoomId"></FocusZoom>
</Wrapper>
);
}
export default App;
Also, ensure you have global styles & component imported in app file.
import Test, { GlobalStyle } from "./test";
export default function App() {
return (
<div className="App">
<GlobalStyle />
<Test />
</div>
);
}
As mentioned by others, we can simply refer to a DOM element in the React component template by using a useRef hook:
function App() {
// Get an imperative reference to a DOM element
const zoomRef = useRef<HTMLDivElement>(null);
const toggleSpotlight = (bool: boolean) =>
// To get the DOM element, use the .current property of the ref
zoomRef.current?.style.setProperty(
"--focal-size",
bool ? "15vmax" : "100%"
);
// Etc. including event listeners
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom ref={zoomRef} /> {/* Pass the reference to the special ref prop */}
</>
);
}
Demo: https://codesandbox.io/s/exciting-flower-349b48?file=/src/App.tsx
A more intensive solution could leverage styled-components props adaptation to replace the calls to zoom.style.setProperty(), as described in Jumping Text in React with styled component
In particular, this can help replace the use of CSS variables.
Except for --focal-size unfortunately, which is configured with a transition.
const FocusZoom = styled.div<{
focalSize: string; // Specify the extra styling props for adaptation
pointerPos: { x: string; y: string };
}>`
--focal-size: ${(props) => props.focalSize};
position: fixed;
touch-action: none;
inset: 0;
background-color: hsl(200 50% 0% / 50%);
backdrop-filter: blur(10px);
mask-image: radial-gradient(
circle at ${(props) => props.pointerPos.x + " " + props.pointerPos.y},
transparent var(--focal-size),
black 0%
);
transition: --focal-size 0.3s ease;
`;
function App() {
// Store all dynamic values into state
const [focalSize, setFocalSize] = useState("100%");
const [pointerPosition, setPointerPosition] = useState({
x: "center",
y: "center"
});
const toggleSpotlight = (bool: boolean) =>
// Change the state instead of messing directly with the DOM element
setFocalSize(bool ? "15vmax" : "100%");
// Etc. including event listeners
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
{/* Pass the states to the styled component */}
<FocusZoom focalSize={focalSize} pointerPos={pointerPosition} />
</>
);
}
Demo: https://codesandbox.io/s/frosty-swirles-jdbcte?file=/src/App.tsx
This solution might be overkill for such case where the values change all the time (especially the mouse position), but it decouples the logic from the style implementation (the component does not know whether CSS variables are used or not).
Side note: for the event listeners, make sure to attach them only once (typically with a useEffect(cb, []) with an empty dependency array), and to remove them when the component is unmounted (typically by returning a clean up function from the useEffect callback).
You could also use useEvent from react-use for example, which hendles all that directly:
React sensor hook that subscribes a handler to events.
import { useEvent } from "react-use";
function App() {
// Attaches to window and takes care of removing on unmount
useEvent("pointermove", (e: PointerEvent) =>
setPointerPosition({ x: e.clientX + "px", y: e.clientY + "px" })
);
// Etc.
}
Instead of getElementById you should use the useRef hook

scrolling for overflow-x not working properly

I can scroll on the x axis only by moving the laptop touchpad right to left or by pressing in the scroll button and then moving right to left.Not with normal scroll.
the css is the following:
.row {
color: white;
margin-left: 20px;
}
.row__posters {
display: flex;
overflow-y: hidden;
overflow-x: scroll;
padding: 20px;
}
.row__posters::-webkit-scrollbar {
display: none;
}
.row__poster {
object-fit: contain;
width : 100%;
max-height: 100px;
margin-right: 10px;
transition: transform 450ms;
}
.row__poster:hover {
transform: scale(1.08);
}
.row__posterLarge {
max-height: 250px;
}
.row__posterLarge:hover {
transform: scale(1.09);
}
the Javascipt file is:
import React,{ useState , useEffect} from 'react'
import axios from './axios';
import './Row.css';
const base_url = "https://image.tmdb.org/t/p/original/";
function Row({ title ,fetchUrl,isLargeRow }) {
const [movies, setMovies] = useState([]);
// A snippet of code which runs based on a specific condition
useEffect(() => {
// if we leave the brackets blank [] ,run once when the row loads
and dont run again
async function fetchData() {
const request = await axios.get(fetchUrl);
setMovies(request.data.results);
return request;
}
fetchData();
}, [fetchUrl]);
return (
<div className="row">
<h2>{title}</h2>
<div className="row__posters">
{/* several row_posters */}
{movies.map(movie => (
<img
key={movie.id}
className={`row__poster ${isLargeRow && "row__posterLarge"}`}
src={`${base_url}${
isLargeRow ? movie.poster_path : movie.backdrop_path
}`}
alt={movie.name}
/>
))}
</div>
</div>
)
}
export default Row
I tried alot of solutions but I must be doing something wrong because nothing worked .it could be that I used the proposed code in the wrong department.
Thank you for the help in advance!!

How to make dropdown animation?

I am implementing drop-down list using styled-component in react. In the process, I have two questions.
First, when dropDownVisible changes from true to false, why doesn't the animation effect apply and it disappears immediately? How can I improve the animation effect? Like when this list goes down, I want to make it gradually when it goes up.
Second, when StyledDropdown is dropped down, I want it to drop down behind the StyledHead, so I set the z-index property like that. I want the StyledHead to be always on top, so I'm curious why the StyledHead is hidden as the StyledDropdown drops down, even though I gave the z-index property bigger.
The source code is roughly structured like this:
// AApage.jsx
import { useEffect, useState, useRef } from 'react';
import { MdArrowDropDown, MdArrowDropUp } from 'react-icons/md';
import styled, { keyframes } from 'styled-components';
const dropAnimation = keyframes`
0% {
transform : translateY(-300px);
display : none;
}
100% {
transform : translateY(0);
}
`;
const StyledHead = styled.div`
width: 100px;
height: 100px;
background-color: red;
z-index: 11;
`;
const StyledDropdown = styled.div`
width: 100px;
height: 300px;
background-color: #d9d9d9;
border-radius: 0px 0px 10px 10px;
z-index: 3;
animation: ${dropAnimation} 1s alternate;
`;
const AApage = () => {
const [dropDownVisible, setDropDownVisible] = useState<boolean>(false);
const toggleDropDownVisible = () => {
setDropDownVisible((prev) => !prev);
};
return (
<>
<StyledHead>
<div>Dropdown</div>
<span>{`${dropDownVisible}`}</span>
{dropDownVisible ? (
<MdArrowDropUp
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropUp>
) : (
<MdArrowDropDown
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropDown>
)}
</StyledHead>
{dropDownVisible ? (
<StyledDropdown>
<div>temp data</div>
<div>temp data</div>
<div>temp data</div>
</StyledDropdown>
) : (
<></>
)}
</>
);
};
export default AApage;

I have tried using styled component in my App. But the cursor moves out after I write city name

In the search area every time I type any letter the cursor moves out and I need to click the text area again to type the next letter. While using CSS it is working fine but in Styled components I am facing the issue. I think there is some issues with my styling but I am unable to debug. How can I fix this issue. Please help.
import React, { FC, useState, FormEvent } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { setAlert } from '../store/actions/alertActions';
import { getWeather, setLoading } from '../store/actions/weatherActions';
interface SearchProps {
title: string;
}
const Search: FC<SearchProps> = ({ title }) => {
const dispatch = useDispatch();
const [city, setCity] = useState('');
const changeHandler = (e: FormEvent<HTMLInputElement>) => {
setCity(e.currentTarget.value);
}
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if(city.trim() === '') {
return dispatch(setAlert('City is required!'));
}
dispatch(setLoading());
dispatch(getWeather(city));
setCity('');
}
const Header = styled.h1`
font-size: 40px;
font-family: 'sans-serif';
padding-top: 30px;
`;
const Input = styled.input`
font-size: 18px;
padding: 10px;
margin: 10px;
background: #b4e6df;
border: none;
border-radius: 3px;
::placeholder {
color: black;
}
`;
const Button = styled.button`
background-color: #10e2f1;
font-size: 20px;
color: white;
margin: 1em;
border: 3px;
padding: 0.25em 6em;
`;
return(
<>
<Header >{title}
<form onSubmit={submitHandler}>
<Input
type="text"
placeholder="Enter city name"
value={city}
onChange={changeHandler}
/>
<br/>
<Button >Search</Button>
</form>
</Header>
</>
);
}
export default Search;
Looks like the value is changing (or refreshing) whenever you make a change in the input as it is bind with state and also the state is updating on onChange event. Keep independent value instead of state variable in value attribute. something like this:
const defaultValue = ''; // or the default value you are getting from props
const [city, setCity] = useState(defaultValue);
---
---
<Input
type="text"
placeholder="Enter city name"
value={defaultValue}
onChange={changeHandler}
/>
Let me know if it works or not.

Targeting a styled component in React with a webpack generated identifier?

I have a component called LargeDialog which encapsulates a StyledDialogContent (both of which are from the Dialog class of the Material UI library).
LargeDialog.jsx
...
const StyledDialogContent = styled(DialogContent)`
padding: 30px;
`;
class LargeDialog extends Component {
...
render(){
return (<StyledDialogContent> ... </StyledDialogContent>) // Some content within.
}
}
...
The styled components adds a padding: 30px to the DialogContent.
I would like to override this with padding: 0px if the LargeDialog modal is reused in another place.
However, the generated webpack CSS has a random identifier i.e. MuiDialogContentroot-0-3-439 FullDialogModal__StyledDialogContent-ogd6um-6 iMpISc and I'm not sure how to target this.
AnotherComponent.jsx
import LargeDialog from './LargeDialog'
...
const LargeDialogWrapper = styled(LargeDialog)`
// What do I put here to override StyledDialogContent with a random identifier?
`;
class AnotherComponent extends Component {
}
...
I tried exporting StyledDialogContent and targetting it as such:
import LargeDialog, {StyledDialogContent} from './LargeDialog'
...
const LargeDialogWrapper = styled(LargeDialog)`
${StyledDialogContent} {
padding: 0px;
}
`;
But that didn't work too.
Example:
https://codesandbox.io/embed/styled-components-d5pzv?fontsize=14&hidenavigation=1&theme=dark
You target it within the style like so:
const Box = styled.div`
background-color: black;
height: 100px;
`;
const Yellow = styled.div`
background-color: blue;
height: 200px;
${Box} {
background-color: yellow;
}
`;
const App = () => {
return (
<>
<Box />
<Yellow>
<Box />
</Yellow>
</>
);
};
Refer to the related docs section.
If it helps, you can check this example file (note the Heading style for example).
An edit after OP question update
In your example, you missing className if you want to enable styling for your components.
Also, you need WrapperDiv to be a direct child, this is how the CSS works, remember that you writing simple CSS just in javascript:
class LargeDialog extends Component {
render() {
return (
<WrapperDiv className={this.props.className}>
<div>{this.props.children}</div>
</WrapperDiv>
);
}
}
const WrapperLargeDialog = styled(LargeDialog)`
${WrapperDiv} {
background-color: blue;
}
`;
// LargeDialog should be red.
// WrapperLargeDialog should be blue.
class App extends Component {
render() {
return (
<div>
<LargeDialog />
<br />
<WrapperLargeDialog>
<WrapperDiv />
</WrapperLargeDialog>
</div>
);
}
}

Resources