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,
Related
I am developing a rating system using ReactJS.
my code is as follows -
This is the essential code in my ReviewCard Component.
This basically returns a div where the review, username,and rating would be displayed.
starfunc(){
document.querySelector('.stars-inner').style.width = starPercentageRounded;
}
render(){
<h1 className="rating">Rating : {review.Rating}
<div class="stars-outer">
<div class="stars-inner" ></div>
</div>
</h1>
}
This is my Reviews Component
It recieves reviews from DB, and for each review, maps in to ReviewCard Component
const reviewColumns = reviews ? reviews.map(review => (
<ReviewCard review={review} styles={{backgroundColor:"black"},{padding:"5px"}} />
)) : null;
The issue is that in my ReviewCard, the document.querySelector('.stars-inner') always takes the first instance of occurence. This is resulting in the only first review getting changed everytime.
Is there a way to keep the id or classname variable or unique? Or are there any other approaches i should follow?
Here is the full code
ReviewCard
class ReviewCardComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.starfunc();
}
starfunc() {
var {review} = this.props;
var rating = review.Rating
if(rating>5){
rating = rating/2;
}
al = 5;
const starPercentage = (rating / starTotal) * 100;
const starPercentageRounded = `${(Math.round(starPercentage / 10) * 10)}%`;
document.querySelector(id).style.width = starPercentageRounded;
}
render() {
console.log("reviewcard",this.props);
const {review} = this.props;
// The CardTitle.subtitle won't render if it's null
console.log("qwer",review.review_id);
return (
<div className="main">
<div className="fline">
<h1 className="name">{review.user_name}</h1>
<h1 className="rating">Rating : {review.Rating}
<div class="stars-outer">
<div class="stars-inner" id={review.user_name}></div>
</div>
</h1>
<button className="DeleteOptions">Options</button>
</div>
<h2 className="reviewtext">{review.review}</h2>
<hr/>
</div>
);
}
}
its CSS file
.stars-outer {
display: inline-block;
position: relative;
font-family: FontAwesome;
}
.stars-outer::before {
content: "\f006 \f006 \f006 \f006 \f006";
}
.stars-inner {
position: absolute;
top: 0;
left: 0;
white-space: nowrap;
overflow: hidden;
width: 0;
}
.stars-inner::before {
content: "\f005 \f005 \f005 \f005 \f005";
color: #f8ce0b;
}
In your case, I think the <Review /> component would look like this:
Please note that I've changed your starfunc function to arrow function to utilize this
Have a percentage state in each <Review /> component, by default it will be 0
class ReviewCardComponent extends React.Component {
state = {
percentage: 0,
}
componentDidMount() {
this.starfunc();
}
starfunc = () => {
const { review } = this.props;
const rating = review.Rating;
const starTotal = 5;
if (rating > 5) {
rating = rating / 2;
}
const starPercentage = (rating / starTotal) * 100;
const starPercentageRounded = Math.round(starPercentage / 10) * 10;
this.setState({
percentage: starPercentageRounded || 0
})
}
render() {
const { review } = this.props;
const { percentage } = this.state;
// The CardTitle.subtitle won't render if it's null
console.log("qwer", review.review_id);
return (
<div className="main">
<div className="fline">
<h1 className="name">{review.user_name}</h1>
<h1 className="rating">
Rating : {review.Rating}
<div class="stars-outer">
<div class="stars-inner" id={review.user_name} style={{width: `${percentage}%`}}></div>
</div>
</h1>
<button className="DeleteOptions">Options</button>
</div>
<h2 className="reviewtext">{review.review}</h2>
<hr />
</div>
);
}
}
Your approach is wrong :) You should not use querySelector as this accesses the real DOM element and not the React virtualDOM element; The approach is to use the React Refs system :
In your ReviewCard Component in the constructor put this.review = React.createRef();
In the div with class "stars-inner" put ref={this.review} as a prop;
In your componentDidMount() life cycle method use this.review.current.style.width to set your styles
PS: Where is your return for the JSX inside the render() for your ReviewCard Component ?
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>
)
}
}
I am making a file sharing web app using a MERN development stack.
While connecting my file sharing screen to my updating screen, I got stuck.
ComponentsWillReceiveProps() is not working in the current version of React. I tried to find an alternative and it showed either set
UNSAFE_ComponentsWillReceiveProps() or use the function static getDerivedStateFromProps(nextProps, prevState), but I don't know how to define prevState.
My code is:
import React,{Component} from 'react'
import _ from "lodash"
import PropTypes from 'prop-types'
class HomeUploading extends Component{
constructor(props) {
super(props);
this.state = {
data: null,
event: null,
loaded: 0,
total: 0,
percentage: 10,
}
}
componentDidMount() {
const {data} = this.props;
this.setState({
data: data
});
}
static getDerivedStateFromProps(nextProps,prevState){
const {event} = nextProps;
console.log("Getting an event of uploading", event,prevState);
switch (_.get(event, 'type')) {
case 'onUploadProgress' :
const loaded = _.get(event, 'payload.loaded', 0);
const total = _.get(event, 'payload.total', 0);
const percentage = (total !== 0) ? ((loaded / total) * 100) : 0;
this.setState ({
loaded: loaded,
total: total,
percentage: percentage
});
break;
default:
break;
}
this.setState({
event:event,
}) ;
}
render() {
const {percentage, data, total, loaded} = this.state;
const totalFiles = _.get(data, 'files', []).length;
return (
<div className={'app-card app-card-uploading'}>
<div className={'app-card-content'}>
<div className={'app-card-content-inner'}>
<div className={'app-home-uploading'}>
<div className={'app-home-uploading-icon'}>
<i className={'icon-cloud-upload'}/>
<h2>Sending...</h2>
</div>
<div className={'app-upload-files-total'}>Uploading {totalFiles} files</div>
<div className={'app-progress'}>
<span style={{width: `${percentage}%`}} className={'app-progress-bar'}/>
</div>
<div className={'app-upload-stats'}>
<div className={'app-upload-stats-left'}>{loaded}Bytes/{total}Bytes</div>
<div className={'app-upload-stats-right'}>456K/s</div>
</div>
<div className={'app-form-actions'}>
<button className={'app-upload-cancel-button app-button'} type={'button'}>Cancel
</button>
</div>
</div>
</div>
</div>
</div>
)
}
}
HomeUploading.propTypes={
data: PropTypes.object,
event: PropTypes.object
}
export default HomeUploading;
Just glancing at the code, I'd refactor this a bit to keep this component as simple as possible (dumb/presentational component) that just displays new props as they're fed in.
Here's a description :https://medium.com/#thejasonfile/dumb-components-and-smart-components-e7b33a698d43
I'd suggest lifting the state up a level and doing the data transformation in the parent.
Something like this for the HomeUploading:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const HomeUploading = ({
data,
event,
loaded,
total,
percentage,
}) => (
<div className={'app-card app-card-uploading'}>
<div className={'app-card-content'}>
<div className={'app-card-content-inner'}>
<div className={'app-home-uploading'}>
<div className={'app-home-uploading-icon'}>
<i className={'icon-cloud-upload'} />
<h2>Sending...</h2>
</div>
<div className={'app-upload-files-total'}>Uploading {data.length} files</div>
<div className={'app-progress'}>
<span style={{ width: `${percentage}%` }} className={'app-progress-bar'} />
</div>
<div className={'app-upload-stats'}>
<div className={'app-upload-stats-left'}>
{loaded}Bytes/{total}Bytes
</div>
<div className={'app-upload-stats-right'}>456K/s</div>
</div>
<div className={'app-form-actions'}>
<button className={'app-upload-cancel-button app-button'} type={'button'}>
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
);
HomeUploading.propTypes = {
data: PropTypes.object,
event: PropTypes.object,
};
export default HomeUploading;
And then in the parent, you could have a the logic for transforming the data in a function:
import React, { Component } from 'react';
class Parent extends Component {
state = {
data: null,
event: null,
loaded: 0,
total: 0,
percentage: 0,
}
calcPercentage(loaded, total) {
return (total !== 0) ? ((loaded / total) * 100) : 0
}
render() {
const {
data,
event,
loaded,
total
} from this.state;
return (
<HomeUploading
data={data}
event={event}
loaded={loaded}
total={total}
percentage={this.calcPercentage(loaded, total)}
/>
);
}
}
export default Parent;
This approach will give you the props updating without relying on componentWillReceiveProps.
Im trying to change the img src getting the src from and array based on the direction of arrow that is clicked in react.js.
So for example I have an array when a user clicks on the right arrow it will change the img src forward and if she clicks backs it will go back the prev image
here is my code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
var i = 0;
var blogPostImages = ['./img/placeholder.jpg', './img/placeholderB.jpg', './img/placeholderC.png'];
export default class HomePage extends Component {
changeBlogPicForwards() {
if (i == blogPostImages.length - 1) {
i = 0;
} else {
i = i + 1;
}
let blogPostImages = blogPostImages[i];
}
changeBlogPicBackwards() {
if (i == 0) {
i = blogPostImages.length - 1;
} else {
i = i - 1;
}
}
render() {
var blogCurrentPic = this.state.blogPostImages[i];
return (
<div>
<div className="top-section-actions">
<div className="image-holder">
<img className="blog-pictures" src={blogPostImages}/>
</div>
<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>
)
}
}
I keep getting an error any help will be much appreciated.
You need to maintain i in the state so that you can signal react to re-render the page when the state changes using setState. Also, the src should be blogCurrentPic
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">
<div className="image-holder">
<img className="blog-pictures" src={blogCurrentPic}/>
</div>
<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>
)
}
}
use setState({'i':i})to refresh your state, setState will make component reRender. it will solve your question
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 }/>