Reactjs, can't getelementbyid when the elem is inside a map() - javascript

I'm facing a problem with reactjs.
I need to compare screen height with a div in order to set the max-width of it.
No problem with the screen size, the problem is that the div I want to check is inside a map. Thus, this line always return null:
componentDidMount() {
test2 = document.getElementById("coucou");
console.log(test2);
}
which refers to:
render() {
return (
<div>
{this.props.data.map((value, i) => {
return (
<div key={i}>
{
this.state.activeIndex === 0 ?
<div id="coucou" style={{width: '75%', margin: '0 auto'}}>
<img
className={"image-detail"}
style={{maxWidth: '80%', borderRadius: '12px'}}
src={imgBaseUrl + value['mainImage']}
/>
</div>
}
</div>
);
}
</div>
);
}
So, I can't access the id nor the class of the div.
Any idea why? And how could I have access to those elements?
Ps: the code showing is a part of the whole, the map deals with other condition. Nevertheless, I checked it, the id is unique to this div / image.
But the problem is the same with a getElementsByClass => null

React works by working on a "virtual" DOM before rendering to the real DOM, so unless your test statements are executed after a call to ReactDOM.render, the result is to be expected, the element is still in React's virtual DOM.
Assuming you placed the test code in the correct place, in .componentDidMount for instance, have you tried logging this.props.data directly? It may simply be empty in your case.
Moreover there's also a little problem with your code since Array#map produces an array of values, there can be multiple divs with the id "coucou", and that isn't valid HTML.

If there a reason why you are not following best practice and using a ref?
return (
<div>
{this.props.data.map((value, i) => {
return (
<div key={i}>
{
this.state.activeIndex === 0 ?
<div ref={n => (this.coucouRef = n)} style={{width: '75%', margin: '0 auto'}}>
<img
className="image-detail"
style={{maxWidth: '80%', borderRadius: '12px'}}
src={imgBaseUrl + value['mainImage']}
/>
</div>
}
</div>
);
}
</div>
);
And access via this.coucouRef.
Also note that you don't need to wrap static strings in braces - className="image-detail" will do.
Also, it is bad practice to assign an array index as the key in your loop - try and use something unique to that item, such as an ID which does not change according to the order the array is in.

Related

React Prop Becomes Undefined on Re-render

so I'm currently working on learning react.js, and I've run into an issue I haven't been able to move past.
So, the broad stroke is that I have a container which is meant to render a grid of images. If you select one of the images, you'll be able to change it to another image from a list of options.
Here is the potentially relevant portion of the Grid container which is rendering fine at this moment. If the full code in context helps, you can find it here: https://codepen.io/KrisSM/pen/wvvmoqg
_onBrigadePosSelection = slot => {
console.log("This was the division selected in Brigade Grid: " + slot);
this.props.onBrigadePosSelected(slot);
};
render() {
for (let i = 0; i < 15; i++) {
//each block is a separated so that they can be rendered as different rows in the return
if (i <= 4) {
rowOne.push(
<div key={i}>
<ImageButton
btnType={"Grid"}
imageSrc={this.props.icons[this.props.brigadeDesign[i]]}
clicked={() => this._onBrigadePosSelection(i)}
/>
</div>
);
}
if (i > 4 && i <= 9) {
rowTwo.push(
<div key={i}>
<ImageButton
btnType={"Grid"}
imageSrc={this.props.icons[this.props.brigadeDesign[i]]}
clicked={() => this._onBrigadePosSelection(i)}
/>
</div>
);
}
if (i > 9 && i <= 14) {
rowThree.push(
<div key={i}>
<ImageButton
btnType={"Grid"}
imageSrc={this.props.icons[this.props.brigadeDesign[i]]}
clicked={() => this._onBrigadePosSelection(i)}
/>
</div>
);
}
}
So, when an image button is selected, it fires the onBrigadePosSelection function, which returns the selected button to the container for the grid where this function is then hit.
onBrigadePosSelected = slot => {
this.setState({ selectedDivision: slot });
console.log("This is the selected division: " + slot);
};
So, thus far, everything works. The grid renders and when a button is hit, this console log correctly states which button was hit. But this is where things start to get odd. When the state changes, their is a re-render of course, but after that re-render, selectedDivision becomes undefined.
Looking at your codes, I think the problem comes from these lines:
clicked={() => this._onBrigadePosSelection(i)}
The reason is that i will keep changing for each iteration, but () => this._onBrigadePosSelection(i) is dynamically called function.
It means that whenever you click the button, it'll get the latest value of i (or garbage or undefined) because i was garbage collected already.
I would suggest you pass the function this._onBridgePosSelection as props and call it inside the <ImageButton /> component instead.
<ImageButton
btnType={"Grid"}
imageSrc={this.props.icons[this.props.brigadeDesign[i]]}
index={i}
onBrigadePosSelection={this._onBrigadePosSelection}
/>
Then inside <ImageButton /> component, you can call it with this:
this.props.onBrigadePosSelection(this.props.index)

React Component State not working

I have been trying to implement a hover effect on a div-element like in this codesandbox:
https://codesandbox.io/s/XopkqJ5oV
The component in which I want to do this, is a reusable component that is used multiple times on the same page. I suppose that is the reason why I can't get it to work. What am I missing?
Even using the above code won't work in my application.
EDIT: Thank you for your responses. I found the issue:
I was not letting ShouldComponentUpdate know, it should take state.isHovering into account.
shouldComponentUpdate(nextProps, nextState) {
return (
nextProps.post.id !== this.props.post.id ||
nextProps.screenshotClickUrl !== this.props.screenshotClickUrl ||
nextProps.onImageClick !== this.props.onImageClick ||
nextProps.handleMouseHover !== this.props.handleMouseHover ||
nextState.isHovering !== this.state.isHovering
)
}
You're missing a this in:
toggleHoverState(state) {
return {
isHovering: !state.isHovering // Need a "this" to access state.
};
}
If you stack the elements too closely it will interfere with the mouse enter/leave events, e.g., if you space them apart:
const Foo = () => {
return (
<div>
<HoverExample />
<div style={{height: '2em', border: '1px solid blue'}} />
<HoverExample />
</div>
)
}
it work like (I think) you'd expect.
https://codesandbox.io/s/93l25m453o
I put borders around it to help visualize the issue.
If that doesn't make sense, see what happens when you have the hover indicator in an adjacent span rather than stacked:
https://codesandbox.io/s/5k5jj3rpok

Multiple elements with the same ref, React

I have a problem. It is:
let list = storage.map((element, index, array) => {
return (
<li key={index} className="list-element">
<div className="title-wrapper" onMouseEnter={this.handleMouseEnter}>
<p className="title">{array[index]['title']}</p>
<p className="title title-full" ref={node => this.title = node}>Text</p>
</div>
</li>
);
});
handleMouseEnter() {
this.title.style.opacity = "1";
}
So, when mouse enters .title-wrapper I want to set opacity to 1 on .title-full. But no matter on which .title-wrapper mouse enters, always opacity will be set to the last .title-full.
The problem is easy to solve with querySelector but I read that using it is bad thing in React, isn't it?
The reason this.title is always set to the last element is because you are setting each element in the loop to this.title, so the last one overwrites the one before it, and so on.
What about just using CSS directly, instead of handling it in React at all?
Example:
.title-wrapper:hover .title-full {
opacity: 1;
}
Just a general comment that refs aren't usually preferred in React (maybe for forms or modals sometimes). What you're emulating is a jQuery-like DOM manipulation approach, which can certainly work but is sidestepping the power of React being state-based, obvious, and easy to follow.
I would typically
this.setState({
hovered: true
})
in your handleMouseEnter method (and unset it in your mouseOut). Then choose your className based on this.state.hovered
I think going with CSS is definitely the best approach.
Just for anyone running into this issue of multiple refs in another context, you could solve the issue by storing the refs in an array
let list = storage.map((element, index, array) => {
return (
<li key={index} className="list-element">
<div className="title-wrapper" onMouseEnter={() => this.handleMouseEnter(index)}>
<p className="title">{array[index]['title']}</p>
<p className="title title-full" ref={node => this.titles[index] = node}>Text</p>
</div>
</li>
);
});
handleMouseEnter(index) {
this.titles[index].style.opacity = "1";
}
Again, you don't need to do this for your use case, just thought it might be helpful for others :D

Apply changes according to div id

I have a div mapped so it would render any number of times according to data sent from the database. And on componentDidMount i'm getting an id. I want to change background color of the div which matches the id i'm getting from componentDidMount How can i do it?
my code
componentDidMount() {
alert(this.props.voted_id);
}
render() {
let {contents, submitvote, postId, voted_id} = this.props
return (
<div className="txt_vote_bar_div" id={contents.post_poll_content_id} style={{backgroundColor: this.state.vote_status ? '#0b97c4' : 'white'}}>
<p className="txt_vote_choice" style={{color: this.state.vote_status ? '#FFFFFF' : '#6a6a6a'}}
onClick={() => {
this.handleClick(contents.post_poll_content_id);
}}> {contents.content} </p>
<p className="txt_tot_votes"
style={{color: this.state.vote_status ? '#FFFFFF' : '#6a6a6a'}}> {contents.votes}%
(Votes:)</p>
</div>
);
};
Basically what i want to do is if this.props.voted_id matches contents.post_poll_content_id , i want to change the background color of that div using states.
You can simply get the element and change its style.
componentDidMount() {
let el = document.getElementById(this.props.voted_id);
if(el) {
el.style.backgroundColor = "blue";
}
}
Update
Above approach manipulates DOM directly. It would be wise to let React handle actual DOM manipulations, unless absolute necessity.
To let React take care, you can make changes in JSX as:
<div className= {voted_id===content.post_poll_content_id ? "txt_vote_bar_div active" : "txt_vote_bar_div"} id={content.post_poll_content_id} >
/CSS
.active {
background-color:#0b97c4;
}
Basically this.props.voted_id inside componentDidMount should equal to this.props.voted_id inside render.
How about
style={{backgroundColor:voted_id===contents.post_poll_content_id ? '#0b97c4' : 'white'}}

Elements doesn't survive state change for css transitions in React

I have a highscore list that updates automatically with current scores once every minute.
It then updates the component with this.setState({ data: newData });.
Below I sort the new data by score, run each item through map that updates their style.top.
Javascript
...
let current = this.state.data.sort((a,b) => {
if(a.score < b.score) return -1;
if(a.score > b.score) return 1;
return 0;
});
let items = current.map((item,i) => {
return (
<div
key={item.name}
className="item"
style={{ top: (i*30) + 'px', backgroundColor: item.color }}>
{item.name}
</div>
);
});
return (
<div>
{items}
</div>
);
CSS
.item {
position: absolute;
transition: top 1s;
}
I want the items to animate up and down to their new positions, but this doesn't happen. The browser seems to remember parts of the DOM, but not others.
React seems to understand which item is which by using the unique key, if I inspect using React Developer Tools, but the actual DOM doesn't.
Even stranger, only items moving up the list (higher score) seems to be remembered. Items moving down is recreated, and therefor the transition doesn't work. They just pop into existence without transitioning to their new position.
Here is a working example to demonstrate. (JSFiddle)
If you change it so that the items are not reordered between render cycles, but are always rendered in the same order with different top styles, it animates correctly. I think this is because changing the order of elements forces them to be removed from the DOM and re-added by react.
The relevant code is:
render() {
let current = this.state.data.slice().sort((a,b) => {
if(a.score < b.score) return -1;
if(a.score > b.score) return 1;
return 0;
});
let items = this.state.data.map(item => {
let position = current.indexOf(item);
return (
<div
key={item.name}
className="item"
style={{ top: (position*30) + 'px', backgroundColor: item.color }}>
{item.name}
</div>
);
});
return (
<div>
{items}
</div>
);
}
Updated demo
React redraws the DOM elements you update. This will resolve in really odd behaviours, such as blinking, jumping etc.
The React team offered a couple of solutions for this before they started working on Fiber instead. With the release of Fiber I hope we'll see better support for transitions and animations.
Until that time, I recommend you use external libs for animations such as:
https://github.com/FormidableLabs/react-animations For general animation.
And https://github.com/reactjs/react-transition-group for elements entering and exiting the DOM.

Categories

Resources