componentDidUpdate(prevProps, prevState, snapshot): prevProps is undefined - javascript

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
}

Related

react-hotkeys cntrl+s while focus is in textarea

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;

I want to use loop to make radio button as many as in list

I want to make sets of buttons as many as the number of rooms
So I used a map to make radio button sets
If it works there should be 4 sets of buttons.
but it doesn't work
please help
import React, { Component } from "react";
import { Text, View } from "react-native";
import RadioForm, {
RadioButton,
RadioButtonInput,
RadioButtonLabel
} from "react-native-simple-radio-button";
const temp_data = {
room1: 0.1,
room2: 0.2,
room3: 0.3,
room4: 0.4
};
var radio_props = [{ label: "10%", value: 0 }, { label: "30%", value: 1 }];
export default class TaskSetting extends Component {
render() {
var tem1 = Object.keys(temp_data).map(num => {
return;
<RadioForm
radio_props={radio_props}
initial={0}
onPress={value => {
this.setState({ value: value });
}}
/>;
});
return <View>{tem1}</View>;
}
}
If you use => {}, you need to add return <YourComponent /> inside {},
or you can choose not to use {} and directly => <YourComponent />
export default class TaskSetting extends Component {
render() {
return (
<View>
{Object.keys(temp_data).map(num =>
<RadioForm
radio_props={radio_props}
initial={0}
onPress={value => {
this.setState({ value: value });
}}
/>
)}
</View>
)
}
}
Check it online:

Why does Axios keep sending requests before component mounts?

