I had to show fading out of OutterNav when clicked on it and open a model in its place and all this should have transition.
so what I did was to add the className on click of the OutterNav to show the scale and fade transition of OutterNav . But the opening of modal was based on state value which changed on click of OutterNav. So I added a delay in state update using setTimeout.
I had a question that if it is a good way to hold state update in such scenario or there is a good way to do it?
here is some code for reference
import React from "react";
import { useRef } from "react";
const OutterNav = ({ setShowInnerNav }) => {
const containerRef = useRef(null);
const handleClick = () => {
if (containerRef.current) {
containerRef.current.classList.add(
"search-widget-container-animation"
);
}
setTimeout(() => {
setShowInnerNav(true);
}, 300); //is it okay....?
};
return (
<div
className='search-widget-container'
ref={containerRef}
onClick={handleClick}
>
<button className='btn'>
<span className='btn-text'>Anywhere</span>
</button>
<button className='btn'>
<span className='btn-text'>Any week</span>
</button>
</div>
);
};
export default AirbnbOutterNav;
Related
In my app I want to make my element always scrolled to bottom after getting new logs.
For some reason my logsRef.current.scrollTop has value of zero all the time. My logs do show on screen and in console. I am not sure why is this not working, I've tried to use different approaches using useLyaoutEffect() but nothing made logsRef.current.scrollTop value change, it stayed zero all the time.
//my Logs.jsx component
import { useEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`}>
<div ref={logsRef}>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also, I do render my Logs.jsx in BattlePhase.jsx component where I do my attack logic on click and I save logs using useState() hook.
//parts where i do save my logs in BattlePhase.jsx
const [logs, setLogs] = useState([]);
const attackHandler = () => {
//logs where pokemon on left attacked pokemon on right
setLogs((prevLogs) => [
...prevLogs,
`${pokemonDataOne.name} attacked ${
pokemonDataTwo.name
} for ${attack.toFixed(2)} dmg`,
`${pokemonDataTwo.name} died`,
])
}
...
<Attack className={isActiveArrow}>
<Button onClick={attackHandler}>Attack!</Button>
</Attack>
Slight long shot but it's possible that the ref is attached to the wrong element. Are you sure the element with the CSS property that makes it scrollable (overflow) isn't on <Container>?
//my Logs.jsx component
import { useLayoutEffect, useRef } from "react";
import Container from "./UI/Container";
import styles from "./Logs.module.css";
const Logs = ({ logs }) => {
const logsRef = useRef(null);
useLayoutEffect(() => {
logsRef.current.scrollTop = logsRef.current.scrollHeight;
console.log(logs);
console.log(logsRef.current.scrollTop);
}, [logs]);
return (
<Container className={`${styles.logs} ${styles.container}`} ref={logsRef}>
<div>
{" "}
{logs.map((log, index) => (
<p key={index}>{log}</p>
))}
</div>
</Container>
);
};
export default Logs;
Also to confirm, you do need useLayoutEffect here.
For example I have this code.
And I want to use CSS transitionfor Button when showButton and when !showButton. Now it's just removed and add Button when showButton changes.
{showButton && (
<Button
onClick={() => setShowMessage(true)}
size="lg"
>
Show Message
</Button>
)}
Is it possible make by some events or appending classNames like active?
Append the className with the ternary operator.
But, for example, this code will only adjust the class of the button specified (effectively doing the same thing you described, hiding & showing the button):
import React, { useState } from 'react';
export const Component = () => {
const [showButton, setShowButton] = useState(false);
const handleClick = () => {
setShowButton(true);
}
return (
<button
onClick={handleClick}
className={showButton ? 'showButtonClass' : 'hideButtonClass'}
>
Show Message
</button>
);
};
For content to show once the button is clicked, you'll need something like:
import React, { useState } from 'react';
export const Component = () => {
const [showMessage, setShowMessage] = useState(false);
const handleClick = () => {
setShowMessage(true);
}
return (
<div>
<button
onClick={handleClick}
>
Show Message
</button>
{showMessage && <h1>
The message you'll see when clicking!
</h1>}
</div>
);
};
This Footer component creates an array. I'd like to make this array available in a sibling Body component. I'd like to do this by passing the reminders array variable up and over to the Body component by way of their mutual Parent component.
import './style.css'
function Footer({ linkToFooter }){
const [reminder, setReminder] = useState("");
const [reminders, setReminders] = useState([]);
const submitThis = (e) => {
e.preventDefault();
if (reminder){
let newReminder = {
reminder: reminder,
complete: false,
};
setReminders(prevRems => [...prevRems, newReminder])
setReminder('');
} else {
setReminder("oops there's a problem");
}
};
useEffect(
() => { localStorage.setItem('reminders', JSON.stringify(reminders)); },
[reminders]
);
console.log(reminders, "from Footer");
return(
<div className="rm-list-footer">
<form onSubmit={submitThis}>
<input
id='newReminder'
type='text'
value={reminder}
onChange={(e) => setReminder(e.target.value)}
/>
<button onClick={() => linkToFooter(reminders)} type='submit'>+</button>
</form>
</div>
)
}
export default Footer;
This is the Parent function component that I'd like to be the segway between the two child components.
import Header from "./Header";
import Footer from "./Footer";
import Body from "./Body";
function RmList() {
const [reminders, setReminders] = useState([])
return(
<div className="rm-list">
<Header></Header>
<Body reminders={reminders}></Body>
<Footer linkToFooter={setReminders}></Footer>
</div>
);
}
export default RmList;
Finally, my goal is to console.log the array in this sibling Body component every time the button in the Footer component is clicked. The problem is that the array that's logged in the Body is always one element behind what's logged in the Footer with every click.
import React from "react";
function Body({reminders}) {
console.log(reminders, "from Body");
return(
<div className="rm-list-body"></div>
);
}
export default Body;
After researching I thought my problem might have to do with changing this
setReminders([newReminder, ...reminders])
to this
setReminders(prevRems => [...prevRems, newReminder])
but that was to no avail as I can't pass prevRems to the parent.
Your help is kindly appreciated.
If im understanding correctly, you want to be able to set reminders in one child and see reminders in a sibling. if that is correct, reminders as a state should only be in the parent. you can then pass the state and state-setter to the children and access them through props. by re-declaring "reminders" in the child you could have a conflict of state.
also, it is best practice to name your props being passed like this
<Footer setReminders={setReminders}>
so that on larger projects its easy to follow your prop's path
try
import './style.css'
CHANGE >>>> function Footer({ linkToFooter > setReminders }){
const [reminder, setReminder] = useState("");
REMOVE >>>> const [reminders, setReminders] = useState([]);
const submitThis = (e) => {
e.preventDefault();
if (reminder){
let newReminder = {
reminder: reminder,
complete: false,
};
setReminders(prevRems => [...prevRems, newReminder])
setReminder('');
} else {
setReminder("oops there's a problem");
}
};
useEffect(
() => { localStorage.setItem('reminders', JSON.stringify(reminders)); },
[reminders]
);
console.log(reminders, "from Footer");
return(
<div className="rm-list-footer">
<form onSubmit={submitThis}>
<input
id='newReminder'
type='text'
value={reminder}
onChange={(e) => setReminder(e.target.value)}
/>
CHANGE >>>> <button onClick={() => linkToFooter > setReminders(reminders)} type='submit'>+</button>
</form>
</div>
)
}
export default Footer;
let me know if that has helped guide you in the right direction. and hopefully these little tricks will help you keep your code more readable. Good Luck!
I'm trying to remove a CSS class from a specific item when clicking on that item's button. Removing the CSS class will make a menu appear. How would I go about doing this with React? Here's the code.
import "./Homepage.css"
import React, { useState, useEffect, useRef } from "react"
// import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
// import { faArrowDown } from "#fortawesome/free-solid-svg-icons"
import { Link } from "react-router-dom"
import useFetch from "./useFetch"
import Axios from "axios"
export default function Homepage() {
const [body, setBody] = useState("")
const [sortedData, setSortedData] = useState("")
const [data, setData] = useState("")
const [css, setCss] = useState("")
const [flash, setFlash] = useState(null)
const posts = useFetch("http://localhost:5000/api/data")
const firstRender = useRef(true)
useEffect(() => {
let test = JSON.parse(window.localStorage.getItem("user"))
console.log(test)
setData(posts)
}, [posts])
useEffect(() => {
if (firstRender.current) {
firstRender.current = false
return
}
data.sort(function (a, b) {
return new Date(b.date) - new Date(a.date)
})
setSortedData(data)
}, [data])
const handleSubmit = (e) => {
e.preventDefault()
Axios.post("http://localhost:5000/api/react-create-post", { text: body }, { withCredentials: true })
.then((res) => {
setSortedData((prevArray) => [res.data.post, ...prevArray])
setFlash("Successfully created post.")
setCss("success-msg")
setBody("")
})
.catch((err) => {
setCss("error-msg")
setFlash("Field cannot be left blank.")
})
}
const handleClick = (e) => {
e.preventDefault()
e.target.parentElement.children[1]
}
return (
<div>
<center>
<div className="create-container">
<div className="posts-title">Create Post</div>
<form id="theForm" onSubmit={(e) => handleSubmit(e)}>
<textarea onChange={(e) => setBody(e.target.value)} value={`${body}`} id="theInput" className="post-input" name="text" type="text"></textarea>
<button className="submit-btn">POST</button>
</form>
</div>
<div id="postsContainer" className="posts-container">
<div className="posts-title">Latest Posts</div>
{flash ? <div className={css}>{flash}</div> : console.log()}
<div id="postInput">
{sortedData &&
sortedData.map((item) => {
return (
<div className="post-container" key={item._id}>
<Link className="a" to={`/user/${item.author}`}>
<h3 className="author">{item.author}</h3>
</Link>
<div className="date">{item.date.toLocaleString()}</div>
<div className="options-cont">
<button onClick={(e) => handleClick(e)} id="optionsBtn" className="options-btn">
<i className="fas fa-ellipsis-v"></i>
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} data-text={`${item.body}`} id="editBtn" className="edit inside-btn invisible">
Edit
</button>
<button data-author={`${item.author}`} data-id={`${item._id}`} id="deleteBtn" className="delete inside-btn invisible">
Delete
</button>
<br></br>
</div>
<p className="body-text">{item.body}</p>
</div>
)
})}
</div>
</div>
</center>
</div>
)
}
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
As pilchard said, you probably want to make each of those its own component with its own "showing" state, or at least "showing" prop.
As far as I'm concerned using state as the className would remove or alter the CSS of each item in the "sortedData" array and make the menus for all items appear. I only want the menu for one of the items to appear.
That would be true if you used a single flag in state. But instead, use a set of flags, one flag for each menu, perhaps keyed by item._id.
Assuming you don't do the refactoring pilchard (and I) suggest:
You haven't shown us enough code for me to know whether you're using class components or function components, so I'm going to guess function components with hooks. If so, the initial state would be:
const [showing, setShowing] = useState(new Set());
Then when rendering, you'd assign the class:
<theElement className={showing.has(item._id) ? "class-if-any-to-show-it" : "class-if-any-to-not-show-it" ...
To toggle, in the button pass the ID:
<button onClick={(e) => handleClick(e, item._id)}
and then update state as appropriate:
const handleClick = (e, id) => {
e.preventDefault()
setShowing(showing => {
showing = new Set(showing);
if (showing.has(id)) {
showing.delete(id);
} else {
showing.add(id);
}
return showing;
});
};
I am working with a form in react, and what I would like is that when I click a button, I add a new component which is just an input to the screen. It all mostly works, as planned. The issue is with the following: the layout is that I have one main component, which then displays a child component. That child component is called from a map of a useState. (More after code snippet)
This is the code of the main component:
import React, { useState } from "react";
import SingleProfile from "./individual_profile";
const ProfileInformation = (props) => {
console.log("proflie render");
const [ProfilesBoolean, setProfilesBoolean] = useState(false);
const [profiles, setProfiles] = useState(props.Data['profiles'])
const FieldAdd = (event)=>{
event.preventDefault();
const copy = profiles;
copy.push({Network:'',url:''})
return(copy)
}
function CreateInput(){
return profiles.map((data, index) =><SingleProfile index={index} data={data} />)
}
const accordion = (event) => {
const NextElement = event.target.nextElementSibling;
if (!event.target.className.includes("display")) {
NextElement.style.maxHeight = NextElement.scrollHeight + "px";
} else {
NextElement.style.maxHeight = 0;
}
};
return (
<div className="AccordionItem">
<div
className={
ProfilesBoolean ? "AccordionHeader-display" : "AccordionHeader"
}
onClick={(e) => setProfilesBoolean(!ProfilesBoolean)}
id="ProfileForm"
>
Profiles
</div>
<div className="AccordionContent">
<div className="AccordionBody">
{
profiles.map((data, index) => (
<SingleProfile index={index} data={data} />
))
}
<button id="ProfileAdd" onClick={(e) => {setProfiles(FieldAdd(e))}}>
Add a profile
</button>
</div>
</div>
</div>
);
};
export default ProfileInformation;
When I click the button and onClick fires FieldAdd() the useState updates, with a new empty object as expected. However, it does not appear inside my <div className="AccordionBody"> as I would expect it to.
The following code is used to display components, by opening and closing the child div. When it is open is when you see the child components and the add button. If I click the div, to close and then click again to re-open it, the new child component appears.
<div
className={ProfilesBoolean ? "AccordionHeader-display" : "AccordionHeader"}
onClick={(e) => setProfilesBoolean(!ProfilesBoolean)}
id="ProfileForm"
>
Profiles
</div>;
Is it possible to have the child component appear without having to close and re-open the div?
Your clickHandler FieldAdd is incorrect. You are mutating the state directly which will not cause re-render.
use setProfiles to update the state in the clickHandler. Like this
const FieldAdd = (event)=>{
setProfiles(prev => [...prev, {Network:'',url:''}])
}
Trigger the onClick like this
<button id="ProfileAdd" onClick={(e) => {FieldAdd(e)}}>
Add a profile
</button>
...