Showing/hiding elements in React - javascript

having a little trouble here when trying to show or hide divs in react.
I have three classes:
1. RunningOrder Section
2. RunningOrder
3. Story
running order section is the main section, which renders the running order class, and running order renders the Story class.
I have a toggle switch in running order section which toggles the 'displayStory' state in running order and running order passes the 'displayStory' bool state as a prop to story.
toggle in RunningOrder Section:
_triggerChildDisplay: function () {
this.refs.child.displayStory();
},
<Toggle
tooltip = {'Auto Sequence'}
onToggle = {this._triggerChildDisplay}
/>
for (let storyId in ro.stories) {
allStories.push(<Story key={storyId} story={ro.stories[storyId]} displayStory={this.state.displayStory}/>)
};
state and prop in Running Order:
displayStory: function() {
this.setState({
displayStory: !this.state.displayStory
});
},
for (let storyId in ro.stories) {
allStories.push(<Story key={storyId} story={ro.stories[storyId]} displayStory={this.state.displayStory}/>)
};
render method of story
if(allItems.length > 0) {
return (
<div className="story-container">
<div className="story-label-container">
<div className="story-slug">
<span className="story-title">
{story.slug}
</span>
{/*<span className="storyItemLength">
{allItems.length}
</span>*/}
<span className="story-sequence-control">
<OverlayTrigger placement="top" overlay={autoSequenceTooltip}>
<Toggle
label = {<SequenceIcon />}
disabled = {allItems.length > 0? false : true}
tooltip = {'Auto Sequence'}
onToggle = {this.toggled}
/>
</OverlayTrigger>
</span>
</div>
</div>
{itemList}
</div>
);
} else {
return (
<div className={this.props.displayStory ? "story-container" : "hidden story-container"}>
<div className="story-label-container">
<div className="story-slug">
<span className="story-title">
{story.slug}
</span>
{/*<span className="storyItemLength">
{allItems.length}
</span>*/}
<span className="story-sequence-control">
<OverlayTrigger placement="top" overlay={autoSequenceTooltip}>
<Toggle
label = {<SequenceIcon />}
disabled = {allItems.length > 0? false : true}
tooltip = {'Auto Sequence'}
onToggle = {this.toggled}
/>
</OverlayTrigger>
</span>
</div>
</div>
{itemList}
</div>
);
}
}
I only want to be able to toggle the display of elements in story if the allItems array has length greater than 0, hence the two returns.
Running order loops and pulls data from a server, the running order class returns 3 items each of these items have multiple items in them rendered by story which i need to toggle the display of.
Now my code is working, but for some reason I can only toggle the display (the hidden css class) in the last element returned by running order. so if running order returned:
<div 1>
<items />
</div>
<div 2>
<items />
</div>
<div 3>
<items />
</div>
the display toggle only affects the items in div 3.
*******Update:** instead of a single toggle for all elements rendered by running order I have put the toggle switch in running order and it works fine, so it is something with call the reffs here: this.refs.child.displayStory();

Related

How do I make React onClick calls only affect a single item, instead of all?

