dynamically render a unique button on a react component that is being used several times on a page - javascript

I'm new to react and have to make a project for my bootcamp with it and I am having trouble getting every movie component I render to have an individual button. Every time I click one button, the rest of the buttons on the page act like they are also clicked. Here is my movie component that is being called on.
Heres my first row of components and the buttons are the green ones on the bottom left corner. https://files.slack.com/files-pri/T571CRHGE-F826BKX7S/api.png.
importReact, { Component} from"react";
importAPIfrom"../utils/API"
classMovieextendsComponent{
constructor(){
super();
this.state={
color:'green',
icon:'add',
result:[]
};
}
componentDidMount() {
this.topMovies();
}
topMovies=() =>{
API.topMovies()
.then(res=>this.setState({ result:res.data.results}))
.catch(err=>console.log(err));
}
handleClick=event=>{
if(this.state.color==='green'){
this.setState({color:'red'});
} else{
this.setState({color:'green'});
}
if(this.state.icon==='add') {
this.setState({icon:'remove'});
} else{
this.setState({icon:'add'});
}
}
render() {
constimgURL="https://image.tmdb.org/t/p/w300/"
return(
<div>
{
this.state.result.map((movieList) =>(
<div className="col s4 movieBox">
<div className="card">
<div className="card-image">
<img src={imgURL +movieList.poster_path} />
<span className="card-title"><a id={this.state.color} onClick={this.handleClick} className="btn-floating btn waves-effect waves-light"><i className="material-icons">{this.state.icon}</i></a></span>
</div>
<div className="card-content movieInfo">
<p>Title:{movieList.title}</p>
<p>Genre:{movieList.genre_ids}</p>
<p>Rating:{movieList.vote_average}</p>
</div>
</div>
</div>
))
}
</div>
)
}
}
exportdefaultMovie;

You need to bind the handleClick function ( in fact all functions ) inside the constructor:
constructor(){
super();
this.state={
color:'green',
icon:'add',
result:[]
};
this.handleClick = this.handleClick.bind(this);
}

onClick={()=> this.handleClick()} will also work.

.map() calls a provided callback for each element in your array. So this means that you are creating several buttons that will execute the same event on each of your elements. I suggest creating a row component that handles your onClickEvent and then you could pass the component an id or use an in line arrow function () => this.handleClick(). (this does create a anonymous function on each click and could potentially be bad for performance in larger apps but could work in your case if you don't want to create a row component)
Also you should read up on why using keys with unique ids is important.
https://coderwall.com/p/jdybeq/the-importance-of-component-keys-in-react-js
Hope that helps.

Related

How to return an HTML <div> tag from a javascript function in React?

I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate

event.stopPropagation() fails on ReactJS Component inside a native HTMLElement

My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';

Set dynamic state name in React.js

I am starting my adventure with React so it is a hard time for me, however I prepared such pen for you to test. Here is a portion of code:
class App extends React.Component {
constructor() {
super();
this.state = {
settings: true,
next: false,
};
}
toggler(abc) {
console.log(">>", abc)
this.setState({
next: !this.state.next
/* {abc}: this.state.{abc} */
})
console.log(this.state.next)
}
render() {
return (
<div className="kalreg">
<MyButton name='settings' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={this.state.settings} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={this.state.next} type="next" toggle={this.toggler.bind(this)}/>
</div>)
}
}
class MyButton extends React.Component {
constructor(props) {
super(props);
}
onChangeName(){
console.log(this.props.type)
if ( this.props.isActive ) { console.log("this one is active"); } else { console.log("ouch! it is not active, ignoring!"); return;}
this.props.toggle(this.props.type);
}
render () {
if ( this.props.isActive ) {
return ( <div className="button notVisible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
} else {
return ( <div className="button visible" onClick={this.onChangeName.bind(this)}>{this.props.name}</div>)
}
}
}
ReactDOM.render(<App />, document.getElementById("app"));
What I am trying to achieve is that when i press one of "settings" buttons (yellow) the "next" button becomes unclickable (green). There is a toggle function that every time I click settings button it turns on and off "next" button.
It works quite good, however it is just a draft of bigger project and i want to automate it a little bit.
As you can see I create my <MyButton> with both "isActive" and "type" props. But isActive holds what's inside this.state.settings while type is "settings". Instead of using two variables it would be great to pass only type of button to its component and component, depending on its type would check its parent's this.state.{type}. I used {type} because i would like to check it dynamically. Is that possible?
If so - how to do it?
My first attempt is to pass type from <MyButton> to <App> via toggler function. I named the variable "abc". I commented the way I wanted to do it because it doesn't work:
{abc}: !this.state.{abc}
Any idea to solve this problem would be more than appreciated.
Kalreg.
It is somewhat unclear what you are trying to achieve here. If you want to wire the state dynamically based on type, as you wrote in code: {abc}: !this.state.{abc} each button would toggle itself, not the next button. In this case your syntax is a little incorrect, it will work if you write it like:
[abc]: !this.state[abc]
However as I said, in your example, this makes the settings button change the state for this.state.settings disabling itself instead of the next button.
Another note would be, that if it is not necessary for the MyButton component to know its own type for other reasons, it is unnecessary to pass it as a prop and than make the component pass it back as an argument (this.props.toggle(this.props.type);). You can simply define the toggle function in the parent as:
toggle={() => this.toggler("settings")}
without passing type as a prop.
So basically we want to have the settings and settings2 buttons, and when we click on them, they toggle the state of the next button by making it un-clickable (green).
So if that is our goal, then
we don't need an isActive prop for the settings button. (Because it's always going to be active no matter what)
We also don't need to have a toggle prop on the Next button. (Because clicking the next button isn't supposed to toggle anything)
Instead of having two variables in the state why not just have one and then use that to determine the isActive prop of the next button?
The component would look like this:
constructor() {
super();
this.state = {
nextIsActive: false,
};
}
toggler() {
this.setState({
nextIsActive: !this.state.nextIsActive
})
console.log(this.state);
}
render() {
const {nextIsActive} = this.state
return (
<div className="kalreg">
<MyButton name='settings' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='settings2' isActive={true} type="settings" toggle={this.toggler.bind(this)}/>
<MyButton name='next' isActive={nextIsActive}/>
</div>
)
}
That way you don't have to have 2 state properties that you have to dynamically update because it adds more complexity to your application.
You can see the finished product here: Codepen

