I have a register page for two types of users as tabs, so when I click one kind of user the other should be hidden and the clicked user tab should be displayed. I tried useState, and the hiding works but the desired user form is not rendered. I'm sure I'm missing something here.
const [customerForm, setCustomerForm] = useState(true);
const [feForm, setFEForm] = useState(false);
const FEToggle = () =>{
customerForm ? setCustomerForm(false) : setCustomerForm(true)
feForm ? feForm(false): setFEForm(true)
}
const CTRToggle = () =>{
feForm ? setFEForm(false) : setFEForm(true)
customerForm ? setCustomerForm(false): setCustomerForm(true)
}
<button type="button" onClick={() => {FEToggle()}}>
<i className="fa fa-building" />
Financial Enterprise
</button>
<button type="button" onClick={() => {CTRToggle()}}>
<i className="fa fa-user" />
Customer
</button>
feForm ? feForm(false): setFEForm(true)
change to
feForm ? setFEForm(false): setFEForm(true)
Change FEToggle and CTRToggle method
const FEToggle = () => {
setCustomerForm(prev => !prev)
setFEForm(prev => !prev)
}
const CTRToggle = () => {
setCustomerForm(prev => !prev)
setFEForm(prev => !prev)
}
Related
import React from 'react'
import { useState, useEffect } from 'react'
import axios from 'axios'
const Home = () => {
const getSongs = () => {
axios.get('http://localhost:8000/api/songs/')
.then(res => setSongs(res.data))
}
let [songs, setSongs] = useState([])
let [paused, setPause] = useState(true)
useEffect(() => {
getSongs()
}, [])
const toggleSong = (id) => {
const x = document.getElementById(id)
if (x.paused){
x.play()
setPause(false)
} else {
x.pause()
setPause(true)
}
}
// Got rid of the functions that are not needed
return (
<>
{
songs.map(song =>
(
<div className='music-controller' key={song.id}>
<div id={'songDiv'} style={{cursor: 'pointer'}} onClick={(e) => changeSongTime(e, song.id)}>
<div id={`songTime-${song.id}`}></div>
</div>
<div className="music-controller-body">
<div className="music-controller-header">
<h2>{song.title}</h2>
<p><small>{song.genre}</small></p>
</div>
<div className="controls">
// here <----------------------
<i unique={song.id} className={`fas fa-${paused ? 'play' : 'pause'}`} onClick={() => toggleSong(song.id)}></i>
<audio id={song.id} onTimeUpdate={() => songTime(song.id)}>
<source src={`http://localhost:8000/api/songs/audio/${song.id}`} />
</audio>
</div>
</div>
</div>
))}
</>
)
}
export default Home
Whenever I click on a specific i element all of the i elements that were not clicked on get changed too.. to put it simply when I click on the 1st i element only its className should change, but all of the i elements classNames are affected, what is causing this?
I think you should use event.target
const handlePlay = (song) => {
song.play();
};
const handlePause = (song) => {
song.pause();
};
...
<div className="controls">
<i
onMouseOver={(e) => handlePlay(e.target)}
onMouseLeave={(e) => handlePause(e.target)}
className={`fas fa-${paused ? 'play' : 'pause'}`}
onClick={() => toggleSong(song.id)}>
</i>
<audio id={song.id} onTimeUpdate={() => songTime(song.id)}>
<source src={`http://localhost:8000/api/songs/audio/${song.id}`} />
</audio>
</div>
I don't think Toggle would work in this case, an action should happen so it knows when it should stop.
Can you put console in toggleSong function at top and check if you are getting correct id. If you are not getting single Id then work is needed with onClick. So, after that also try passing id like this
onClick={(song?.id) => toggleSong(song?.id)}
then see console again and look for correct id if it is displayed or not. I think your className is not updating due to this issue.
One thing more you can try at end is replacing with this
const x = id; //without document.getElementById
const toggleSong = (e, id) => {
const x = document.getElementById(id)
const button = e.currentTarget
if (x.paused){
x.play()
button.className = 'fas fa-pause'
} else {
x.pause()
button.className = 'fas fa-play'
}
}
<i unique={song.id} className='fas fa-play' onClick={(e) => toggleSong(e, song.id)}></i>
I fixed this by just getting the current target with event.currentTarget and change its className accordingly!
When I click on a button, I am adding a product to the cart but I also want to set the state of a side drawer to true so it appears. This is working but trying to pass that state to the component so that when I click on close is giving me trouble. Here are basically all the moving parts:
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
<CartDrawer toggleOpen={isOpen} />
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
This is working fine. I click on add to cart, it adds to cart and my modal shows up as expected.
The modal is basically component and I am receiving toggleOpen as props. Here is the CartDrawer component
const CartDrawer = (props) => {
const [isOpen, setIsOpen] = useState(false);
const closeNavHandler = () => {
setIsOpen(false);
};
return (
<div
id="mySidenav"
className={props.toggleOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={closeNavHandler}
>
×
</a>
</div>
);
};
export default CartDrawer;
I know this is wrong but I can't figure out how to update the state here correctly to close it.
Just control everything from the parent. The cartDrawer only needs to receive a ìsOpen prop to know its state. Don't write another state in it.
A component like this should be stupid. It receives informations, and display them. Don't spill the logic all over your components. Just have a single source of truth.
// Main
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
<CartDrawer isOpen={isOpen} onClose={()=> setIsOpen(false)}/>
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
// CartDrawer
const CartDrawer = ({isOpen, onClose}) => {
return (
<div
id="mySidenav"
className={isOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={onClose}
>
×
</a>
</div>
);
};
export default CartDrawer;
I think that's what you want?
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
const toggleOpenDrawer = (val) => {
setIsOpen(val);
}
<CartDrawer toggleOpenDrawer={toggleOpenDrawer} toggleOpen={isOpen} />
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
const CartDrawer = (props) => {
const { toggleOpenDrawer } = props
return (
<div
id="mySidenav"
className={props.toggleOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={toggleOpenDrawer(!props.toggleOpen)}
>
×
</a>
</div>
);
};
export default CartDrawer;
I'm sorry if the answer is obvious. I probably missed a pattern to do that...
I have this kind of architecture (more complexe, of course) :
A component (here a button) have to call a function and to set a state in the same time.
Of course, This example don't work because the state to displayed word isn't defined before the speak() function is called.
What is the best way to do this ?
const Speaker = () => {
const [word, setWord]= useState ("nothing")
const speak = () => {
console.log(word)
...
}
const speakDirectly = () => {
setWord("hello")
speak() // say "nothing" instead of "hello" 😞
}
const prepareWord = (wordToPrepare) => {
setWord(wordToPrepare)
}
return (
<>
<p>Thinking word : {word} </p>
<button onClick={() =>prepareWord('Goodbye')} >Prepare to say "Goodbye"</button>
<button onClick={() =>prepareWord('Goodnight')} >Prepare to say "Goodnight"</button>
----
<button onClick={() =>speak()} >Say Hello</button>
<button onClick={() =>speakDirectly('hello')} >Say Hello</button>
</>
)
}
Does exists a pattern to resolve this behaviour in React?
I want to avoid to update my functions like this 🤢
const Speaker = () => {
const [word, setWord]= useState ("nothing")
const speak = (directWord) => {
let wordToSay = directWord || word
console.log(word)
...
}
const speakDirectly = (directWord) => {
setWord(directWord)
speak(directWord)
}
...
You could do something like this. Have a state for your word and your direct word. Use an effect for your direct word to trigger speak whenever it's updated, but call speak on click for your prepared word.
const Speaker = () => {
const [preparedWord, setPreparedWord]= useState();
const [directWord, setDirectWord]= useState();
const speak = (word) => {
console.log(word)
...
}
useEffect(() => directWord && speak(directWord), [directWord]);
return (
<>
<p>Thinking word : {preparedWord} </p>
<button onClick={() => setPreparedWord('Goodbye')} >Prepare to say "Goodbye"</button>
<button onClick={() => setPreparedWord('Goodnight')} >Prepare to say "Goodnight"</button>
----
<button onClick={() => speak(preparedWord)} >Speak</button>
<button onClick={() => setDirectWord('hello')} >Say Hello</button>
</>
)
}
Update
After thinking about it for a second, unless you need the direct word to be in your state, you can get rid of that part too and just call speak on it while passing it to the function.
const Speaker = () => {
const [preparedWord, setPreparedWord]= useState();
const speak = (word) => {
console.log(word)
...
}
return (
<>
<p>Thinking word : {preparedWord} </p>
<button onClick={() => setPreparedWord('Goodbye')} >Prepare to say "Goodbye"</button>
<button onClick={() => setPreparedWord('Goodnight')} >Prepare to say "Goodnight"</button>
----
<button onClick={() => speak(preparedWord)} >Speak</button>
<button onClick={() => speak('hello')} >Say Hello</button>
</>
)
}
When I click on the + every list is displayed and if I click on the - everything is hidden. How can I click on specific button and it displays the content or hide as the case may be. Right now a click on any of the button either hides or shows.
{ showing ? <button onClick={(e) => setShowing(false)}>-</button> : <button onClick={(e) => setShowing({showing: showing})}>+</button>
}
{ showing
? student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
)) : <span></span>
}
Your plus function seems to be invalid, try something like this:
For the minus (-):
<button onClick={(e) => setShowing(false)}>-</button>
For the plus (+):
<button onClick={(e) => setShowing(true)}>+</button>
You either need to keep track of a showing state variable for each row, or a more "React" style solution would be to create another component for each row, and let them each have one state variable:
function myComponent(props) {
const { students } = props.students; // or wherever these come from - you didn't specify
return students.map((student) => <StudentRow {...student} />});
}
function StudentRow (props) {
const [showing, setShowing] = useState(false); // or true if they default visible
return (<>
{ showing ? <button onClick={(e) => setShowing(false)}>-</button> : <button onClick={(e) => setShowing(true)}>+</button>}
{ showing ? student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
)) : <span></span>}
</>);
}
You could refactor this StudentRow component to be more easily readable as:
function StudentRow (props) {
const [showing, setShowing] = useState(false); // or true if they default visible
if (!showing) {
return <button onClick={(e) => setShowing(true)}>+</button>;
} else {
return (<>
<button onClick={(e) => setShowing(false)}>-</button>
{student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
))}
</>);
}
I am much confused as I don't know what I am doing wrong. Each time I clicked on the plus sign, all the other div elements display instead of the specific one I click on. I tried to use id argument in my show and hide functions, it is complaining of too many re-rendering . I have been on this for the past 12 hours. I need your help to solving this mystery. All I want to do is to click on the plus sign to display only the content and minus sign to hide it.
import React, {useState, useEffect} from 'react'
function Home() {
const [userData, setUserData] = useState([]);
const [showing, setShowing] = useState(false)
const [search, setSearch] = useState("");
const [clicked, setClicked] = useState("")
async function getData()
{
let response = await fetch('https://api.hatchways.io/assessment/students');
let data = await response.json();
return data;
}
useEffect(() => {
getData()
.then(
data => {
setUserData(data.students ) }
)
.catch(error => {
console.log(error);
})
}, [])
const handleFilterChange = e => {
setSearch(e.target.value)
}
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
const searchPosts = DataSearch(userData);
const show = (id, e) => {
setShowing(true);
}
const hide = (id, e) => {
setShowing(false);
}
return (
<>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search by name"} />
</div>
{
searchPosts.map((student) => (
<div key={student.id} className="holder">
<div className="images">
<img src={student.pic} alt="avatar" width="130" height="130" />
</div>
<div className="data-container">
<span className="name">{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</span>
<span>Email: {student.email}</span>
<span></span>
<span>Company: {student.company}</span>
<span>Skill: {student.skill}</span>
<span>City: {student.city}</span>
{ showing ?
<button id={student.id} onClick={hide}>-</button>
: <button id={student.id} onClick={show}>+</button>
}
<div data-id={student.id}>
{ (showing )
? student.grades.map((grade, index) => (
<span id={index} key={index}>Test {index}: {grade}%</span>
)) : <span>
</span>
}
</div>
</div>
</div>
))
}
</>
)
}
export default Home
Change,
const [showing, setShowing] = useState(false)
to:
const [showing, setShowing] = useState({});
Here change the useState from boolean to object.. Reason for this is we will store the ids as keys and a boolean value indicating if the grade should be shown or not.
And remove Show and hide function and have a common toggle function like,
const toggleGrades = (id) => {
setShowing((previousState) => ({
...previousState,
[id]: !previousState[id]
}));
};
You are using setShowing(true) in show function and setShowing(false) in hide function which is the reason for opening all and closing all at any click.. Because you have never mentioned which exact grade should be shown so you need to make use of id here..
And buttons click handler will be like,
{showing[student.id] ? (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
-
</button>
) : (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
+
</button>
)}
So pass student id () => toggleGrades(student.id) in both show and hide button an make the button gets toggled.
Display the grades like,
<div data-id={student.id}>
{showing[student.id] ? (
student.grades.map((grade, index) => (
<span id={index} key={index}>
Test {index}: {grade}%
</span>
))
) : (
<span></span>
)}
</div>
Here if showing[student.id] will display only the grades of clicked item.
And that is why id plays a major role in such case.
Working Example: