Attach ToggleClass events to future elements in ReactJS - reactjs

I have multiple Div elements rendered dynamically through a loop.
Am trying to,
Attach an onClick event to the dynamically rendered elements.
Toggle the class through the onClick function.
The class name has to be toggled only to the span element that is clicked.
But the problem with the below code is, the class gets toggled to all the span elements rendered through this loop.
But I need to toggle the class only to the particular element that is clicked.
Though not the exact/complete code, the basic structure looks like this.,
constructor(props) {
super(props);
this.state = {
classnm: 'collapsed',
};
}
func2 = event => {
let currentClass = this.state.classnm;
if(currentClass == 'collapsed') {
this.setState({classnm: 'expanded'});
}
else {
this.setState({classnm: 'collapsed'});
}
};
render() {
return (
<div>
<Iterate>{this.renderIndexColumn}</Iterate>
</div>
);
}
// The below function gets called at least 10 times, rendering the below span element at least 10 times.
renderIndexColumn = () => {
return (
<div>
<span id={data.RefId} className = {this.state.classnm} onClick={(event) => {this.func2(event)}}>
<i className="material-icons">arrow_right</i></span>
</div>
);
};
If it is JQuery, I would have used ' on', 'this' to target the particular element & toggleClass.
Am not sure how to do the same in React.
Can someone help to achieve this? Thanks.

You don't need to set className to this.state.classnm. Just do this instead:
<span id={data.RefId} className = 'collapsed' ... />
And then your func2 should be something like:
func2 = event => {
event.target.classList.toggle('collapsed')
};

Related

React: document.getElementById (or onClick) does not work

I want a random number to be generated and put into the "roomid" input field once the "generate" button is clicked, but the value shown is still 0 after I clicked the button. It guess it is not the problem of onClick because there was indeed a "?" added to the URL after I clicked the button. What is wrong with the code? Thank you so much for your help]1
React way of doing something like this would be
class StartStream extends Component {
state = { randomId: 0 }
generate = () => {
this.setState({ randomId: Math.random() * 1000 });
}
render() {
return <div>
<input type="text" value={this.state.randomId} />
<button onClick={this.generate}>Generate</button>
</div>;
}
}
Here is a working demo https://jsfiddle.net/tfc3javx/1/

(React) How to match the modal content with the clicked element?

