react: strange behaviour on hover - javascript

I'm new to React and making a small 'Tweet box' app for practice. There are some strange things which I don't understand...
User can add images to tweets. Images are stored as objects in an array and rendered via looping and running renderThumb() method. On image hover I show a small 'remove icon' in top right of image. When clicked the image is removed.
When I hover an image, ALL images show is hovered (icon is visible on all images). Why?
renderThumb() is executed on hover. Why? (It should only be executed when rendering images).
I use this.state.images.filter( (img) => { return img.id != image.id; } ); to remove image. But it does not work. Why?
// Thanks,
Ole
TweetBox.js
import React, {Component, PropTypes} from 'react';
import './tweetbox.css';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import TextArea from './TextArea';
class TweetBox extends Component {
numPhotoChars = 17;
numStartChars = 140;
author = 'Ole Frank Jensen';
counter = 0;
imageUrl = 'http://i.imgur.com/Crcz7dJ.jpg';
constructor(props) {
super(props);
this.state = {
text: '',
author: this.author,
date: 0,
startValue: this.numStartChars,
images: []
};
this.onTextareaChange = this.onTextareaChange.bind(this);
this.getNumRemainingChars = this.getNumRemainingChars.bind(this);
this.disableTextarea = this.disableTextarea.bind(this);
this.addImage = this.addImage.bind(this);
this.removeImage = this.removeImage.bind(this);
this.submitTweet = this.submitTweet.bind(this);
this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
}
onTextareaChange(e) {
this.setState({text: e.target.value});
}
getNumRemainingChars() {
return this.state.startValue - this.state.text.length;
}
disableTextarea() {
return this.state.text.length > 0 || this.state.images.length;
}
addImage() {
if (this.getNumRemainingChars() >= this.numPhotoChars) {
const startValue = this.state.startValue - this.numPhotoChars;
const image = Object.assign({}, {
id: this.counter += 1,
url: this.imageUrl
});
this.setState({startValue: startValue});
this.setState({images: [...this.state.images, image]});
}
}
removeImage(image) {
let arr = this.state.images.filter( function(img) { return img.id != image.id; } );
console.log(image, arr);
this.setState({
images: this.state.images.filter( function(img) { return img.id != image.id; } ),
startValue: this.state.startValue + this.numPhotoChars,
hover: false
});
}
submitTweet(e) {
e.preventDefault();
// compose
const tweet = {
text: this.state.text,
author: this.state.author,
date: new Date().getTime(),
images: this.state.images
};
// store
this.props.storeTweet(tweet);
// reset
this.setState({
text: '',
images: [],
startValue: this.numStartChars
});
}
onMouseOver() { console.log('over'); this.setState({hover: true}); }
onMouseOut() { console.log('out'); this.setState({hover: false}); }
renderThumb(image, index) {
console.log(index);
let k = 'image-' + index;
return (
<div key={k} className="col-xs-3">
<div className="thumbnail-wrapper">
<img src={image.url}
alt="My something"
className="img-thumbnail"
onMouseOver={ this.onMouseOver }
onMouseOut={ this.onMouseOut }/>
<div className={"img-thumbnail-close-btn " + (this.state.hover ? 'show' : 'hidden')}
onMouseOver={ this.onMouseOver }
onMouseOut={ this.onMouseOut }
onClick={ () => { this.removeImage({image}) } }>
<span className="fa-stack fa-1x">
<i className="fa fa-circle fa-stack-2x white-background"></i>
<i className="fa fa-circle-thin fa-stack-2x black-border"></i>
<i className="fa fa-times fa-stack-1x"></i>
</span>
</div>
</div>
</div>
);
}
render() {
return (
<div className="tweet-box">
<div className="row">
<div className="col-xs-6 col-xs-offset-3">
<div className="well tweet-box clearfix">
<h1>Tweet Box</h1>
<TextArea value={ this.state.text }
maxLength={this.state.startValue}
onChange={this.onTextareaChange}/>
<span className="pull-right">
<em>{this.getNumRemainingChars()} characters left...</em>
</span>
<br/>
<div className="row">
<ReactCSSTransitionGroup transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}>
{
this.state.images.map((image, index) => {
return this.renderThumb(image, index);
})
}
</ReactCSSTransitionGroup>
</div>
<br/>
<div className="row">
<div className="col-xs-6">
<button className="btn btn-default add-photo pull-left"
onClick={this.addImage}>
<i className="fa fa-camera"></i> Add photo
</button>
</div>
<div className="col-xs-6">
<button onClick={this.submitTweet} className="btn btn-primary pull-right"
disabled={!this.disableTextarea()}>
<i className="fa fa-pencil-square-o"></i> Tweet!
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
TweetBox.propTypes = {
storeTweet: PropTypes.func.isRequired
};
export default TweetBox;

Your state is designed to be general, id suggest you to add id on each image. and add a handler that accepts that id. that way you can change the hover of specific image only.
this.setState({hover: true})
you can see below that you are adding the same handler to all the images,
so one image hovered will result to them all being hovered.
<img src={image.url}
alt="My something"
className="img-thumbnail"
onMouseOver={ this.onMouseOver }
onMouseOut={ this.onMouseOut }/>

Related

Using useEffect like ComponentDidUpdate

I am trying to have 3 buttons where if one is in an active state, the other 2 will automatically be inactive.
if (isActive === "true") {
setActive2("false")
setActive3("false")
}
if (isActive2 === "true") {
setActive("false")
setActive3("false")
}
if (isActive3 === "true") {
setActive("false")
setActive2("false")
}
I'm aware there's probably a better way of doing this and this is a brute force option, and I'm open to your suggestions.
I have tried putting this block of code in a function and running it whenever the buttons are clicked, but that is giving me the previous state instead of the current state.
So I was suggested to use the useEffect hook.
useEffect(() => {
if (isActive === "true") {
setActive2("false")
setActive3("false")
}
if (isActive2 === "true") {
setActive("false")
setActive3("false")
}
if (isActive3 === "true") {
setActive("false")
setActive2("false")
}
}, [isActive, isActive2, isActive3]);
However this is giving me the same issue, where the previous state is being applied.
I am for sure doing something very wrong with this hook (i have never used it before).
I have a codesandbox with all my code here
Have modified only the onChange handler in an efficient way without touching JSX much and have worked on your set-up only. CodeSandBox Link Checkbox-Selection
Some Major changes that I did are as follows:
Instead of setting seperate state to each button, I have used a single object with 3 keys isActive, isActive2 and isActive3.
const [btnStatus, setBtnStatus] = useState({
isActive: true,
isActive2: true,
isActive3: true
});
Your Handler looks something like this now.
const addPizza = (e) => {
setPizzaSize(e.target.name);
setStartPrice(parseInt(e.target.value));
const currentActive = e.target.id;
if (currentActive === "isActive") {
setBtnStatus({ isActive: true, isActive2: false, isActive3: false });
console.log("1");
}
if (currentActive === "isActive2") {
setBtnStatus({ isActive: false, isActive2: true, isActive3: false });
console.log("2");
}
if (currentActive === "isActive3") {
setBtnStatus({ isActive: false, isActive2: false, isActive3: true });
console.log("3");
}
console.log(btnStatus);
};
In your JSX each button will look like this, with own ids to track the status of button.
<button
name="Extra Large"
className={
btnStatus.isActive3
? "button btn fourth"
: "button btn fourthActive"
}
value="20"
onClick={addPizza}
id="isActive3"
>
Extra large
</button>
And here you go. All working nicely with the same code :)
I have update the code a little bit, you can create seprate constants and use them to reduce the code and also, to keep the active state use a single state only.
https://codesandbox.io/s/gracious-franklin-m8wkx?file=/src/CYO.js:0-4147
import React, { useState, useEffect } from "react";
import ButtonClickable from "./button";
import ButtonClickable2 from "./button2";
import { burgerSize, vegToppings, nonvegToppings } from "./const/size";
import "./index.css";
const CYO = () => {
const [pizzaSize, setPizzaSize] = useState("Choose your Pizza Size");
const [activeSize, setActiveSize] = useState(burgerSize.MEDIUM);
const [toppings, setToppings] = useState([]);
const [startPrice, setStartPrice] = useState(0);
const addPizza = (e) => {
setPizzaSize(e.target.name);
setStartPrice(parseInt(e.target.value));
};
const CheckSize = () => {
if (pizzaSize === "Choose your Pizza Size") {
alert("You must choose a pizza size");
} else if (toppings.length === 0) {
alert("Are you sure you don't want toppings?");
} else {
alert("Sorry, this isn't a real pizza place.");
}
};
const ToppingPlusMinus = (e) => {
const { value } = e.target;
const position = toppings.indexOf(value);
if (position !== -1) {
return removeTopping(value);
}
return addTopping(value);
};
const removeTopping = (value) => {
// We need to filter out the value from the array and return the expected new value
setToppings(toppings.filter((topping) => topping !== value));
//handleToggle();
};
const addTopping = (value) => {
setToppings([...toppings, value]);
// handleToggle();
};
let toppingPrice = toppings.length * 1.5;
let price = startPrice + toppingPrice;
return (
<div className="container CYO">
<h2 className="text-center white">Create your own pizza</h2>
<div className="row">
<div className="col-sm-8">
<div className="">
<img
src="./pizza.png"
className="img-fluid pizza"
alt="Pizza"
></img>
</div>
<h3 className="white">{pizzaSize}</h3>
<p className="white">
Your Toppings: <br />
<div className="col-lg-12">
{toppings
.filter((x) => x.name !== "")
.map((toppings) => (
<img
src={toppings}
alt="topping"
width="100px"
height="100px"
></img>
))}
</div>{" "}
</p>
</div>
<div className="col-sm-4">
<h3 className="white">Pizza size</h3>
{Object.values(burgerSize).map((value) => (
<button
name={value}
className={
activeSize !== value
? "button btn fourth"
: "button btn fourthActive"
}
value="10"
onClick={(event) => {
addPizza(event);
setActiveSize(value);
}}
>
{value}
</button>
))}
<br />
<h3 className="white">Toppings</h3>
<p className="white">Toppings are $1.50 each</p>
<div className="topping-wrapper">
<h4 className="white">Meats</h4>
{nonvegToppings.map(({ name, image }) => (
<ButtonClickable
onClick={(event) => {
ToppingPlusMinus(event);
}}
name={name}
value={image}
/>
))}
<h4 className="white">Veggies</h4>
{vegToppings.map(({ name, image }) => (
<ButtonClickable2
onClick={(event) => {
ToppingPlusMinus(event);
}}
name={name}
value={image}
/>
))}
</div>
</div>
<div className="pricefooter">
<p className="price">Price: ${price}</p>
</div>
<div className="pricefooter2">
<button className="checkout button btn fourth" onClick={CheckSize}>
Checkout
</button>
</div>
</div>
</div>
);
};
export default CYO;

Vue.js: Vue-Carousel jumping to last item in array on click

I am currently working on an application that involves scrolling through members from two different lists. Once you click one you see their image and bio. We added up and down arrows to scroll through their biography, but when i click the down arrow it seems that the active class on the carousel slide now jumps to the last member in my array, but still displays the member i was already on. To add to that I cannot swipe back and forth once i am on a member (this is something that I could do previously.) I know this probably isn't the best description and there is only so much code I can show, as I cannot display the entire application.
I wanted to also note that if I refresh my page, everything works as expected. Does this mean something isn't initializing correctly?
this is the code specific to the page i am talking about:
<template>
<div class="member-info-carousel">
<div class="header">
<h2 v-if="founderChairman === true">Member List One</h2>
<h2 v-else>Member List Two</h2>
<img src="../assets/logo.png" alt="Logo" />
</div>
<carousel :minSwipeDistance="384" :perPage="1" :paginationEnabled="false" :navigationEnabled="true" navigationNextLabel="<i>NEXT</i>"
navigationPrevLabel="<i>BACK</i>" :navigateTo="selectedListIndex" #pagechange="OnPageChange">
<slide v-for="(member, index) in selectedList" :key="index">
<div class="member-bio-page" :member="member" v-on:showContent="showContent">
<div class="bio">
<div class="portrait-image">
<img :src="member.imgSrc" />
</div>
<div class="bio-container">
<div class="inner-scroll" v-bind:style="{top: scrollVar + 'px'}">
<div class="english"></div>
<div class="pin-name">
<img :src="member.pin" />
<h1>{{ member.name }}</h1>
</div>
<div class="description-container">
<div class="para">
<p class="quote" v-html="member.quote"></p>
<p v-html="member.bio"></p>
<div class="spanish"></div>
<p class="quote" v-html="member.spanishQuote"></p>
<p v-html="member.spanishBio"></p>
</div>
</div>
</div>
</div>
<div class="scroll-buttons">
<div>
<!-- set the class of active is the scroll variable is less than 0-->
<img class="btn-scroll" v-bind:class="{ 'active': scrollVar < 0 }" #click="scrollUp" src="#/assets/arrow-up.png">
</div>
<div>
<!-- set the class of active is the scroll variable is greater than the height of the scrollable inner container-->
<img class="btn-scroll" v-bind:class="{ 'active': scrollVar > pageChangeHeight }" #click="scrollDown" src="#/assets/arrow-down.png">
</div>
</div>
<div class="eng-span">
English
Español
</div>
</div>
<div class="play-button">
<!-- if the array members has a property of video, then the play button will show on the slide. If not it will not show the image -->
<img v-if="member.hasOwnProperty('video')" #click="showContent" src="#/assets/play-button.png">
</div>
</div>
<!-- <MemberBioPage :member="member" v-on:showContent="showContent"/> -->
</slide>
</carousel>
<modal name="video-modal"
:width="1706"
:height="960">
<video width="1706" height="960" :src="(selectedList && selectedList[this.currentPage]) ? selectedList[this.currentPage].video : ''" autoplay />
</modal>
<div class="footer-controls">
<div class="footer-bar">
<p>Tap Back or Next to view additional profiles.</p>
<p>Tap the arrows to scroll text up or down.</p>
</div>
<div class="nav-container">
<img class="nav-bubble" src="#/assets/navigation-bubble-bio-page.png" alt="An image where the back, next and close button sit" />
</div>
<button class="close-button" #click="closeInfo">
<img src="#/assets/x-close-button.png" />
CLOSE
</button>
</div>
</div>
</template>
<script>
import { Carousel, Slide } from 'vue-carousel'
export default {
data () {
return {
currentPage: 0,
pageChangeHeight: -10,
scrollVar: 0
}
},
components: {
// MemberBioPage,
Carousel,
Slide
},
mounted () {
this.enableArrows()
},
updated () {
this.enableArrows()
},
computed: {
selectedList () {
return this.$store.state.selectedList
},
selectedListIndex () {
return this.$store.state.selectedListIndex
},
founderChairman () {
return this.$store.state.founderChairman
}
},
methods: {
enableArrows () {
var outerHeight
var innerHeight
if (document.querySelectorAll('.VueCarousel-slide-active').length > 0) {
outerHeight = document.querySelectorAll('.VueCarousel-slide-active .bio-container')[0].clientHeight
innerHeight = document.querySelectorAll('.VueCarousel-slide-active .inner-scroll')[0].clientHeight
} else {
outerHeight = document.querySelectorAll('.VueCarousel-slide .bio-container')[0].clientHeight
innerHeight = document.querySelectorAll('.VueCarousel-slide .inner-scroll')[0].clientHeight
}
this.pageChangeHeight = outerHeight - innerHeight
return this.pageChangeHeight
},
scrollUp () {
this.scrollVar += 40
console.log(this.scrollVar += 40)
},
scrollDown () {
this.scrollVar -= 40
console.log(this.scrollVar)
},
OnPageChange (newPageIndex) {
this.scrollVar = 0
this.currentPage = newPageIndex
this.pageChangeHeight = -10
},
closeInfo () {
if (this.$store.state.selectedList === this.$store.state.foundersList) {
this.$store.commit('setSelectedState', this.$store.state.foundersList)
this.$router.push({ name: 'Carousel' })
} else if (this.$store.state.selectedList === this.$store.state.chairmanList) {
this.$store.commit('setSelectedState', this.$store.state.chairmanList)
this.$router.push({ name: 'Carousel' })
}
},
showContent () {
this.$modal.show('video-modal')
},
toEnglish () {
this.scrollVar = 0
},
toSpanish () {
var spanishPos
if (document.querySelectorAll('.VueCarousel-slide-active').length > 0) {
spanishPos = document.querySelectorAll('.VueCarousel-slide-active .spanish')[0].offsetTop
} else {
spanishPos = document.querySelectorAll('.VueCarousel-slide .spanish')[0].offsetTop
}
this.scrollVar = -spanishPos
}
}
}
</script>
This is my store index file:
import Vue from 'vue'
import Vuex from 'vuex'
import chairmans from '#/data/chairmans-club'
import founders from '#/data/founders-circle'
import memoriam from '#/data/in-memoriam'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
finishedLoading: false,
transitioning: false,
foundersList: founders,
chairmanList: chairmans,
selectedList: founders,
selectedListIndex: -1,
founderChairman: true,
inMemoriam: memoriam,
idleTimer: {
id: null,
duration: 1
},
idleTimeoutModal: {
show: false,
duration: 1
}
},
mutations: {
setSelectedState (state, list) {
state.selectedList = list
},
setSelectedListIndex (state, idx) {
state.selectedListIndex = idx
},
showIdleTimeoutModal (state, value) {
state.idleTimeoutModal.show = value
},
founderChairmanClicked (state, data) {
state.founderChairman = data
},
setInMemoriam (state, content) {
state.inMemoriam = content
}
},
actions: {
restartIdleTimer ({ state, commit }) {
clearTimeout(state.idleTimer.id)
state.idleTimer.id = setTimeout(() => {
commit('showIdleTimeoutModal', true)
}, state.idleTimer.duration * 1000)
},
stopIdleTimer ({ state }) {
clearTimeout(state.idleTimer.id)
}
}
})
export default store
and an example of the data i am pulling:
const event = [
{
index: 0,
name: 'Member Name',
carouselImage: require('#/assets/carousel-images/image.jpg'),
drawerImage: require('#/assets/drawer-images/drawerimage.jpg'),
imgSrc: require('#/assets/bio-images/bioimage.jpg'),
quote: '“quote.”',
spanishQuote: `“spanish quote.”`,
bio: '<p>bio copy here</p>',
spanishBio: '<p>spanish bio copy</p>',
pin: require('#/assets/pin.png')
}
]
export default event