I have an array of objects, and for each one I .map it into a component called Card.js. Each card has an 'edit' button, and I have an edit form which I want to appear ONLY for the card on which I clicked the button.
At the moment, whatever I try to do to pass an id into the Editform.js component, it still makes the form appear for all of the card components.
Here's the current component I call which is meant to render just form for the clicked button. I pass in all of the cards in the 'cards' array, and what I believe is the id of the current .map object from the calling function:
function Editform({ cards, setCards, id }) {
const thisCard = cards.filter((card) => card.id === id)[0];
const editThisCard = thisCard.id === id; // trying to match id of passed card to correct card in 'cards' array.
console.log(editThisCard);
return (
<>
{editThisCard && ( // should only render if editThisCard is true.
<div className="form">
<p>Name of game:</p>
<input type="text" value={thisCard.gamename}></input>
<p>Max players: </p>
<input type="text" value={thisCard.maxplayers}></input>
<p>Free spaces: </p>
<input type="text" value={thisCard.freespaces}></input>
<p>Table #: </p>
<input type="text" value={thisCard.tablenum}></input>
<p></p>
<button type="button" className="playbutton">
Save changes
</button>
</div>
)}
</>
);
}
export default Editform;
edit: apologies, I forgot to paste in the other code. Here it is. Note that I'm just hardcoding in a couple of cards for now:
import React from "react";
import ReactFitText from "react-fittext";
import Editform from "./Editform";
function Displaycards({ lastid }) {
const [cards, setCards] = React.useState([
{
id: 1,
gamename: "El Dorado",
maxplayers: 4,
freespaces: 1,
tablenum: 5,
},
{
id: 2,
gamename: "Ticket to Ride",
maxplayers: 4,
freespaces: 2,
tablenum: 3,
},
]); // using the React state for the cards array
const [showForm, setShowForm] = React.useState((false);
return (
<div className="cardwrapper">
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
<div>
<div className="card">
<ReactFitText compressor={0.8}>
<div className="gamename">{gamename}</div>
</ReactFitText>
<div className="details">
<p>Setup for: </p>
<p className="bignumbers">{maxplayers}</p>
</div>
<div className="details">
<p>Spaces free:</p>
<p className="bignumbers">{freespaces}</p>
</div>
<div className="details">
<p>Table #</p>
<p className="bignumbers">{tablenum}</p>
</div>
<button type="button" className="playbutton">
I want to play
</button>
<br />
</div>
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(!showForm)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}
</div>
);
}
export default Displaycards;
I feel like I'm missing something obvious, but I can't get my head around what it is. The current iteration of it is here - https://github.com/TSDAdam/lfp/tree/usestate-trial - and it was created with create-react-app .
It sounds like you have one state controlling all of the Cards. You haven't shown the Card component yet however. Have every Card control its own state, so when the edit button bound to the card is clicked, it only applies to that one card. If you show us more code we can narrow it down, but this is most likely the gist of your problem.
The problem is that the EditForm is inside the map function, so for every item in your cards array, a separate EditForm is rendered with the corresponding values, and all these EditForms get shown/hidden based on the same boolean in your state.
The solution is to move the EditForm outside the map function, and create a new state object that tracks an "active" card, from where the single EditForm could take its values.
This of course won't work if you want to render the EditForm in a position relative to the "active" card.
[Edit]
Okay, I ended my answer with a caveat, but I should add a solution for that as well, since it isn't very complicated.
If you want to render an EditForm below the selected card, for example, the approach would be to keep it inside the map function as it is now, and change the boolean state variable showForm into one that accepts a string/number (depending on what you use as the identifier for each card). And then use this state variable to determine which form shows at any given time.
const [showForm, setShowForm] = React.useState("");
{cards.map(({ id, gamename, maxplayers, freespaces, tablenum }) => {
return (
<div key={id}>
// Rest of the JSX
<div className="editbuttons">
<button
type="button"
className="editbutton"
onClick={() => setShowForm(id)}
>
Edit
</button>
<button type="button" className="delbutton">
X
</button>
</div>
{showForm == id && (
<div>
<Editform
cards={cards}
setCards={setCards}
id={id}
/>
</div>
)}
</div>
</div>
);
})}

How to render a component outside the component that contains the function that renders the first component?

