I'm trying to make a simple game: country flags are flashing one by one on the screen, and it stops on one particular flag after clicking on the button.
This is what I have so far:
import React from 'react'
import ReactDOM from 'react-dom'
// CSS styles in JavaScript Object
const buttonStyles = {
backgroundColor: '#61dbfb',
padding: 10,
border: 'none',
borderRadius: 5,
margin: 30,
cursor: 'pointer',
fontSize: 18,
color: 'white',
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { image: 'https://www.countryflags.io/US/shiny/64.png' }
this.makeTimer()
}
makeTimer() {
setInterval(() => {
let countries = {
USA: 'https://www.countryflags.io/US/shiny/64.png',
Australia: 'https://www.countryflags.io/AU/shiny/64.png',
"Puerto Rico": 'https://www.countryflags.io/PR/shiny/64.png'
}
let currentCountry = Math.floor(Math.random() * (Object.entries(countries).map(([key, value]) => <div>{key} <img alt={key} src={value}></img></div>)))
this.setState({ currentCountry })
}, 1000)
}
stopInterval = () => {
clearInterval(this.interval);
}
render() {
return (
<div className='app'>
<h1>where are you going on vacation?</h1>
<div>{this.state.currentCountry}</div>
<button style={buttonStyles} onClick={this.stopInterval}> choose country </button>
</div>
)
}
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
It does not work, all that renders is:
NaN
Before I added Math.floor(Math.random() * ...) it rendered all three flags at the same time, which is not what I want. Where is the mistake?
Also, I am not sure if the timer works correctly.
You can't multiply a number (Math.random) with an array (Object.entries(countries).map).
You should create a helper function to grab a single element (or value) from an object if you're storing the flags in an object.
Also, you should never ever store JSX elements in your state. All you need is a URL, not a whole image element. You can store a random URL and update the image's src if the state is updated:
const buttonStyles = {
backgroundColor: '#61dbfb',
border: 'none',
borderRadius: 5,
color: 'white',
cursor: 'pointer',
fontSize: 18,
margin: 30,
padding: 10,
};
function randomProperty(obj) {
const keys = Object.keys(obj);
return obj[keys[(keys.length * Math.random()) << 0]];
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentCountry: null,
image: 'https://flagcdn.com/w128/us.png',
};
this.makeTimer();
}
makeTimer() {
let countries = {
'Australia': 'https://flagcdn.com/w160/au.png',
'Puerto Rico': 'https://flagcdn.com/w160/pr.png',
'USA': 'https://flagcdn.com/w160/us.png',
};
this.interval = setInterval(() => {
let currentCountry = randomProperty(countries);
this.setState({ currentCountry });
}, 1000);
}
stopInterval = () => {
clearInterval(this.interval);
};
render() {
return (
<div className="app">
<h1>Where are you going on vacation?</h1>
<img alt="" src={this.state.currentCountry} width="80" />
<button style={buttonStyles} onClick={this.stopInterval}>
Choose country
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I got something different for you, I expect it helps you.
let countries = {
USA: 'https://www.countryflags.io/US/shiny/64.png',
Australia: 'https://www.countryflags.io/AU/shiny/64.png',
'Puerto Rico': 'https://www.countryflags.io/PR/shiny/64.png',
};
//I changed the object to array for better data manipulation.
const countriesArray = Object.entries(countries).map(country => {
return { name: country[0], flag: country[1] }
})
//Getting random country index by its length
const randomCountryIndex = Math.floor(Math.random() * countriesArray.length);
console.log("Set the current country: ", countriesArray[randomCountryIndex])
console.log("Set the random flag: ", countriesArray[randomCountryIndex].flag)
So after this you can check if user's answers match the current country on state
Avoid jsx in state.
Refactored your Math.random() code.
import React from 'react'
import ReactDOM from 'react-dom'
const countries = [
{ name: "USA", image: 'https://www.countryflags.io/US/shiny/64.png' },
{ name: "Australia", image: 'https://www.countryflags.io/AU/shiny/64.png'},
{ name: "Puerto Rico", image: 'https://www.countryflags.io/PR/shiny/64.png' }
];
// CSS styles in JavaScript Object
const buttonStyles = {
backgroundColor: '#61dbfb',
padding: 10,
border: 'none',
borderRadius: 5,
margin: 30,
cursor: 'pointer',
fontSize: 18,
color: 'white',
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { image: 'https://www.countryflags.io/US/shiny/64.png' }
this.makeTimer()
}
makeTimer() {
this.interval = setInterval(() => {
const countryIndex = Math.floor(Math.random() * countries.length);
this.setState({
image: countries[countryIndex].image
});
}, 1000)
}
stopInterval = () => {
clearInterval(this.interval);
}
render() {
return (
<div className='app'>
<h1>where are you going on vacation?</h1>
<div>{this.state.currentCountry}</div>
<button style={buttonStyles} onClick={this.stopInterval}> choose country </button>
</div>
)
}
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Math.floor(Math.random()) returns zero!
use this:
Math.floor(Math.random() * 10);
Related
I am pretty new to React and I am trying to build this simple web app that takes a stock tag as an input and updates the graph based on the performance of the given stock. However, I can't get my graph to update. I tried using componentDidUpdate(prevProps, prevState, snapshot), but for some reason prevProps is undefined and I don't know/understand why. I tried searching online and reading the doc file, but I still can't figure it out. Any help would be appreciated.
import Search from './Search.js'
import Graph from './Graph.js'
import Sidebar from './Sidebar.js'
import './App.css'
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [{
x: [],
close: [],
decreasing: { line: { color: '#FF0000' } },
high: [],
increasing: { line: { color: '#7CFC00' } },
line: { color: 'rgba(31,119,180,1)' },
low: [],
open: [],
type: 'candlestick',
xaxis: 'x',
yaxis: 'y'
}]
,
layout: {
width: 1500,
height: 700,
font: { color: '#fff' },
title: { text: 'Stock', xanchor: "left", x: 0 }, paper_bgcolor: '#243b55', plot_bgcolor: '#243b55', yaxis: { showgrid: true, color: '#fff' },
xaxis: {
zeroline: true, color: '#fff', showgrid: true, rangeslider: {
visible: false
}
}
},
searchfield: '',
stocktag: ' '
};
this.onSearchChange = this.onSearchChange.bind(this);
this.onSubmitSearch = this.onSubmitSearch.bind(this);
}
componentDidMount() {
document.body.style.backgroundColor = '#243b55';
this.loadGraphInfo();
}
componentDidUpdate(prevProps, prevState, snapshot){
console.log(prevProps.stocktag);
console.log(prevState.stocktag);
if (prevProps.stocktag !== prevState.stocktag) {
//this.fetchData('SPY');
}
}
onSearchChange = (event) => {
var search = event.target.value;
this.setState({ stocktag: search });
}
onSubmitSearch = (e) => {
var search = this.state.searchfield;
this.setState({ stocktag: search });
}
fetchData(stock) {
//GET DATA
//UPDATE STATE
}
loadGraphInfo() {
if (this.state.stocktag == ' ') {
this.fetchData('SPY');
} else {
this.fetchData(this.state.stocktag);
}
}
render() {
return (
<div className="App" >
<Sidebar />
<Search searchChange={this.onSearchChange} submitChange={this.onSubmitSearch} />
<Graph data={this.state.data} layout={this.state.layout} />
</div>
);
}
}
export default App;
import React, { Component } from 'react';
import './Search.css'
const Search = ({ searchChange, submitChange }) => {
return (
<div>
<div class="SearchCompInput">
<input class="SearchBar" type="text" onChange={searchChange}/>
</div>
<div class="SearchCompButton">
<button class="SearchButton" onClick={submitChange}>Search</button>
</div>
</div>
);
}
export default Search;
The prevProps.stocktag is undefined because you didn't pass any props to App component. Try this in your index.js you will see preProps value but actually it does not make any sense.
render(<App stocktag='' />, document.getElementById('root'));
componentDidUpdate(prevProps, prevState, snapshot){
console.log(prevProps.stocktag);
console.log(prevState.stocktag);
if (prevProps.stocktag !== prevState.stocktag) {
//this.fetchData('SPY');
}
}
I am not quite sure on what you are trying to accomplish here but the first thing I notice is you setState of stocktag to this.state.searchfield which is ' ' in your onSubmitSearch function.
onSearchChange = (event) => {
var search = event.target.value;
this.setState({ stocktag: search });
}
onSubmitSearch = (e) => {
var search = this.state.searchfield;
this.setState({ stocktag: search });
}
Add I will also like to add that it is good practice to set value of input to a state value like so
import React, { Component, useState } from 'react';
import './Search.css'
const Search = ({ searchChange, submitChange }) => {
const [inputValue, setInputValue] = useState('')
const handleChange = (e) => {
setInputValue(e.target.value)
searchChange(e)
}
return (
<div>
<div class="SearchCompInput">
<input class="SearchBar" type="text" value = {inputValue} onChange={handleChange}/>
</div>
<div class="SearchCompButton">
<button class="SearchButton" onClick={submitChange}>Search</button>
</div>
</div>
);
}
export default Search;
I had this problem, and it was because there was a child class that was calling super.componentDidUpdate() WITHOUT passing in the parameters. So the child class looked something like:
componentDidUpdate() {
super.componentDidUpdate();
... <-- other stuff
}
And I had to change it to:
componentDidUpdate(prevProps, prevState) {
super.componentDidUpdate(prevProps, prevState);
... <-- other stuff
}
I am trying to be able to use cntrl+s while focus within a textarea using react-hotkeys.
this.keyMap = {
KEY: "ctrl+s"
};
this.handlers = {
KEY: (e) => {
e.preventDefault();
this.saveBtn(c);
}
};
<HotKeys keyMap={this.keyMap} handlers={this.handlers}>
<textarea/>
</HotKeys>
You need to use Control+s, not ctrl+s.
You need to call configure like that so it won't ignore textareas:
import { configure } from "react-hotkeys";
configure({
ignoreTags: []
});
Following is not solution it's work around but it fulfills the requirement...
[Please Note] Basically I have restricted access to Ctrl key in browser and then it
works fine though.
import { HotKeys } from 'react-hotkeys';
import React, { PureComponent, Component } from 'react';
import { configure } from 'react-hotkeys';
const COLORS = ['green', 'purple', 'orange', 'grey', 'pink'];
const ACTION_KEY_MAP = {
KEY: 'Control+s',
};
class Login extends Component {
constructor(props, context) {
super(props, context);
this.changeColor = this.changeColor.bind(this);
configure({
ignoreTags: ['div']
});
this.state = {
colorNumber: 0
};
}
changeColor(e) {
e.preventDefault();
this.setState(({ colorNumber }) => ({ colorNumber: colorNumber === COLORS.length - 1 ? 0 : colorNumber + 1 }));
}
KeyDown(e){
if(e.ctrlKey) e.preventDefault();
}
render() {
const handlers = {
KEY: this.changeColor
};
const { colorNumber } = this.state;
const style = {
width: 200,
height: 60,
left: 20,
top: 20,
opacity: 1,
background: COLORS[colorNumber],
};
return (
<HotKeys
keyMap={ACTION_KEY_MAP}
handlers={handlers}
>
<textarea
style={style}
className="node"
tabIndex="0"
onKeyDown={this.KeyDown}
></textarea>
</HotKeys>
);
}
}
export default Login;
Every element of the array should be displayed for some time and the time for which each element is displayed should be determined by a value in each element.
let array=[{display:"a",time:10},{display:"b",time:15},{display:"c",time:22}]
class App extends React.Component{
state={stateDisplay:"",
stateTime:""
}
componentWillMount(){
var i=0;
let handle=setInterval(()=>{
var element= array[i]
this.setState({
stateDisplay:element.display,
stateTime:element.time,
})
i=i+1;
if(i===array.length){
clearInterval(handle)
}
},10000)
}
render(){
return(
<div> {this.state.stateDisplay} </div>
)}}
i have done something like this but using setinterval the delay can only be set for a constant time,here 10s.
I want the first element to display for 10s and then the next element for 15s, third for 22s which is the time value for each element of the array.
I know i cant do that using setinterval is there a way to do this using Settimeout?
This was almost like a little challenge, heres what i managed to come up with, its in typescript, if you need js, just remove interfaces and type annotations
/* eslint-disable #typescript-eslint/no-explicit-any */
/* eslint-disable prettier/prettier */
/* eslint-disable no-shadow */
/* eslint-disable no-console */
import React, { FC, useState, useEffect, useCallback } from 'react';
import { View, Button, Text } from 'react-native';
interface Data {
duration: number;
bgColor: string;
}
const dataArr: Data[] = [
{ duration: 3, bgColor: 'tomato' },
{ duration: 6, bgColor: 'skyblue' },
{ duration: 9, bgColor: 'gray' },
];
const Parent = () => {
const [currentIdx, setCurrentIdx] = useState<number>(0);
const [elementData, setElementData] = useState<Data>(dataArr[currentIdx]);
useEffect(() => {
console.log('idx', currentIdx);
if (currentIdx > dataArr.length) return;
setElementData({ ...dataArr[currentIdx] });
}, [currentIdx]);
const pushNext = () => {
setCurrentIdx(currentIdx + 1);
};
const handleRestart = () => {
setCurrentIdx(0);
setElementData({ ...dataArr[0] });
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Timer
data={elementData}
onCountDownComplete={pushNext}
restart={handleRestart}
/>
</View>
);
};
interface Props {
data: Data;
onCountDownComplete: () => void;
restart: () => void;
}
const Timer: FC<Props> = ({ data, onCountDownComplete, restart }) => {
const [seconds, setSeconds] = useState<number>(data.duration);
// update on data change
useEffect(() => {
setSeconds(data.duration);
}, [data]);
const callback = useCallback(() => {
onCountDownComplete();
}, [onCountDownComplete]);
useEffect(() => {
let interval: any = null;
if (seconds > -1) {
interval = setInterval(() => {
if (seconds - 1 === -1) {
callback();
} else {
setSeconds(seconds - 1);
}
}, 1000);
} else {
return;
}
return () => {
clearInterval(interval);
};
}, [seconds, callback]);
return (
<View
style={{ backgroundColor: data.bgColor, padding: 16, borderRadius: 10 }}
>
<Text style={{ marginBottom: 24 }}>{seconds}</Text>
<Button title="restart" onPress={restart} />
</View>
);
};
Every time I click an option of size and click add to cart I would like to add the data of the selected object to this array cart. This currently works kinda but only one object can be added and when you try to do it again the old data disappears and is replaced with the new object.
I would like to keep odd objects in the array and add new objects too. How do I go about doing this?
index.js
export class App extends Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
render() {
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
,
{
name: "size three",
price: 3
}
];
const cart = [];
const addCart = function() {
cart.push(product[evenIndex]);
if (cart.length > 0) {
}
};
console.log("cart", cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={addCart}>Add to cart</button>
</div>
);
}
}
child.js
export class Child extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
Here is my code on sandbox: https://codesandbox.io/s/14vyy31nlj
I've just modified your code to make it work. Here is the complete code. You need cart to be part of the state, so it does not initialize in each render, and to make the component render again when you add an element.
Remove the function to make it a method of the class:
addToCart() {
const selectedProduct = products[this.state.evenSelected];
this.setState({
cart: [...this.state.cart, selectedProduct]
});
}
And call it on render:
render() {
console.log("cart", this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = products[evenIndex] && products[evenIndex].price;
return (
<div>
<Child
product={products}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addToCart.bind(this)}>Add to cart</button>
</div>
);
}
}
Check that I have binded on render, which can bring performance issues in some cases. You should check this
Update
As devserkan made me notice (Thanks!), when you use the previous state to define the new state (for example adding an element to an array), it is better to use the updater function instead of passing the new object to merge:
this.setState(prevState => ({
cart: [...prevState.cart, products[selectedProduct]],
}));
For more info check the official docs.
I don't quite understand what are you trying to but with a little change here it is. I've moved product out of the components like a static variable. Also, I've changed the addCart method, set the state there without mutating the original one and keeping the old objects.
const product = [
{
name: " size one",
price: 1
},
{
name: "size two",
price: 2
},
{
name: "size three",
price: 3
}
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
evenSelected: null,
cart: [],
};
}
handleSelectL1 = i => {
this.setState({
evenSelected: i,
oldSelected: null
});
};
addCart = () => {
const evenIndex = this.state.evenSelected;
this.setState( prevState => ({
cart: [ ...prevState.cart, product[evenIndex] ],
}))
};
render() {
console.log(this.state.cart);
const evenIndex = this.state.evenSelected;
const priceShown = product[evenIndex] && product[evenIndex].price;
return (
<div>
<Child
product={product}
handleSelectL1={this.handleSelectL1}
evenIndex={evenIndex}
/>
<h2>Price:{priceShown} </h2>
<button onClick={this.addCart}>Add to cart</button>
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const { product, evenIndex } = this.props;
return (
<div>
{product.map((p, i) => {
return (
<div
key={p.id}
className={evenIndex === i ? "selectedRBox" : "selectorRBox"}
onClick={() => this.props.handleSelectL1(i)}
>
<h1 className="selectorTextL">{p.name}</h1>
</div>
);
})}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.selectorRBox {
width: 260px;
height: 29.5px;
border: 1px solid #727272;
margin-top: 18px;
}
.selectedRBox {
width: 254px;
height: 29.5px;
margin-top: 14px;
border: 4px solid pink;
}
.selectorTextL {
font-family: "Shree Devanagari 714";
color: #727272;
cursor: pointer;
font-size: 18px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Currently working on a personal project and i'm getting this error
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op. Please check the code for the Graph component.
I also get the same issue for my App component. I've done some reading on what the issue could be, but i'm non the wiser as to what the issue is with my code specifically.
Any insight would be greatly appreciated.
Here is a link to the running project (with sourcecode) on CodeSnadbox.io I've linked the offending code below as well.
Here is the Graph Component
import React, { Component } from "react";
import { render } from "react-dom";
import { Line, Doughnut, Bar } from "react-chartjs-2";
import moment from "moment";
import PropTypes from "prop-types";
import styleConstants from "../misc/style_constants.js";
class Graph extends Component {
constructor(props) {
super(props);
this.state = {
label: "default",
dataset: [],
labels: []
};
}
/**
* https://min-api.cryptocompare.com/ for documentation
*/
async getHistoryData(ticker = "BTC", currency = "USD", filter = "close") {
try {
let response = await fetch(
`https://min-api.cryptocompare.com/data/histoday?fsym=${ticker}&tsym=${currency}&limit=60&aggregate=3&e=CCCAGG`
);
const responseJson = await response.json();
const dataset = responseJson.Data.map(data => {
return data[filter];
});
const labels = responseJson.Data.map(data => {
return moment(new Date(data.time * 1000)).format("MMM Do YY");
});
this.setState({ dataset: dataset });
this.setState({ labels: labels });
} catch (error) {
console.log(error);
}
}
componentDidMount() {
const { ticker, currency, filter } = this.props;
this.getHistoryData(ticker, currency, filter);
}
render() {
const { label, graphType } = this.props;
const { dataset, labels } = this.state;
const options = {
legend: {
fontColor: styleConstants.get("Dark")
},
scales: {
yAxes: [
{
ticks: {
fontColor: styleConstants.get("Light"),
beginAtZero: true,
callback: function(value, index, values) {
if (parseInt(value) >= 1000) {
return (
"$" + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
);
} else {
return "$" + value;
}
}
}
}
],
xAxes: [
{
ticks: {
fontColor: styleConstants.get("Light"),
fontSize: 10,
stepSize: 1,
beginAtZero: true
}
}
]
}
};
const data = {
labels: labels,
datasets: [
{
label: label,
fill: true,
lineTension: 0.1,
backgroundColor: styleConstants.get("Medium"),
borderColor: styleConstants.get("Medium"),
borderCapStyle: "butt",
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: "miter",
pointBorderColor: styleConstants.get("Light"),
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: dataset
}
]
};
return <Line data={data} options={options} />;
// switch (graphType) {
// case "line":
// return <Line data={data} options={options} />;
// break;
// case "bar":
// return <Bar data={data} options={options} />;
// break;
// case "doughnut":
// return <Doughnut data={data} options={options} />;
// break;
// default:
// return null;
// }
}
}
Graph.propTypes = {
label: PropTypes.string,
graphType: PropTypes.string
};
Graph.defaultProps = {
label: "Default String",
graphType: "Default String"
};
export default Graph;
Here is the App Component also
import React, { Component } from "react";
import { render } from "react-dom";
import styled, { css } from "styled-components";
import styleConstants from "../misc/style_constants.js";
import Overview from "../components/Overview";
import Panel from "../components/Panel";
import Table from "../components/Table";
import Options from "./Options";
import Graph from "./Graph";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
selectedTicker: "BTC",
currency: "USD",
tickers: [],
overview: []
};
this.updateTicker = this.updateTicker.bind(this);
this.createGraph = this.createGraph.bind(this);
}
updateTicker(selectedValue) {
const { value } = selectedValue;
this.setState({ selectedTicker: value });
}
async getTickers() {
try {
const response = await fetch('https://api.coinmarketcap.com/v1/ticker/')
const responseJSON = await response.json();
this.setState({ tickers: responseJSON });
} catch (error) {
console.log("App getTickers() ", error);
}
}
async getOverviewData() {
try {
const response = await fetch(`https://api.coinmarketcap.com/v1/global/?convert=${this.state.currency}`)
const responseJSON = await response.json();
this.setState({ overview: responseJSON });
} catch (error) {
console.log("App getOverviewData() ", error);
}
}
componentDidMount() {
this.getTickers();
this.getOverviewData();
}
createGraph(ticker = "", currency = "", graphType = "", label = "", filter = "") {
return (
<Graph
filter={filter}
ticker={ticker}
currency={currency}
graphType={graphType}
label={label}
/>
)
}
render() {
const { selectedTicker, currency } = this.state;
const Container = styled.div`
input:focus,
select:focus,
textarea:focus,
`;
const Title = styled.h1`
text-align: center;
color: ${styleConstants.get('Yellow')};
`;
const LightSpan = styled.span`
font-weight: 200;
`;
return (
<Container>
<Title>
Coin:<LightSpan>Dash</LightSpan>
</Title>
<Overview {...this.state.overview} />
<Options
selectedValue={this.state.selectedTicker}
values={this.state.tickers.map(data => {
return data.symbol;
})}
labels={
this.state.tickers.map(data => {
return data.id;
})
}
updateTicker={this.updateTicker} />
<Panel label={"Price Action"} content={this.createGraph(selectedTicker, currency, 'line', "Close", "close")} />
<Panel label={"Highest Price"} content={this.createGraph(selectedTicker, currency, 'bar', "High", "high")} />
<Panel label={"Lowest Price"} content={this.createGraph(selectedTicker, currency, 'bar', "Low", "low")} />
<Panel label={"Top Ten List"} content={
<Table header={["Rank", "Name", "Price", "Change(24 Hour)"]} collection={this.state.tickers} />
} />
</Container>
);
}
}
The problem is caused by stateless functional components that are defined inside the render method of App. If you define the following functions outside of the App class, the error is fixed:
const Container = styled.div`
input:focus,
select:focus,
textarea:focus,
`;
const Title = styled.h1`
text-align: center;
color: ${styleConstants.get('Yellow')};
`;
const LightSpan = styled.span`
font-weight: 200;
`;
export default class App extends Component {
..
The reason for the failure is that locally created SFC's change on each render, which causes them to unmount and remount, even though the rendering stays the same. There are also some other local SFC's in the Table component, which did not create any warning, but do cause unnecessary remounting.
UPDATE: It was a bit of a puzzle, but the remaining warning came from one of the tests:
describe("App", () => {
it("renders without crashing", () => {
const div = document.createElement("div");
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
});
Which makes sense, as you unmount the component before the async action is completed.
Here's a working sandbox: https://codesandbox.io/s/24o6vp4rzp (I've also removed the arrow function in content={..}, since that needs to be a value)