I am new to react and its life-cycles, so currently following some tutorials and I am stuck with a problem that componentWillReceiveProps life-cycle method is not working the way I expect.
The thing is that in App component I am passing prop isActive to Card component, and it is changing its value when input checkbox checked/unchecked, so I expect componentWillReceiveProps life-cycle method to be triggered. However, this is not working at all. Maybe anything you can advice me on that case? As well as I am open for the best practice advice. Thank you for your time in advance.
Components code:
//App.js
import React, {Component} from 'react';
import Ticker from "./Ticker/Ticker";
import currencies from './currencies';
import Card from "./Card/Card";
import uuid from "uuid";
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
class App extends Component {
state = {
activePairs: []
};
handleCheckbox = (rateId, event) => {
const {checked} = event.target;
this.setState(({activePairs}) => {
let pairs = [...activePairs];
if (checked) {
if (!pairs.includes(rateId)) {
pairs.push(rateId);
}
} else {
let index = pairs.findIndex(rate => rate === rateId);
pairs.splice(index, 1);
}
return {
activePairs: pairs
};
});
};
render() {
return (
<div className="App">
<Ticker handleCheckbox={this.handleCheckbox.bind(this)}/>
<div className="container">
<div className="row">
{currencies.map(pair => <Card key={"card-" + uuid.v4()} currency={pair}
isActive={this.state.activePairs.includes(pair)}/>)}
</div>
</div>
</div>
);
}
}
export default App;
//Card.js
import React, {Component} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../App.css';
class Card extends Component {
state = {
value: 0
};
componentWillReceiveProps(nextProp) {
console.log(nextProp);
if (!this.props.isActive && nextProp.isActive) {
this.updateExchangeRate();
this.interval = setInterval(this.updateExchangeRate, 3000);
} else if (this.props.isActive && !nextProp.isActive) {
clearInterval(this.interval);
this.setState({
value: 0
})
}
}
updateExchangeRate = () => {
return fetch(`https://www.cbr-xml-daily.ru/daily_json.js`).then(r => r.json()).then(res => {
let exRate = res["Valute"][this.props.currency.toUpperCase()]['Value'] + (Math.random() * (0.99 - 0.01 + 1) + 0.01);
let maximum = exRate + 5.00;
let minimum = exRate - 5.00;
this.setState({
value: (Math.floor(Math.random() * (maximum - minimum + 1)) + minimum).toFixed(2)
});
});
};
render() {
return (
<div className="col-md-3 col-sm-6 mb-3">
<div className="card text-center text-white bg-info">
<div className="card-header bg-info">{this.props.currency.toUpperCase() + " to RUB"}</div>
<div className="card-body">
<h5 className="card-title">Current exchange pair:</h5>
<p className="card-text">{this.state.value}</p>
</div>
</div>
</div>
);
}
}
export default Card;
//Ticker.js
import React, {Component} from 'react';
import currencies from "../currencies";
export default class Ticker extends Component {
state = {
currencies: currencies
};
render() {
return (
<div id="wrapper">
<div id="sidebar-wrapper">
<ul id="sidebar-ul" className="sidebar-nav">
{this.state.currencies.map(currency => {
return <li key={currency}>
<input id={currency + "-input"} type="checkbox" onChange=
{
this.props.handleCheckbox.bind(this, currency)
}/>
<label htmlFor={currency + "-input"} className="text-info"
role="button"> {currency.toUpperCase()} rate</label>
</li>
})}
</ul>
</div>
</div>
);
}
}
//currencies.js
export default ["aud", "azn", "gbp", "bgn", "usd", "eur"];
Well, I finally found what was causing the problem here. In App component I was using uuid module as a key prop for every Card component, so because of it that was always rendering a new Card component each time isActive props were updating.
Solution: use a constant id instead as a key prop.
Related
I trying to display the rating of a query in my React App. But I'm not sure if I understand how to handle the state.
This is my query component:
import React, { Component, useRef, useState, useEffect } from 'react';
import { render } from 'react-dom';
import InputSearchLandlord from './search'
import './style.css'
import SimpleRating from '../components/star_display'
import ReactStars from 'react-rating-stars-component'
import './style.css'
const HandleSearch = () => {
const [ratingValue, setRating] = useState(0)
const [name, searcName] = useState("")
const nameForm = useRef(null)
const average = arr => arr.reduce( ( p, c ) => p + c, 0 ) / arr.length;
const ratings = []
const displayComment = async() => {
try {
const form = nameForm.current
const name = form['name'].value
searchName(name)
const response = await fetch(`localhost`)
const jsonData = await response.json()
getComments(jsonData)
comments.forEach(e => {
console.log(e.rating)
ratings.push(e.rating)
})
const rating = average(ratings) //Avg of all rating associated with the search
console.log(rating) //Should be pass to Rating component
setRating(rating)
} catch (error) {
console.log(error.message)
}
}
return(
<div className="container">
<div className="form-group">
<h1 className="text-center mt-5">SEARCH</h1>
<form ref={nameForm} className="mt-5">
<InputSearch name={'name'}/>
<div className="d-flex justify-content-center">
<button type="submit" className="d-flex btn btn-primary" onClick={displayComment}>Search</button>
</div>
</form>
<div>
<div className='container'>
<h1>{name}</h1>
<SimpleRating data={ratingValue}
/>
</div>
<div className='container'>
{comments.map(comment => (
<div className="commentSection">
<a>
{comment.problem}
</a><br/>
<a>
Posted on : {comment.date}
</a>
</div>
))}
</div>
</div>
</div>
</div>
)
}
export default HandleSearch;
And this is my Rating component:
import React, { useState } from 'react';
import { render } from 'react-dom';
import ReactStars from 'react-rating-stars-component'
import './style.css'
import HandleSearch from '../pages/handleSearch'
export default function SimpleRating(rating) {
const [ratingValue, setRating] = useState(0)
const options = {
value: ratingValue, //Should use the value from the Search component
a11y: true,
isHalf: true,
edit: false,
};
console.log(options.value)
if (options.value == 0) return null //if rating value = 0 doesn't display the component
return (
<div className="starComponent">
<ReactStars {...options}/>
</div>
);
}
So I trying to pass the value computed in the Search component to the Rating component. Before any query is made with the Search component, the value should be 0 and hidden.
What am I missing ?
Its to do with your props. In your parent component you create a prop called data so in your rating component you need to extract that value from props
// HandleSearch Component
<SimpleRating data={ratingValue}
export default function SimpleRating(props) {
const { data } = props
// You can also just say props.data
... rest of your component
}
Currently you are actually defining the props in your SimpleRating component but you are calling them rating (it doesn't actually matter what you call it but commonly its called props) and that is an object that contains all of the props that you pass into that component.
i have a question regarding this problem
TypeError: Cannot read property 'reverse' of undefined
inside a component in react. I try to put async call inside componentDidMount because this call is connected to another async api.
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchProducts } from "../../actions";
import M from "materialize-css/dist/js/materialize.min.js";
import { loadStripe } from "stripe";
class MembershipList extends Component {
async componentDidMount() {
const products = await this.props.fetchProducts();
var elems = document.querySelectorAll(".modal");
var instances = M.Modal.init(elems);
}
renderProducts() {
return this.props.products.reverse().map(product => {
return (
<div className="row">
<div className="col s12 offset-m2 m8 offset-l4 l4">
<div className="card pink lighten-3 z-depth-5">
<div className="card-image">
<img
src="/sub.jpg"
className="modal-trigger"
href="#modal1"
></img>
</div>
</div>
</div>
</div>
);
});
}
render() {
return (
<div>
{this.renderProducts()}
</div>
);
}
}
function mapstateToProps({ products }) {
return { products };
}
export default connect(
mapstateToProps,
{ fetchProducts }
)(MembershipList);
I need to see what you have set as an initial value for products
But it seems like on initial render you need to check if products exist i.e.
this.props.products && this.props.products.reverse().map(product => {
...
});
I'm working on a slider of items similar to Netflix but with movies, using the info recovered from the tmdb API.
I would like to add a skeleton load for each dynamically generated item using map to improve the user experience.
First of all for the skeleton loading I got a lot of inspiration from this code available on pen code that I adapted afterwards.
https://codepen.io/mxbck/pen/EvmLVp
I try a solution first here but it does not work, I voluntarily remove code that did not relate to my problem for clarity:
Slider component
import React, {PureComponent , Component } from "react";
import style from './Caroussel.css';
import MovieItem from "../components/MovieItem";
import MovieItemContainer from '../components/MovieItem/MovieItemContainer';
class Slider extends Component {
constructor(props){
super(props);
this.handleOnLeftArrowClick = this.leftArrowClick.bind(this);
this.handleOnRightArrowClick = this.rightArrowClick.bind(this);
this.state = {
sliderItems: [],
}
}
componentDidMount() {
this.updateSliderState();
this.setState({
totalItems: this.props.movieList.length,
sliderItem: this.props.movieList
})
}
componentWillMount(){
if(typeof(window) !== 'undefined') {
$(window).on('resize', this.updateSliderState.bind(this))
}
}
render(){
const { sliderItem} = this.state;
const sliderClass = cx ({
sliderMask:true,
moving
})
return(
<div className="wrapper">
<div className={style.slider}>
<div className={sliderClass} ref="slider">
{this.state.sliderItem ?
sliderItem.map((element, index) => (
<MovieItemContainer>
<MovieItem
key={index}
title={element.title}
id={element.id}
release_date={element.release_date}
url={element.backdrop_path}
/>
</MovieItemContainer>
))
:
sliderItem.map((element, index) => {
null
})
}
</div>
{
click &&
<div className={style.leftArrow} ref="leftArrow">
<IosArrowBack onClick{this.handleOnLeftArrowClick} color="black" />
</div>
}
<div className={style.rightArrow} ref="rightArrow">
<IosArrowForward onClick={this.handleOnRightArrowClick} color="black" />
</div>
</div>
</div>
);
}
}
export default Slider;
MovieItem Container component
import React, {Component} from "react";
import '../../../style/card.scss';
class MovieItemContainer extends React.Component {
render() {
return (
<div className="card">
{this.props.children}
</div>
);
}
}
export default MovieItemContainer;
MovieItem component
import React from 'react';
import Moment from 'react-moment';
import style from './MovieItem.css';
import {Link} from 'react-router';
const MovieItem = ({ url, title, release_date, id }) => {
let link ='https://image.tmdb.org/t/p/w300/'+url;
const text_truncate = (str, length, ending) => {
if (length == null) {
length = 100;
}
if (ending == null) {
ending = '...';
}
if (str.length > length) {
return str.substring(0, length - ending.length) + ending;
} else {
return str;
}
};
return (
<div className={style.sliderItem}>
<div style={{ borderBottomLeftRadius: 8, borderBottomRightRadius:8 }} className={style.sliderItemInner}>
<img style={{ borderRadius: 8 }} className={style.cover} src={link} />
<div className={style.shadow}></div>
<div className={style.titles}>
<span className={style.title}>
<Link className={style.title} to={`film/${id}`}>
{text_truncate(title,18)}
</Link>
</span>
<span className={style.release_date}>
<Moment format="YYYY">
{release_date}
</Moment>
</span>
</div>
</div>
</div>
)
}
export default MovieItem;
The MovieItem component only makes the item a movie with a background image, the title of the movie and the year of release.
In the example above, the loading is done for only one card, I would just adapt this code for each of the generated items (10 items).
I thank you in advance for your help and your answers.
I'm new to ReactJs, coding and this is my first time posting here! So, I'm trying to build a Todo app in ReactJs. I have four components.
the first compo. is App.js - the parent one
import React, { Component } from 'react';
import TaskTodo from './TaskTodo';
import './App.css';
import TaskDisplayed from "./TaskDisplayed";
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Hey, i'm the header! </h1>
</header>
<div className="App-intro">
<TaskTodo/>
</div>
<div className="App-right">
<TaskDisplayed/>
</div>
</div>
);
}
}
export default App;
TaskTodo.js - which is the parent of the TodoItems.js
import React, {Component} from 'react';
import TodoItems from './TodoItems';
export default class TaskTodo extends Component{
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItem = this.addItem.bind(this);
};
addItem(e) {
const itemArray = this.state.items;
if (this._inputElement.value !== "") {
itemArray.unshift(
{
text: this._inputElement.value,
key: Date.now()
}
);
this.setState({
items: itemArray
});
this._inputElement.value = "";
}
e.preventDefault();
}
render() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit={this.addItem}>
<input type="text" ref={(a) => this._inputElement = a}
placeholder="Add a list">
</input>
</form>
</div>
<TodoItems entries={this.state.items}/>
</div>
);
}
}
TodoItems.js - the child of the TaskTodo.js
import React, { Component } from 'react';
class TodoItems extends Component {
constructor(props) {
super(props);
this.createTasks = this.createTasks.bind(this);
}
handleClick = (text) => {
console.log(text);
}
createTasks(item) {
return <li key={item.key}><a onClick={() => this.handleClick(item.key, item.text)} href={'#about'}>#{item.text}</a></li>
}
render() {
const todoEntries = this.props.entries;
const listItems = todoEntries.map(this.createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
};
export default TodoItems;
What I need to do, is how I can pass the handleClick method (a child's of TaskTodo) to an 'external' component - TaskDisplayed.js; or how I can track when the user click to a listed item? Please pardon me for this unprofessional way of asking! But, I truly need to get in track with ReactJS! Thanks!
p.s. The above code I found online, so thanks for that :D!
You should define the onClick event handler in the parent component and pass it to the child as a prop.
See How to pass an event handler to a child component in React
In this case, you would want to define it in the App component since that is the parent of the two components that need to communicate.
I need is to display selected product details, I am using React and Redux:
After selecting one product, in my ProductDetails page should be shown details of selected product. How should I properly make it in order to get and show product details
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { asyncConnect } from 'redux-connect';
import { connect } from 'react-redux';
import * as productActions from 'redux/modules/products';
#asyncConnect([{
promise: ({ store: { dispatch, getState }, params }) => {
dispatch(productActions.load({ id: params.prodId }, getState()));
}
}])
#connect(
state => ({
products: state.products.items
}),
{
...productActions
}
)
export default class ProductDetails extends Component {
static propTypes = {
products: PropTypes.array.isRequired
};
static defaultProps = {
products: []
};
state = {
products: []
}
render() {
const { products } = this.props;
return (
<div className="container">
<div className="row">
<div className="col-md-12">
<h1>Product details</h1>
<div className="col-xs-12">
<div className="row">
<div className="col-xs-4">
<div className="panel panel-default">
<div className="panel-body">
<p>{products.name}</p>// show selected product name
<p>{products.description}<p> // show selected product description
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
I managed to solve the problem, it was very easy:
{products[0].name}
and
{products[0].description}