I'm trying to match the modal that shows with the clicked element, right now i'm rendering all the modals with the click, I been trying to make them match with the index but no luck , please help.
here are my constructors
constructor(props) {
super(props);
this.state = {
portfolioData: [],
newModal:[],
modalPost: false,
isShown: false
};
}
showModal = (i) =>{
this.setState({ isShown: true, modalPost: true })
}
closeModal = () => {
this.setState({isShown:false, modalPost: false})
}
and here I get the data and render two list component and the modal
componentDidMount() {
axios.get(`data.json`)
.then(res => {
const portfolioData = [res.data.portfolio.projects.film];
this.setState({ portfolioData });
})
};
the components
const portfolioList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<PortfolioItem
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
showModal={this.showModal}
youtube={val.trailer}
/>
))
const modalList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<Modal
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
closeModal={this.closeModal}
youtube={val.trailer}
/>
))
and the return
<section id="portfolio">
{ portfolioList }
{ this.state.modalPost !== false ? modalList : null }
</section>
Your code will make as many modal as the data held by this.state.portfolioData, which is unnecessary, inefficient and may result into wasted rendering. If you take a step back and think from this way, You are going to render only one modal but render it with the data of the selected item
Lets see an example,
We can start by having an additional state value selectedValue which will hold the clicked item's value
this.state = {
portfolioData: [],
newModal:[],
modalPost: false,
isShown: false,
selectedValue: {} //<---
};
Then, when the user clicks the item we can set that particular items value in the state; specifically in selectedValue
const portfolioList = this.state.portfolioData.map((value) =>
value.map((val, idx) =>
<PortfolioItem
id={val.title.en.toString().toLowerCase().split(" ").join("-")}
title={val.title.en}
imgsrc={val.imgsrc}
status={val.status}
profile={val.profile}
director={val.director}
production={val.production}
showData={() => this.showData(val)} //<---
youtube={val.trailer}
/>
))
//new function for one specific task <---
const showData = (value) => {
this.setState({selectedValue: value}, this.showModal)
}
Finally, instead of mapping over the data you can render only one modal which takes and show the data from the this.state.selectedValue
<Modal
id={this.state.selectedValue.title.en.toString().toLowerCase().split(" ").join("-")}
title={this.state.selectedValue.title.en}
imgsrc={this.state.selectedValue.imgsrc}
status={this.state.selectedValue.status}
profile={this.state.selectedValue.profile}
director={this.state.selectedValue.director}
production={this.state.selectedValue.production}
closeModal={this.closeModal}
youtube={this.state.selectedValue.trailer}
/>
This is merely an idea you can follow. You should organize/optimize your code later per your codebase afterwards. If you are unfamiliar with setStates second parameter it takes a callback as a second parameter which it executes after updating state. Reference: https://reactjs.org/docs/react-component.html#setstate
Hope this helps you, Cheers!
Edit: I just noticed you are not using that isShown anywhere. That is the value that the modal should be opened based on. The modal should have a prop which makes it show/hide by passing true/false, check the documentation. And you should pass this.state.isShown to that specific prop to make everything come together and work!
The problem is you are creating multiple instances of modals when you are looping over your portfolioData. I dont think you need the multiple instances since you will open only one at a time.
When you are setting the state of modal to isShown. You are actually setting state on all the instances of the generated modals. Hence you end up opening multiple modals. You should ideally have just one modal and pass data to your modal.
You can do this:
First, Move the modal out of the loop so that you have only one instance of it.
Pass data to:
<a>onClick={() => this.toggleModal(provider.data)} key={index}>Portfolio Item</a>
Lastly, in toggleModal function first set the data then open modal.
This way all your PortfolioItem links will end up calling the same modal instance. But with different data. Once you set the data you can rerender your component with the already existing isShown state.
Here is a small example:
This way all your PortfolioItem links will end up calling the same modal instance. But with different data. Once you set the data you can rerender your component with the already existing isShown state.
Here is a small example:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
isShown: false,
data: ''
}
this.list = [{data: 'data1'}, {data: 'data2'}];
}
onAnchorClick({data},event){
this.setState({data, isShown: true});
}
render(){
return(
<div>
{this.list.map((obj, idx) => <div key={idx}>
<a onClick={this.onAnchorClick.bind(this, obj)}>Portfolio Item</a>
</div>)}
<div style={{display: !this.state.isShown ? 'none' : 'block'}}>
<h3>Data: {this.state.data}</h3>
</div>
</div>
)
}
}
window.onload = () => {
ReactDOM.render(<App />, document.getElementById("app"));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Show data on buttonclick

I am very new to react ,Trying to build a app . The basic functionality i am trying to implement is
I will have some text ,with a button called show details ,clicking the button will show details regarding the text .
I have the text as well as the details saved as a object array in state
Watcher_list :[
{
id:uuid.v4(),
name : "Memory",
showdetails : "False"
},
{
id:uuid.v4(),
name :"Network",
showdetails : "False"
}
]
for the component to render the page the code is as follows
class Watchers extends Component
{
showdetails(id)
{
this.props.showonclick(id);
}
render() {
if (this.props.item.showdetails ==="True") {
return (
<div className="Watchers" >
<li>{this.props.item.name}</li>
<p1>{this.props.item.id} </p1>
<button ref="show details" onClick={this.showdetails.bind(this,this.props.item.id)} >
Show Details
</button>
</div>
);}
else {
return (
<div className="Watchers" >
<li>{this.props.item.name}</li>
<button ref="show details" onClick={this.showdetails.bind(this,this.props.item.id)} >
Show Details
</button>
</div>
);
}
}
}
export default Watchers;
the handler for show click just updates the value of showdetails in the state ,ad upon re rendering the details are displayed.
I just wanted to know wether this is the best way to do this ,or is there a much smarter way that I can do the same thing ?
There are a couple of things that you can improve on,
First: you have a lot of redundant code that can be avoided by using conditional rendering like
class Watchers extends Component {
showdetails(id){
this.props.showonclick(id);
}
render() {
return (
<div className="Watchers" >
<li>{this.props.item.name}</li>
{this.props.item.showdetails ==="True" ? <p1>{this.props.item.id} </p1> : null }
<button ref={(ref) => this.showDetails = ref} onClick =
{this.showdetails.bind(this,this.props.item.id)} > Show Details </button>
</div>
);
}
}
export default Watchers;
Second: bind inside render is not a good pattern since it creates a new function everytime render is called. Check this answer on how to avoid it.
Third: I don't see any point on why you are using ref looking at the code in your question, but even if you need it, you need to use ref callback like ref={(ref) => this.showDetails = ref} instead of string refs since string refs are a legacy
You need not bind inside render function.
No need to set ref
For a change in ID, you do not require separate conditions. You can use the short circuit operator.
class Watchers extends Component {
constructor(props) {
super(props);
this.showdetails = this.showdetails.bind(this);
}
showdetails(id) {
this.props.showonclick(id);
}
render() {
return (
<div className = "Watchers">
<li> {this.props.item.name}</li>
{
this.props.item.showdetails === "True" &&
<p> {this.props.item.id} < /p>
}
<button onClick = {this.showdetails}>Show Details</button>
</div>
);
}
}
export default Watchers;
If you are setting state locally, you should probably set showDetails to Boolean true instead of a String like you have provided.

React onChange not working as intended

I'm trying to create a products component that get's all the products available on the website and displays each of them in sort of like a box and when the user clicks that box they get redirected to that product page. I'm using react and redux and I'm having a difficulty with onClick. This is how my code looks
class Products extends Component{
constructor(){
super();
this.state = {
products: [...Some array]
};
}
handleProductRedirect(productNumber){
console.log(productNumber)
// Redux function
// this.props.handleRedirect(productNumber)
}
render(){
var products = this.state.products
return (
<div id="content">
{product &&
<div id="searchContent">
{product.map(element => <Item element={element}
handleProductRedirect={this.handleProductRedirect.bind(this)}
key={element['productNumber']}/>)}
</div>
}
</div>
</div>
)
}
};
class Item extends Component{
render(){
var element = this.props.element;
return (
<div id="itemBox" onClick={this.props.handleProductRedirect(element['productNumber'])}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
)
}
}
so the component gets the products from an api and once it's get them it iterates through them. However, I noticed using chromes developer console that every that it iterates through every <Item /> component it calls handleProductRedirect even though that Item wasn't clicked on. It does it automatically. Instead of calling that function when the div itemBox is clicked on, it calls it when it's rendered. Any suggestions
That's because you are calling the handleProductRedirect on every render for each item. Instead of that, you need send the callback in the onClick prop, something like this:
class Item extends Component{
onClickItem = () => { // <=== Defines the callback and bind it to the instance
const { element } = this.props;
this.props.handleProductRedirect(element['productNumber']);
};
render(){
var element = this.props.element;
return (
<div id="itemBox" onClick={this.onClickItem}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
)
}
}
This way you are not calling the callback on every render, but when the user actually clicks element.
Also, don't forget to define the propTypes on your components, it really helps to catch issues later on.
Your onClick is calling the function here:
onClick={this.props.handleProductRedirect(element['productNumber'])}>
Instead you should return a function that calls it with the argument. You can do that by making an arrow function like this:
onClick={() => this.props.handleProductRedirect(element['productNumber'])}>
But the best way to do it is to extract it into a class method (so that you don't get unnecessary re-renders):
class Item extends Component {
clickProduct = () => {
this.props.handleProductRedirect(this.props.element['productNumber']);
}
render() {
var element = this.props.element;
return (
<div id="itemBox" onClick={this.clickProduct}>
<h3>{elementTitle.slice(0, 85)}</h3>
<p>{element.Manufacturer}</p>
</div>
);
}
}

ReactJS - Showing some elements and a button to show more

I'm using React, with the Flux architecture.
In a certain part of my app, I need to show some elements (let's say, 6) and, if there's more, a button that will display the rest of them when clicked.
As I'm using Flux, I want my components to be as stateless as possible. So far I've tried:
let elements = [...] //Array with elements, passed as a prop to MyApp
class MyApp extends React.Component {
...
render(){
var elements = this.props.elements.map((elem, i) => {
if (i == 5)
return <More />
if (i > 5)
return;
return (
<ShowElement key={i} />
)
return <div>{elements}</div>
}
}
I think I could make this work by passing the remaining elements to my More component as prop, but it feels wrong. Is there a better way to do this?
As far as showing 6 items and rest when button is clicked, this is way to do it using state:
let elements = [...] //Array with elements, passed as a prop to MyApp
class MyApp extends React.Component {
getInitialState() {
return {
all: false
};
},
render(){
return <div>{this._renderElements(this.props.elements, this.state.all)}</div>
},
_renderElements(elements, showAll) {
const _elementsToRender = [];
const _elements = elements.splice(0); // clone array
let remainder = [];
if (!showAll) {
remainder = _elements.splice(6);
}
_elements.forEach(el => {
_elementsToRender.push(
<ShowElement {..el} />
);
});
if (remainder.length > 0) {
_elementsToRender.push(
<button onClick={evt => this.setState({ all: true })}>show all</button>
);
}
return (
<div>
{_elementsToRender}
</div>
);
}
}
And if you want to keep your component stateless you could pass something like showAll prop and make <button> (or whatever custom element you use) trigger action that will change showAll value in store and emit new value so component updates that way.
Wrap your more elements in a span or div with a style={moreSty}. The default for moreSty is display: 'none'. The click method for the more button will set the moreSty to display: 'inline' or 'block'.

Resources