The situation is a bit complicated:
inside a component called "LeftSectionHeader" I have a div, which when clicked must render a component;
the component to be rendered is called "ProfileMenu", and is basically a div that must be rendered on top of "LeftSectionHeader" itself and another div;
All these components are rendered inside another component called "Main".
The problem is that if I define the function inside "LeftSectionHeader", "ProfileMenu" will be rendered inside, while I need it to not only be rendered outside, but even cover it; that's why you'll see some boolean vars inside "Main", because that is the only way i could render it, but it still doesn't cover the other divs. I'll attach the code of each component and how the final result should look below.
LeftSctionHeader:
function LeftSectionHeader(){
return(
<div class="left-section-header">
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
The div belonging to the "crop" class is the one that must be clicked to render "ProfileMenu"
ProfileMenu:
function ProfileMenu(){
return(
<div class="profile-side-menu">
//A lot of boring stuff
</div>
);
}
There are some functions related to this component, but they are not important, so I didn't put them, just ignore it
Main:
var p=true;
var m=true;
function Main(){
return(
<div class="main">
<Header />
<div class="left-section">
{m ? <div><LeftSectionHeader /><LangMenu /></div> : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
Before clicking on the orange div
After clicking
This might help as guidline, hopefully!
function LeftSectionHeader({ onClick }){
return(
<div class="left-section-header" onClick={onClick}>
<div class="crop" ><img src="./images/profiles/anonimous.png" /></div>
</div>
);
}
function Main(){
const [showProfile, setShowProfile] = useState(false);
return(
<div class="main">
<Header />
<div class="left-section">
{!showProfile ? (
<div>
<LeftSectionHeader onClick={() => setShowProfile(true)} />
<LangMenu />
</div>
) : <ProfileMenu />}
</div>
{p ? <PostPage /> : <NoPostsMessage />} //Ignore this line
</div>
);
}
The simplest solution might be to pass a handler into the header component to toggle the menu:
function App () {
const [showMenu, setShowMenu] = useState();
return (
<div>
<Header onMenuToggle={() => setShowMenu(!showMenu)} />
{ showMenu && <Menu /> }
</div>
)
}
function Header ({ onMenuToggle }) {
<div onClick={onMenuToggle}>...</div>
}
Caveat: This will cause the entire App component to re-render when the menu state changes. You can mitigate this by either
A) placing the menu state closer to where it's actually needed, like in the sidebar component instead of at the top, or
B) using a context or other orthogonal state store.
Another approach would be to leave the state handling in the LeftSectionHeader component and then use a React portal to render the menu elsewhere in the DOM.

Mouse hover in React

I'm trying to display a div when the mouse is over another div element. I've managed to do so via onMouseEnter and onMouseLeave.
The issue here is that if you quickly move from one div to another (it's an array of divs that contain data about a product), the value of index[0] becomes true.
The way it works is that I have an array initialised to false when the mouse enters one of them, it becomes true and shows the div that I wanted. Once it leaves, it set it back to false.
this.state = {
isProductsHovering: new Array(this.props.currentProducts.length).fill(false)
};
handleMouseHover = (idx) => {
this.setState({
isProductsHovering: update(this.state.isProductsHovering, {
[idx]: { $set: !this.state.isProductsHovering[idx] }
})
})
}
render() {
return this.props.currentProducts.map((product, idx) => {
return <Fragment key={idx}>
<div className="product-grid-view col-6 col-md-4" >
<div
className=" product-holder"
onMouseEnter={this.handleMouseHover.bind(this, idx)}
onMouseLeave={this.handleMouseHover.bind(this, idx)}>
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
this.state.isProductsHovering[idx] &&
<div className="product-buttons">
<Link to={`products/${product.id}`} className="btn-detail" text="View Details" />
<Link to='#' className="btn-cart" icons={["icon-cart", "icon-plus"]} />
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<Stars values={product.averageRating} {...starsRating} />
<span className="product-review">{getLength(product.reviews)} review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">SR</span>
</div>
</div>
</div>
</Fragment>
})
}
Update
I've made a stackblitz project to reproduce the same issue as suggested:
https://stackblitz.com/edit/react-mouse-hover.
For everyone that wants to see what I mean. I've attached a photo of the issue. If you move the mouse over the two divs (up and down as quick as you can), this what happens:
mouse hover broken
For situation like this, I wouldn't rely on array and index to make it work. You are further complicating your handleMouseHover functions and the checking of isHovering.
A 'more React' way of dealing with this situation is simply make each Product a component itself. And this Product component will have its own state of isHovered and handleOnHover method, that way you create a more concise and reliable code without having to rely on array index at all:
App.js can be as simple as this:
class App extends Component {
render() {
return (
<div>
{
data.map(product =>
<Product product={product} />
)
}
</div>
)
}
}
A new Product.js:
import React from 'react'
import ReactHoverObserver from 'react-hover-observer';
export default class Product extends React.Component {
render() {
const { product } = this.props
return (
<ReactHoverObserver className="product-grid-view col-6 col-md-4">
{
({isHovering}) => (
<div className=" product-holder">
<div className="image-container" align="center">
<img src={"/img/product-3.jpg"} alt="" />
{
isHovering &&
<div className="product-buttons">
<button className="btn-detail">View Details</button>
</div>
}
</div>
<div className="details-holder">
<span className="part-text">{product.desc}</span><br />
<span className="manufacturer-text">{product.manufacturer.name}</span>
<div className="product-review_slide">
<span className="product-review">0 review</span>
</div>
<span className="product-price">{product.salesPrice.toFixed(2)}</span>
<span className="product-currency">Currency</span>
</div>
</div>
)
}
</ReactHoverObserver>
)
}
}
I have put the moficiation in Stackblitz: https://stackblitz.com/edit/react-mouse-hover-2cad4n
Liren's answer is good advice and will help simplify the code. One thing I also noticed is that occasionally the HoverObserver won't 'hear' an event, and since the hover enter and hover exit events are listening to the same event, then the display state for the button will become reversed (i.e., it will show when the mouse is NOT hovering and hide when the mouse hovers over the observer).
I would recommend getting rid of the ReactHoverObserver HOC and instead just listen for the onMouseOver for hover enter and onMouseLeave for hover exit. That way, even if the div doesn't register a hover enter or exit, it will easily reset because onMouseOver will toggle the display state to true and onMouseLeave will reliably set the button's display state to false.
See here for those events in the docs:
https://reactjs.org/docs/events.html#mouse-events
The way you trigger it (from array or from a component) is semantics , the real issue is that these events don't always fire.
I had the same issue , apparently the events of react are not that reliable.
In my case I could live in a situation where a tooltip does not close , but not in the situation where 2 tooltips are open. In order to solve my issue , I returned to good old dom manipulation - Every time a tooltip appeared , it made all the other ones invisible.
It looked something like this :
showTooltip() {
// Clear old tooltips, EventTooltip is the name of my tooltip class.
Array.from(document.getElementsByClassName('EventTooltip'))
.forEach(tooltip=>tooltip.style = 'display:none;')
this.setState({showTooltip: true})
}
hideTooltip() {
this.setState({showTooltip: false})
}

How to access to state of repeated component in ReactJS

i am new to reactjs. I want to access state of a repeated react component. My code is
{Object.keys(this.state.sharing).map((share)=>{
return(<Sharings />)
})}
above component is repeated 3 times
The Sharing Component contains radio buttons and button.
When the radio button is clicked then only button is enabled.
Here is my code
<div className="col-xs-12 col-md-4">
<div className="book-now-card p-b-10" style={{height:height}}>
<div className="single-sharing" style={{height: '180px'}}>
<h3 className="single-sharing-heading">{props.heading}</h3>
</div>
<div className="m-t-20">
{
props.rent.map((rent)=>{
return(
<div className="m-10" key={rent}>
<input type="radio" name={'radio'} className="m-l-20 " onClick={(e)=>{this.handleChange(rent,e)}} /> <span className="m-l-20" style={{color:'#009688'}}>₹ {rent}</span>
<p className="m-l-55 f-s-12" style={{color: '#65747a'}}>{props.details}</p>
</div>
)
})
}
</div>
<div className="m-20">
<button className="btn btn-secondary col-6 select-rent" disabled={true} onClick={(e)=>{this.proceed(e)}} id={'Button'+this.props.num}>SELECT</button>
</div>
</div>
</div>
I want disable button in button in 1st and 3rd sharing component when radio button in 2nd sharing Component is clicked. I tried DOM manipulation with document.getElementById but click function in button stopped working.
Your input component should be controlled component
In your map method you should have something like this:
<input
type="radio"
name={'radio'}
className="m-l-20 "
value={this.state.radiosValues[index]} {/* get radio value from state */}
onClick={()=>{ // We don't need event because we work with booleans
const radiosValues = this.state.radiosValues
radiosValues[index] = !radiosValues[index] // If true we want false, if false we want true
this.setState({ radioValues})
}}
/>

passing props to another Component and being pushed to variable and being displayed at same time

I don't understand this method of passing props to a Class and at the same time pushing it into a variable.
class Grid extends React.Component{
render(){
const width=(this.props.cols*14);
var rowsarr=[];
var boxclass="";
for(var i=0;i<this.props.rows;i++){
for(var j=0;j<this.props.cols;j++){
let boxid=i+"_"+j;
boxclass=this.props.gridfull[i][j]? "box on":"box off";
rowsarr.push(
<Box
boxclass={boxclass}
key={boxid}
boxid={boxid}
row={i}
col={j}
selectbox={this.props.selectbox}
/>
);
}
}
return(
<div className="grid" style={{width:width}}>
{rowsarr}
</div>
);
}
}
In the above code, I see that the props have been passed to the box component but at same it is pushed to rowsarr and rowsarr is being returned.I need an explanation as what is being done here and how this technique works in reactjs.
i guess it is used to display a list of boxes <Box>.
But is is very poorly written.
The common pattern is to iterate on this.props.cols
this.props.cols.map(col => <Box> ...)
What this code is trying to do is form a grid layout or simply, a list of rows (i) with columns (j).
For every item (Box) - row i and column j, the item is inserted into an array, rowsarr and different props are passed to it such as className etc.
Once the rows and columns are filled in the rowsarr array it is passed as the child to <div className="grid" style={{width:width}}> so that <div className="grid"> could render the rowsarr elements as its child.
That return method it will return a JSX element which is a div with multiple elements. Let's deep into it:
return(
<div className="grid" style={{width:width}}>
{rowsarr}
</div>
);
The above code it's similar to:
return(
<div className="grid" style={{width:width}}>
{[ <Box />
<Box />
<Box />
<Box />
<Box />
]}
</div>
);
Each of this Box class will have different attributes that are calculated based on the props received by Grid Component.
Each box will have different attributes. And each box is put into an array which is put inside of that div. I just asume that Box is a class that returns a div element, so the above code will be rendered as:
<div className='grid' style={{width:width}}>
<div className='box'></div>
<div className='box'></div>
<div className='box'></div>
<div className='box'></div>
<div className='box'></div>
</div>
This code creates an array of <Box> components and renders them inside a <div>. The class name and implementation do not match. This code creates a single row, not an entire grid. There is a mistake in the logic.

Categories