I have an app with React front and Spring backend. I use Axios to fetch from the back. I have 2 class components with tables and I can access them via a menu component (in componentDidMount and componentDidUpdate only). I use all the possible precautions against infinite loops (loaded state and isMounted with a custom name). It works in the first component which I access after logging in. However, the second component (which is logically the same as the first, just has another entity to fetch) keeps requesting with axios until i go there (i see it in the network tab of my browser). Why can it be? it is definitely not mounted and console.logs don't work from there but while I'm on first it keeps requesting on and on (and it doesn't receive anything I guess, it is 0 bytes at this time)
import React, { Component } from 'react'
import {Link} from 'react-router-dom';
import axios from 'axios'
import "react-table/react-table.css";
import ReactTable from 'react-table';
import {Button, ButtonToolbar} from 'react-bootstrap';
import { LinkContainer } from "react-router-bootstrap";
import AddCalculationsModal from './AddCalculationsModal';
import UpdateCalculationsModal from './UpdateCalculationsModal';
import Cluster from './Cluster';
import Select from 'react-select/src/Select';
export default class Calculations extends Component {
isCMounted = false;
constructor(props) {
super(props)
this.state = {
items: [],
selected: null,
addModalShow: false,
updateModalShow: false,
updateId: null,
buttonOn: false,
page: 0,
elements: 0,
loaded: false
}
}
componentDidMount() {
this.isCMounted = true;
if(!this.state.loaded){
this.load();
}
};
componentDidUpdate() {
if(!this.state.loaded){
this.load();
}
};
componentWillUnmount(){
this.isCMounted = false;
}
increasePage = () => {
this.setState({
page: this.state.page + 1
})
}
decreasePage = () => {
this.setState({
page: this.state.page - 1
})
}
load = async () => {
await axios.get(`calculations?page=${this.state.page}&elements=${this.state.elements}`)
.then(res => {
if (this.isCMounted && this.state.items.id === res.data.id){
this.setState({items: res.data})
}
});
if(this.state.selected != null && this.isCMounted) {
this.setState({buttonOn: true})
}
this.setState({loaded: true})
}
setId = (id) => {
const idValue = this.state.items[id].id;
if (this.isCMounted)
this.setState({updateId: idValue});
}
deleteRow = (id) => {
const index = this.state.items.findIndex(item => {
return item.id === this.state.items[id].id})
const idValue = this.state.items[id].id
axios.delete(`calculations/${idValue}`).then(
res => {
this.load();
}
)
this.state.items.splice(index, 1)
this.load();
}
render() {
let addModalClose = () => this.setState({addModalShow: false});
let updateModalClose = () => this.setState({updateModalShow: false});
return (
<div>
<h3>Calculations</h3>
<ReactTable
columns={
[
{
Header: "ID",
accessor: "id"
},
{
Header: "Name",
accessor: "name"
},
{
Header: "Creation Date",
accessor: "dateCreate"
},
{
Header: "Update Date",
accessor: "dateUpdate"
},
{
Header: "User",
accessor: "userId"
}
]
}
data={this.state.items}
filterable
showPagination={false}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
this.setState({
selected: rowInfo.index
})
},
style: {
background: rowInfo.index === this.state.selected ? '#00afec' : 'white',
color: rowInfo.index === this.state.selected ? 'white' : 'black'
}
}
}else{
return {}
}
}}
>
</ReactTable>
<ButtonToolbar>
<Button variant="primary" onClick={() => {
this.decreasePage();
this.load();
}}>PREVIOUS PAGE</Button>
<Button variant="primary" onClick={() => {
this.increasePage();
this.load();
}}>NEXT PAGE</Button>
</ButtonToolbar>
<ButtonToolbar>
<Button variant="primary" onClick={() => this.setState({addModalShow: true})}>
Add Calculation
</Button>
<Button variant="primary" onClick={() => {
this.setId(this.state.selected);
this.setState({updateModalShow: true})}} disabled={this.state.buttonOn ? false : true}>
Update Calculation
</Button>
<Button variant="danger" onClick={() => {
this.deleteRow(this.state.selected);
}}>DELETE</Button>
<Link to={`/calculations/${this.state.items[this.state.selected] && this.state.items[this.state.selected].id}`}>
<Button variant="warning" disabled={this.state.buttonOn ? false : true}>Cluster</Button>
</Link>
<AddCalculationsModal
show={this.state.addModalShow}
onHide={addModalClose}
calculation={this.state.items[this.state.selected]}
/>
<UpdateCalculationsModal
show={this.state.updateModalShow}
onHide={updateModalClose}
calculation={this.state.items[this.state.selected] && this.state.items[this.state.selected].id}
calcname={this.state.items[this.state.selected] && this.state.items[this.state.selected].name}
/>
</ButtonToolbar>
</div>
)
}
}
And
import React, { Component } from 'react'
import axios from 'axios'
import "react-table/react-table.css";
import ReactTable from 'react-table';
import {Button, ButtonToolbar} from 'react-bootstrap';
import AuthenticationService from '../service/AuthenticationService';
export default class Calculations extends Component {
isCMounted = false;
constructor(props) {
super(props)
this.state = {
items: [],
selected: null,
updateId: null,
loaded: false
}
}
componentDidMount() {
this.isCMounted = true;
if(!this.state.loaded) {
this.load();
}
};
componentDidUpdate() {
if(!this.state.loaded) {
this.load();
}
};
componentWillUnmount() {
this.isCMounted = false;
}
load = async () => {
if(this.isCMounted && !this.state.loaded) {
await axios.get('calculation-types')
.then(res => {
console.log(this.isCMounted)
if (this.isCMounted && this.state.items.id === res.data.id){
this.setState({items: res.data})
}
});
this.setState({loaded: true})
}
}
setId = (id) => {
const idValue = this.state.items[id].id;
if (this.isCMounted)
this.setState({updateId: idValue});
}
render() {
return (
<div>
<h3>Calculation Types</h3>
<ReactTable
columns={
[
{
Header: "ID",
accessor: "idType",
width: 100,
minWidth: 100,
maxWidth: 100
},
{
Header: "Name",
accessor: "name"
}
]
}
data={this.state.items}
filterable
showPagination={false}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
this.setState({
selected: rowInfo.index
})
},
style: {
background: rowInfo.index === this.state.selected ? '#00afec' : 'white',
color: rowInfo.index === this.state.selected ? 'white' : 'black'
}
}
}else{
return {}
}
}}
>
</ReactTable>
</div>
)
}
}
are my components. Menu is a normal link. after login i appear on the first with menu on top.
Have you tried moving this.setState({loaded: true}) into the axios response callback block? Since you're awaiting the fetch request, I wonder if the this.setState({items: res.data} that you have in the callback block is causing an infinite componentDidUpdate loop that causes load to be repeatedly called without ever having the chance to arrive at the this.setState({loaded: true}) in the final line of load.
load = async () => {
if(this.isCMounted && !this.state.loaded) {
await axios.get('calculation-types')
.then(res => {
console.log(this.isCMounted)
if (this.isCMounted && this.state.items.id === res.data.id){
this.setState({ items: res.data, loaded: true })
}
});
}
}

Image is not visible (source is not recognized)

I'm making a simple web-app in React.js (+ Spring in back).
I have problem with displaying a photo (.img) from local path in function displayItems. Picture is not visible. If i load file from web in the same code (src="http.......") everything is fine.
Could you help?
import React, { Component } from 'react';
import '../index.css';
class Author extends Component {
constructor(props) {
super(props);
this.state = {
mail: window.location.href.slice(32, -7),
items: 2,
loadingState: false
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=this.refs.iScroll.scrollHeight){
this.loadMoreItems();
}
});
}
displayItems() {
var items = [];
for (let i = 0; i < this.state.items; i++) {
//PROBLEM
items.push(<img src="../resources/Photos/1.jpg"></img>);
}
return items;
}
loadMoreItems() {
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 2, loadingState: false });
}, 3000);
}
render() {
return (
<div
className="vc"
ref="iScroll"
style={{ height: "200px", overflow: "auto" }}
>
<h2>My adventures: </h2>
<div>
{this.displayItems()}
</div>
{this.state.loadingState
? <p className="loading">
loading More Images..
</p>
: ""}
</div>
);
}
}
export default Author;
You will have to get the image using require or import and then use it in the src,
const image = require("../resources/Photos/1.jpg")
...
items.push(<img src={image}></img>);

Can only update a mounted or mounting component.

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)

Categories

Resources