I'm attempting to use useState to alter the display type in my styled components. When attempting to use my code the display type is not altered and my variable "displayType" is undefined.
I've attempted altering what my setStyle() function returns, but I am starting to see this is a larger problem that I'd like to understand better.
When I print the value to the console in index.js everything works fine. However I just get undefined when I try to use displayType in StoreElements.js
src/pages/store.js
const [displayType, setDisplayType] = useState("none");
const setStyle = (displayType) => {
setDisplayType(displayType);
console.log(displayType)
};
const [isOpen, setIsOpen] = useState(false)
const toggle = () => {
setIsOpen(!isOpen)
}
return (
<div>
<Sidebar isOpen={isOpen} toggle={toggle} />
<Navbar toggle={toggle} />
<Store setStyle={setStyle} displayType={displayType}></Store>
<Footer />
</div>
)
}
export default StorePage
src/store/index.js
const Store = ({displayType, setStyle}) => {
return (
<>
<AboutBg style={{ backgroundImage: `url(${BgPic})` }}></AboutBg>
<StoreContainer>
<StoreWrapper>
<Title>Store</Title>
<ItemsContainer>
<ItemWrapper
onMouseEnter={() => setStyle("hoodie")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/about">
<MerchImg src={frontHoodie}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>Hoodie text</MerchText>
<HoodiePriceText>price</HoodiePriceText>
</TextWrapper>
</ItemWrapper>
<ItemWrapper
onMouseEnter={() => setStyle("sweats")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/tournaments">
<MerchImg src={frontSweats}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>Sweats text</MerchText>
<SweatsPriceText displayType={displayType}>
price
</SweatsPriceText>
</TextWrapper>
</ItemWrapper>
<ItemWrapper
onMouseEnter={() => setStyle("shirt")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/">
<MerchImg src={frontShirt}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>Shirt text</MerchText>
<ShirtPriceText>price</ShirtPriceText>
</TextWrapper>
</ItemWrapper>
<ItemWrapper
onMouseEnter={() => setStyle("mousepad")}
onMouseLeave={() => setStyle("none")}
>
<ImgWrapper>
<ImgLink to="/">
<MerchImg src={mousepadFront}></MerchImg>
</ImgLink>
</ImgWrapper>
<TextWrapper>
<MerchText>mouspad text</MerchText>
<MousepadPriceText>price</MousepadPriceText>
</TextWrapper>
</ItemWrapper>
</ItemsContainer>
</StoreWrapper>
</StoreContainer>
<div>
{listItems}
{cartItems}
Total: ${cartTotal}
{cartItems.length}
</div>
</>
);
};
export default Store;
src/store/StoreElements.js
export const HoodiePriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
display: ${({ displayType }) => {
if (displayType === "hoodie") {
console.log("working");
return "block";
} else {
console.log({displayType})
return "none";
}
}};
`;
export const ShirtPriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
`;
export const MousepadPriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
`;
export const SweatsPriceText = styled.h4`
color: red;
position: absolute;
top: 365px;
transition: 0.8s all ease;
`;
In your styled component usage, you should bind the property displayType:
<HoodiePriceText displayType={displayType}>price</HoodiePriceText>
Thus, you should able get displayType in styled component!
setDisplayType is triggering the state change and causes a re-render of the function. It does not modify the value of the variable displayType. The value displayType is still undefined directly after calling setDisplayType, because it only gets its value after the function re-runs the useState-line.
const [displayType, setDisplayType] = useState("none");
// displayType can only get a new value here
Related
I want to make a reusable modal component with transitions, but CSSTransition doesn't work, i have been trying for many ways but nothing works. Maybe is createPortal, or useContext.
I am interested in creating a single modal component for several pages, and only place the transition once so that it is reusable
Route
const App = () => {
const initialState = useInicialState();
return (
<AppContext.Provider value={initialState}>
<AdminProvider>
<Routes>
<Route exact path="/" element={<Login />} />
<Route element={<Layout />}>
<Route exact path="/dashboard" element={<Dashboard />} />
<Route exact path="/areas" element={<Areas />} />
<Route path="*" element={<NotFound/>} />
</Route>
</Routes>
</AdminProvider>
</AppContext.Provider>
);
};
export default App;
Hook:
const useInicialState= ()=>{
const [openModal, setOpenModal] = useState(false);
return{
openModal,
setOpenModal
}
}
export default useInicialState;
Context:
import React from "react";
const AppContext = React.createContext({});
export default AppContext;
Modal Component:
const Modal = ({children}) => {
const {openModal, setOpenModal}= useContext(AppContext)
const nodeRef = useRef(null);
console.log(openModal)
const handleClose = ()=>{
setOpenModal(false)
}
return (
ReactDOM.createPortal(
<div className="modal-background">
<CSSTransition
in={openModal}
timeout={500}
classNames="modal"
unmountOnExit
nodeRef={nodeRef}
>
<div className="modal" ref={nodeRef}>
<button onClick={handleClose} className="button-close">
x
</button>
{children}
</div>
</CSSTransition>
</div>,
document.getElementById('modal')
)
);
}
export default Modal
Page:
const Areas = () => {
const [token] = useContext(AdminContext);
const {openModal, setOpenModal} = useContext(AppContext);
const [sectors, setSectors] = useState([]);
const getSectors = async ()=>{
const requestOptions = {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
};
const response = await fetch("/api/sector/list", requestOptions);
if(!response.ok){
}else{
const data = await response.json();
setSectors(data);
}
};
useEffect(() => {
getSectors();
}, [])
const handleModal= ()=>{
setOpenModal(!openModal)
}
return (
<>
{openModal && (
<Modal>
<p>esto es una prueba</p>
</Modal>
)}
<button className="button-create" onClick={handleModal}>Crear área</button>
<table className="styled-table">
<thead>
<tr>
<th>Nombre</th>
<th>Descripción</th>
<th>Modificar/borrar</th>
<th>Administrar personal</th>
<th>Administrar aspectos</th>
</tr>
</thead>
<tbody>
{sectors.map((sector)=>(
<tr key={sector.sector_name}>
<td>{sector.sector_name}</td>
<td>{sector.sector_description}</td>
<td>
<img src={edit} alt="" />
<img src={garbage} alt="" />
</td>
<td>
<img src={personal} alt="" />
</td>
<td>
<img src={drop} alt="" />
</td>
</tr>
))}
</tbody>
</table>
</>
)
}
export default Areas;
and last Css:
.modal-background{
display:block;
position: fixed;
z-index: 1;
padding-top:100px;
left: 0;
top: 0;
width:100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba($color: #000000, $alpha: 0.5);
}
.button-close{
position: absolute;
left:200px;
}
.modal{
position:relative;
background-color:white;
margin:auto;
margin-left:267px;
padding:0;
border:none;
border-radius:10px;
}
.modal-enter{
opacity:0;
transform:scale(0);
}
.modal-enter-active{
opacity:1;
transform: scale(1);
transition: opacity 500ms, transform 500ms;
}
.modal-exit{
opacity: 1;
}
.modal-exit-active{
opacity:0;
transform: scale(0);
transition: opacity 500ms, transform 500ms;
}
Find myself the solution, thge problem was by the conditional for CSStransition.
{openModal && (
<Modal>
<p>esto es una prueba</p>
</Modal>
)}
with out this condition work fine:
<Modal>
<p>esto es una prueba</p>
</Modal>
I create a function when I click outside of the sidebar it will hide it and I also have a button that toggles show and hide the sidebar. But when I combined both of them together, the button did not work properly, it only show the sidebar but can't close it, only when I click outside it will close the sidebar
Click OutSide to close function:
const ref = useRef(null);
useEffect(() => {
document.addEventListener("mousedown", Clickout);
return () => {
document.removeEventListener("mousedown", Clickout);
};
}, []);
const Clickout = (eve) => {
if (ref.current && !ref.current.contains(eve.target)) {
setShow(false);
}
};
My Return:
return (
<header>
<div className="head">
<div className="logo">
<img src={logo} alt="logo" />
</div>
<button
className="burger"
onClick={() => {
setShow(!showMenu);
console.log("here");
}}
>
<div className={`${showMenu ? "change" : ""} bur1 `}></div>
<div className={`${showMenu ? "change" : ""} bur2 `}></div>
<div className={`${showMenu ? "change" : ""} bur3 `}></div>
</button>
</div>
<nav className={showMenu ? "active" : ""} ref={ref}>
<ul>
{navItem.map((item) => {
const { id, url, text } = item;
return (
<li key={id}>
<a href={url}>{text}</a>
</li>
);
})}
</ul>
</nav>
</header>
);
};
Nav bar CSS:
nav {
position: fixed;
right: -100%;
top: 0;
width: 60%;
height: 100vh;
text-align: center;
padding-top: 15vh;
transition: 0.8s ease;
background-color: blue;
}
nav.active {
right: 0;
transition: 0.5s;
}
Thank you.
you can use another state for manage button onclick when menu is open:
const [disableBtn, setDisableBtn] = useState(false);
and in Clickout function manage it:
const Clickout = (eve) => {
if (showMenu && ref.current && !ref.current.contains(eve.target)) {
setShow(false);
setDisableBtn(true)
} else {
setDisableBtn(false)
}
};
and in button for onclick use condition:
if (!disableBtn) setShow(true);
Updating state this way setShow(!showMenu) does not immediately update the state.Rather it schedules the update(You can read the docs). When your setState depends on your previous state (in this case showMenu depends on previous state) use this technique: (prev) => setState(!prev) instead. So, simply updating your onClick will solve the issue.
<button className="burger"
onClick={() => {
(prevShowMenu) => setShow(!prevShowMenu)
}}>
(Let me know in the comments if this was helpful)
I am trying to create a react component that represents a tile.
This component is just a div that's composed of a label and a checkbox.
The problem that I have is that I can click wherever on the component and the states changes like it would normally do (eg: by clicking on the component i can check or uncheck the checkbox). but when I click on the checkbox nothing happens.
Here is my newly created component code:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(()=>{
console.log(selected)
},[selected])
return (
<>
<div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
<label>{title}</label>
<input
type="checkbox"
checked={!!selected}
onChange={ev=>{setSelected(curr=>!curr)}}
></input>
</div>
</>
);
};
and here I use it in my App.js :
return (
<Container>
<Row>
<Col md={4}>
<Tile title="USA"></Tile>
<Tile title="MOROCCO"></Tile>
<Tile title="FRANCE"></Tile>
</Col>
<Col md={8}>
<h1>Hello</h1>
</Col>
</Row>
</Container>
and finally here is my css :
body {
padding-top: 20px;
font-family: "Poppins", sans-serif;
background-color: cornsilk;
}
.tile {
position: relative;
display: block;
width: 100%;
min-height: fit-content;
background: bisque;
padding: 8px;
margin: 1px;
}
.tile input[type="checkbox"] {
position: absolute;
top: 50%;
right: 0%;
transform: translate(-50%, -50%);
}
EDIT: the problem with using the htmlFor fix on the label is that the label is clickable and the checkbox is clickable but the space between them is not. I want the the whole component to be clickable
You don't need the onClick on your div.
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected);
}, [selected]);
return (
<>
<div className="tile" onClick={() => setSelected((curr) => !curr)}>
<label htmlFor={title}>{title}</label>
<input
id={title}
type="checkbox"
checked={!!selected}
onChange={(ev) => {}}
/>
</div>
</>
);
};
I made a code sandbox to test: https://codesandbox.io/s/optimistic-tharp-czlgp?file=/src/App.js:124-601
When you click on the checkbox, your click event is propagated and handled by both the div and the checkbox inside the div, which results in state being toggled twice and ultimately having the same value as before.
You need to remove one of the onClicks, depending on what you want to be clickable (either the whole div or just the checkbox with the label).
Clickable div:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected)
}, [selected])
return (
<>
<div className="tile" onClick={() => setSelected(curr => !curr)}>
<label>{title}</label>
<input
type="checkbox"
checked={!!selected}
/>
</div>
</>
);
};
Clickable checkbox and label:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected)
}, [selected])
return (
<>
<div className="tile">
<label htmlFor="title">{title}</label>
<input
id="title"
type="checkbox"
checked={!!selected}
onChange={() => setSelected(curr => !curr)}
/>
</div>
</>
);
};
Add htmlFor prop to the label and add id to the input matching that htmlFor value.
In your case Tile component would be:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(()=>{
console.log(selected)
},[selected])
return (
<>
<div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
<label htmlFor={title}>{title}</label>
<input
id={title}
type="checkbox"
checked={!!selected}
onChange={ev=>{setSelected(curr=>!curr)}}
></input>
</div>
</>
);
};
I have almost got this workign but not quite sure what I am doing wrong. It will slide in when I click the toggle button, but it wont slide out when I click it again, it will just rerun the slide in animation.
Any help would be great
I have the following state and toggle function
const [close, setClose] = useState(false)
const toggleCart = () => {
setClose(!close)
}
following component
<CartItems close={close} location={location} />
import React, { useState } from "react"
import tw, { styled } from "twin.macro"
import { useTransition, animated } from "react-spring"
const CartWrapper = styled.div`
.test {
position: fixed;
top: 0px;
z-index: 5000;
right: 0;
height: 100vh;
background: lightgrey;
padding: 25px;
}
`
export function CartItems({ location, close }) {
const transitions = useTransition(close, null, {
enter: { transform: "translate3d(100%,0,0)" },
leave: { transform: "translate3d(0%,0,0)" },
})
return (
<>
<CartWrapper>
{transitions.map(({ props }) => {
return (
<animated.div className="test" style={props}>
<h2>Shopping Cart</h2>
{cart}
<p>Total: {formattedTotalPrice}</p>
<form onSubmit={handleSubmitCheckout}>
{/* include validation with required or other standard HTML validation rules */}
<input
name="name"
placeholder="Name:"
type="text"
onChange={e => setName(e.target.value)}
/>
<input
name="giftMessage"
placeholder="Gift Message:"
type="text"
onChange={e => setGiftMessage(e.target.value)}
/>
<input type="submit" />
</form>
<button onClick={clearCart}>Remove all items</button>
</animated.div>
)
})}
{/* <button onClick={handleSubmit}>Checkout</button> */}
</CartWrapper>
</>
)
}
In your example there is a second item during the transition, one entering, and one leaving. That's why you see always the entering animation.
If you use a boolean instead of array in the useTransition you have to insert a condition in the render method to prevent the second item. Just like the third example in the useTransition doc. https://www.react-spring.io/docs/hooks/use-transition
transitions.map(({ item, props, key }) => {
return (
item && <animated.div className="test" style={props} key={key}>
Now it basically works, but a slight modification in the useTransition is necessary.
const transitions = useTransition(close, null, {
from: { transform: "translate3d(100%,0,0)" },
enter: { transform: "translate3d(0%,0,0)" },
leave: { transform: "translate3d(100%,0,0)" }
});
I have a working example here: https://codesandbox.io/s/toggle-react-spring-transition-ju2jd
can someone tells me why the dropdown menu is not displaying in this demo? the dropdown menu should show when I hover over the word 'collective'?
https://codesandbox.io/s/funny-river-c76hu
For the app to work, you would have to type in the input box "collective", click analyse, then a progressbar will show, click on the blue line in the progressbar, an underline would show under the word "collective" then you should hover over "collective" word and a drop down menu should be displayed but the whole window disappears when I hover over the word "collective" instead of the drop down menu
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Content, Dropdown, Label, Progress, Button, Box } from "rbx";
import "rbx/index.css";
function App() {
const [serverResponse, setServerResponse] = useState(null);
const [text, setText] = useState([]);
const [loading, setLoading] = useState(false);
const [modifiedText, setModifiedText] = useState(null);
const [selectedSentiment, setSentiment] = useState(null);
const [dropdownContent, setDropdownContent] = useState([]);
const [isCorrected, setIsCorrected] = useState(false);
const [displayDrop, setDisplayDrop] = useState(false);
useEffect(() => {
if (serverResponse && selectedSentiment) {
const newText = Object.entries(serverResponse[selectedSentiment]).map(
([word, recommendations]) => {
const parts = text[0].split(word);
const newText = [];
parts.forEach((part, index) => {
newText.push(part);
if (index !== parts.length - 1) {
newText.push(
<u
className="dropbtn"
data-replaces={word}
onMouseOver={() => {
setDropdownContent(recommendations);
setDisplayDrop(true);
}}
>
{word}
</u>
);
}
});
return newText;
}
);
setModifiedText(newText.flat());
}
}, [serverResponse, text, selectedSentiment]);
const handleAnalysis = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setServerResponse({ joy: { collective: ["inner", "constant"] } });
}, 1500);
};
const handleTextChange = event => {
setText([event.target.innerText]);
};
const replaceText = wordToReplaceWith => {
const replacedWord = Object.entries(serverResponse[selectedSentiment]).find(
([word, recommendations]) => recommendations.includes(wordToReplaceWith)
)[0];
setText([
text[0].replace(new RegExp(replacedWord, "g"), wordToReplaceWith)
]);
setModifiedText(null);
setServerResponse(null);
setIsCorrected(true);
setDropdownContent([]);
};
const hasResponse = serverResponse !== null;
return (
<Box>
<Content>
<div
onInput={handleTextChange}
contentEditable={!hasResponse}
style={{ border: "1px solid red" }}
>
{hasResponse && modifiedText
? modifiedText.map((text, index) => <span key={index}>{text}</span>)
: isCorrected
? text[0]
: ""}
</div>
<br />
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))
</div>
) : null}
<br />
<Button
color="primary"
onClick={handleAnalysis}
disabled={loading || text.length === 0}
>
analyze
</Button>
<hr />
{hasResponse && (
<Label>
Joy{" "}
<Progress
value={Math.random() * 100}
color="info"
onClick={() => setSentiment("joy")}
/>
</Label>
)}
</Content>
</Box>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
css file
.App {
font-family: sans-serif;
text-align: center;
}
.highlight {
background: red;
text-decoration: underline;
}
.dropbtn {
color: white;
font-size: 16px;
border: none;
cursor: pointer;
}
.dropbtn:hover,
.dropbtn:focus {
background-color: #2980b9;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
position: relative;
background-color: #f1f1f1;
min-width: 160px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}
.show {
display: block;
}
The problem is this:
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))
</div>
) : null}
You are missing a pair of curly brackets around dropdownContent. It should be:
{displayDrop ? (
<div
id="myDropdown"
class="dropdown-content"
onClick={() => setDisplayDrop(false)}
>
{dropdownContent.map((content, index) => (
<>
<strong onClick={() => replaceText(content)} key={index}>
{content}
</strong>{" "}
</>
))}
</div>
) : null}
A working sandbox here https://codesandbox.io/embed/fast-feather-lvpk7 which is now displaying this content.