ReactJS rendering issue with edited array

Why does ReactJS remove the last element when the array is different after removing the middle element when using array.splice?
This is my code. I am using React-Redux.
const reducerNotesAndLogin = (state = initialState, action) => {
var tableNotes = "notities";
var tableCategories = "categories";
switch(action.type){
case "CATEGORY_REMOVE":
// Remove the category
var newCategories = state.categories;
console.log("state.categories", state.categories);
console.log("before: ", {newCategories});
var index = 0;
for(var i = 0; i < newCategories.length; i++){
if(newCategories[i].id === action.payload.categoryId){
newCategories.splice(i, 1);
index = i;
i--;
}
}
console.log("after: ", {newCategories});
state = {
...state,
categories: newCategories
}
break;
default:
break;
}
return state;
}
export default reducerNotesAndLogin;
Output below (I deleted the middle element. My web app always removes the last element of the categories (but not from the array).
Step 1: Initial state
Step 2: Remove middle item, expecting the middle item to be removed.
Step 3: Confusion
Why is the array correct, but the view incorrect? I am updating the state.categories correctly right?
This is my render code (as is - without filtering away any other code that mihgt be important)
CategoriesBody:
import React from 'react';
import { connect } from 'react-redux';
import CategoryItem from './CategoryItem';
import Button from './../../Button';
import store from '../../../redux/store-index';
class CategoriesBody extends React.Component {
render(){
return (
<div>
<ul className="list--notes">
{this.props.categories.map((category) => {
if(category.id === undefined){ // No categories
return <li>No categories</li>
} else {
return (
<div>
<CategoryItem category={category} />
<div className="mb-small hidden-sm hidden-md hidden-lg"> </div>
</div>
);
}
})}
</ul>
</div>
);
}
}
function mapStateToProps(state){
return {
categories: state.reducerNotesAndLogin.categories,
categoriesLength: state.reducerNotesAndLogin.categories.length
};
}
export default connect(mapStateToProps)(CategoriesBody);
CategoriesItem.js:
import React from 'react';
import store from './../../../redux/store-index';
import Button from './../../Button';
class CategoryItem extends React.Component {
constructor(props){
super();
this.state = {
edit: false,
categoryName: props.category.categoryName,
categoryColor: props.category.categoryColor
}
this.onClickEdit = this.onClickEdit.bind(this);
this.onChangeCategoryColor = this.onChangeCategoryColor.bind(this);
this.onChangeInputCategoryName = this.onChangeInputCategoryName.bind(this);
this.onClickEditSave = this.onClickEditSave.bind(this);
this.onClickEditCancel = this.onClickEditCancel.bind(this);
}
removeCategory(id, name){
console.log("nsvbsvbfjvbdjhbvv");
store.dispatch({ type: "CATEGORY_REMOVE", payload: {
categoryId: id
}});
// store.dispatch({type: "NOTIFY", payload: {
// type: 'success',
// message: 'Category "' + name + '" removed!'
// }});
}
onClickEdit(){
this.setState({
edit: true
});
}
onChangeCategoryColor(e){
this.setState({
categoryColor: e.target.value
});
}
onChangeInputCategoryName(e){
this.setState({
categoryName: e.target.value
});
}
onClickEditSave(){
this.setState({
edit: false,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
});
store.dispatch({type: "CATEGORY_EDIT", payload: {
categoryId: this.props.category.id,
categoryName: this.state.categoryName,
categoryColor: this.state.categoryColor
}});
store.dispatch({type: "NOTIFY", payload: {
type: "success",
message: "Category saved!"
}});
}
onClickEditCancel(){
this.setState({
edit: false,
categoryName: this.props.category.categoryName,
categoryColor: this.props.category.categoryColor
});
}
render(){
return (
<li key={this.props.category.id} className={this.state.edit === true ? "mt mb" : "flex-justify-between flex-align-center"}>
<div className={this.state.edit === true ? "d-none" : ""}>
<div className="input--color" style={{
backgroundColor: this.state.categoryColor
}}> </div>
{this.state.categoryName}
</div>
{/* Mobile */}
<div className={this.state.edit === true ? "d-none" : "hidden-sm hidden-md hidden-lg"}>
<Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button>
<div className="mt-small"> </div>
<Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button>
</div>
{/* Tablet and desktop */}
<div className={this.state.edit === true ? "d-none" : "hidden-xs"}>
<div style={{float:'left',}}><Button onClick={() => this.onClickEdit()} buttonType="primary">Edit</Button></div>
<div style={{float:'left',marginLeft:'15px'}}><Button onClick={() => this.removeCategory(this.props.category.id, this.props.category.categoryName)} type="primary">Remove</Button></div>
</div>
{/* EDITING STATE */}
<div className={this.state.edit === true ? "" : "d-none"}>
<div className="row">
<div className="col-xs-12">
<input onChange={this.onChangeCategoryColor} className="input--wide" type="color" value={this.state.categoryColor}
style={{backgroundColor: this.state.categoryColor, height: '30px'}}
/>
<input onChange={this.onChangeInputCategoryName} className="input--wide" type="text" value={this.state.categoryName} />
</div>
</div>
<div className="row mt">
<div className="col-xs-12">
<Button buttonType="primary" onClick={() => this.onClickEditSave()}>Save</Button>
</div>
</div>
<div className="row mt-small">
<div className="col-xs-12">
<Button buttonType="secondary" onClick={() => this.onClickEditCancel()}>Cancel</Button>
</div>
</div>
</div>
</li>
)
}
}
export default CategoryItem;
I think it has something to do with the rendering. Because the arrays are correct when I console.log them. Only the view is different...
Do not modify the state in reducer directly. Create a copy of state value and then modify it.
Change:
var newCategories = state.categories;
To:
var newCategories = [...state.categories];
You should not modify the same array while looping through it.
for (var i = 0; i < newCategories.length; i++) {
if (newCategories[i].id === action.payload.categoryId) {
newCategories.splice(i, 1);
index = i;
i--;
}
}
I got the answer after looking through it with a friend of mine. The solution is pretty simple...
Lesson 101: Make sure that you have a unique "key" property when looping through an array in your UI.
The solution is to add this to my code:
<div key={category.id}>
{this.props.categories.map....
...
</div>

React JS removeEventListener not firing

I have two functions with one containing addEventListener("click") and the other to remove
show() {
console.log("SHOW FUNCTION")
this.setState({
listVisible: true
})
document.addEventListener("click", this.hide.bind(this));
}
hide() {
console.log("HIDE FUNCTION")
this.setState({
listVisible: false
})
document.removeEventListener("click", this.hide.bind(this));
}
On initial click, show() is fired, state is updated as expected and once the second click is made hide() is fired and all is well.
When I click again ( third click ) my console.logs list show() first and then two logs for hide() thus setting my state back to false when it should be true, just like the first sequence.
I'm unsure as to why this is happening, it's almost as if the removeEventListener isn't firing. Could also be wrong context of "this"?
Here is my component code:
renderListItems() {
const items = [];
for (var i = 0; i < this.props.list.length; i++) {
const item = this.props.list[i];
items.push(<div className="option" onClick={() => this.select(item)}>
<span>{item.name}</span>
<i className="fa fa-check"></i>
</div>);
}
return items;
}
render() {
console.log("give me", this.state.listVisible)
return (
<div className={"dropdown-container" + (this.state.listVisible ? " show" : "")}>
<div className={"dropdown-display" + (this.state.listVisible ? " clicked": "")} onClick={() => this.show()}>
<span>
{this.state.selected.name}
</span>
<i className="fa fa-angle-down"></i>
</div>
<div className="dropdown-list">
<div>
{this.renderListItems()}
</div>
</div>
</div>
)
}
Every time you call .bind it is creating a new function. You should create the bound function once when the component is created so that the removeEventListener can find the function.
You can bind class methods in ES6:
hide = () => {
}
Or do it in your constructor:
constructor(...args) {
super(...args);
this.hide = this.hide.bind(this);
}
Then your event listener can simply refer to the already bound function this.hide.
I found a solution - to remove the hide function, remove all binds and update state within the select function.
import React, {Component} from 'react'
import classNames from 'classnames';
import './style.scss'
export default class FormCustomSelect extends Component {
constructor(props) {
super(props);
this.state = {
listVisible: false,
display: "",
selected: this.props.selected
}
}
select(item) {
this.setState({
selected: item,
listVisible: false
})
}
show() {
this.setState({
listVisible: true
})
}
renderListItems() {
const items = [];
for (var i = 0; i < this.props.list.length; i++) {
const item = this.props.list[i];
items.push(<div className="option" onClick={() => this.select(item)}>
<span>{item.name}</span>
<i className="fa fa-check"></i>
</div>);
}
return items;
}
render() {
console.log("give me", this.state.listVisible)
return (
<div className={"dropdown-container" + (this.state.listVisible ? " show" : "")}>
<div className={"dropdown-display" + (this.state.listVisible ? " clicked": "")} onClick={() => this.show()}>
<span>
{this.state.selected.name}
</span>
<i className="fa fa-angle-down"></i>
</div>
<div className="dropdown-list">
<div>
{this.renderListItems()}
</div>
</div>
</div>
)
}
}

react animation on img src change from an array

I have this react code that changes the img src onClick, it gets the new img src from an array, I now want to add an animation to it using react-motion, Everytime the img src changes I want to play the animation so far I got it working threw a ? statement however it only plays every other time it changes I think its very similar to what Im doing. here is the code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
export default class HomePage extends Component {
constructor() {
this.state = { index : 0 };
this.blogPostImages = ['./img/placeholder.jpg', './img/placeholderB.jpg', './img/placeholderC.png'];
}
changeBlogPicForwards() {
let i = this.state.index;
if (i == this.blogPostImages.length - 1) {
i = 0;
} else {
i = i + 1;
}
this.setState({index: i});
}
changeBlogPicBackwards() {
let i = this.state.index;
if (i == 0) {
i = this.blogPostImages.length - 1;
} else {
i = i - 1;
}
this.setState({index: i});
}
render() {
var blogCurrentPic = this.blogPostImages[this.state.index];
return (
<div>
<div className="top-section-actions">
<Motion style={{ x: spring( this.state.index ? 1:0 ) }}>
{({ x }) =>
<div className="image-holder">
<img className="blog-pictures" src={blogCurrentPic} style={{
WebkitTransform: `translate3d(${x}px, 0, 0)`,
transform: `scale(${x}, ${x})`,
}} />
</div>
}
</Motion>
<div className="blog-name">
<div className="left-arrow-action arrow-icons">
<i onClick={(e) => this.changeBlogPicForwards(e)} className="fa fa-arrow-circle-o-left fa-2x" aria-hidden="true"></i>
</div>
<div className="right-arrow-action arrow-icons">
<i onClick={(e) => this.changeBlogPicBackwards(e)} className="fa fa-arrow-circle-o-right fa-2x" aria-hidden="true"></i>
</div>
</div>
</div>
</div>
)
}
}
The type of animation I want does not matter since Im just trying to play any animation after the img src changes,

Categories