Bind click to a div and get attribute data in React

There are multiple divs on a page. What I want to do is get an attribute when a div is clicked. It is very easy in jquery but I am using Reactjs. Here is my code. It works but every time when I click on div attribute of the last element in is returned. Following is my code and the codepen URL.
https://codepen.io/anon/pen/gepVNP?editors=0010
class Content extends React.Component{
constructor(props) {
super(props)
this.click = this.click.bind(this)
}
click(){
// this.prop.setActiveMenu();
var summary = this.refs.summary;
console.log(summary.getAttribute('data-slug'))
}
render(){
return(
<div className="content">
{posts.map((post)=>{
return (
<div ref="summary" data-slug={post.slug} onClick={this.click} key={post.slug}>
<h1>{post.title}</h1>
<div>{post.content}</div>
<br/><br/><br/><br/><br/>
</div>
);
})}
</div>
);
}
}
That's because you're changing the ref element inside the map everytime you iterate on the posts array.
No need for refs here IMO.
Why not use the event.target from the click event?
onClick(event){
console.log(event.target.getAttribute('data-slug'))
}
BTW:
String refs are considered legacy. have a look here:
Why ref='string' is "legacy"?
I discourage you from using this approach, you could use a component instead.
The ref prop accepts a function and return the ref, so passing a string doesn't work.
class Content extends React.Component{
constructor(props) {
super(props)
this.click = this.click.bind(this)
}
click(){
console.log(this.summary.getAttribute('data-slug'))
}
render(){
return(
<div className="content">
{posts.map((post)=>{
return (
<div ref={ref => this.summary = ref} data-slug={post.slug} onClick={this.click} key={post.slug}>
<h1>{post.title}</h1>
<div>{post.content}</div>
<br/><br/><br/><br/><br/>
</div>
);
})}
</div>
);
}
}

Rendering Component in Carousel using React

I want to achieve a carousel like Materialize.
Have an API from where I am fetching the data, so according to Materialize
I compared the console or Materialize default and my rendered components.
I guess the problem is, it's not inheriting the properties of carousel-item
Class carousel-item is supposed to Render inside of Class carousel.
<div className="carousel">
// These are supposed to be dynamic, below component is not present here
<div className="carousel-item">
</div>
</div>
How I am trying to render the data is in this manner.
renderAlbums(){
return this.state.albums.map(album =>
<Card song={album.name} singer={album.artist_name} src={album.cover_photo_url}/>
);
}
Rendered the data <Card />(It contains the class of carousel-item), which is supposed to place Card containing class of carousel-item.
class Carousel extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://cors-anywhere.herokuapp.com/https://stg-resque.hakuapp.com/albums.json')
.then(response => this.setState({albums: response.data}));
}
renderAlbums(){
return this.state.albums.map(album =>
<div className="carousel-item"><Card key={album.name} song={album.name} singer={album.artist_name} src={album.cover_photo_url}/></div>
);
}
render() {
return (
<div className="carousel center">
{this.renderAlbums()}
</div>
);
}
}
export default Carousel;
This is my Card component
class Card extends Component {
render() {
return (
<div className="card z-depth-4">
<div>
<img src={this.props.src} />
</div>
<p>{this.props.song}</p>
<div className="singer">{this.props.singer}</div>
</div>
);
}
}
export default Card;
EDIT:
Want that content to display like this.
But it's not working the way it's expected.
Please suggest me, what am I doing wrong?
In axios.get, I see that you are using proxy link.
One reason is, it can be creating problems.
Other reason can be you are trying to put carousel-item into carousel.
Try adding center class to both i.e. carousel as well as carousel-item.
Check if these works.
First of all, there is nothing in your Carousel that says which element is active. You need to have a state variable that points to the active element.
Then you only need to draw [-2, -1, 0, 1, 2] offsets vs the active one. And each rendered card needs to know which offset to know their style.

Categories