I have a stateful React Component, And i want to access a value of state form inside the state object, specifically number_rows.
class Contracts extends React.Component{
constructor(props) {
super(props);
this.state = {
number_rows: 30, //starting default
data: props.data
.sort((a, b) => this.high_to_low(a, b, "latestVolume"))
.slice(0, this.state.number_rows)
};
}
render(){
return(
...
)
}
}
export default Contracts;
I cant get my slice() to read the number of rows set in this.state.number_rows
example
b={a:'a', c:b.a}
{a: "a", c: "a"}
I tried state.number_rows and this.number_rows Is this even possible? Is there a work around??
Thanks for your help!!
numbers_rows is a constant or may be changed?
If it's a constant, consider moving it out of your state since it's technically not the state of the application.
Perhaps do something like
const NUMBERS_ROWS = 30; above the Contracts component or save that variable inside your constants file(s) and import that variable on the component.
If it can be changed.
You can make your initial state of data to empty array, then you have 2 choice depending on your circumstances.
If props.data is a fetched value from an API, and the value can either be null or empty array if the fetching still in progress, use componentDidUpdate to update your data state according to props.data value after the value is already fetched.
If you're pretty sure the props.data won't be a null/empty array and already populated before the component is mounted, you can use componentDidMount to set the data state, basically you just move the logic of both sorting and slicing to either componentDidMount or componentDidUpdate based on your circumstances.
Maybe you can use a default number rows variable to initialize your component's state first. After that, when you need to change your number_rows variable, just call the alterNumberRows function and calculate the data using the new number_rows value, and then update the state with the newly calculated number_rows and data.
class Contracts extends React.Component {
constructor(props) {
super(props);
const DEFAULT_NUMBER_ROWS = 30;
this.state = {
number_rows: DEFAULT_NUMBER_ROWS, // starting default
data: props.data
.sort((a, b) => this.high_to_low(a, b, "latestVolume"))
.slice(0, DEFAULT_NUMBER_ROWS),
};
}
alterNumberRows = () => {
// calculate the new number_rows
// I just add one to the previous number_rows for simplicity
const number_rows = this.state.number_rows + 1;
// use the newly calculated number_rows to slice a new data array
const data = this.props.data
.sort((a, b) => this.high_to_low(a, b, "latestVolume"))
.slice(0, number_rows);
this.setState({
number_rows,
data,
});
}
render() {
return (
...
);
}
}
Related
I am trying to change the state in a class component by using setState.
More specific I have a table, and I want to edit/update one of its elements. For this case, I am passing the indeces to the handleTableFieldOnChange function for the position of the value in the array.
Since I know that I should not mutate the state, I used an external library to deep copy the tables array/list.
The deep copy and the new value assignment works. The deep copy worked also with the JSON.parse(JSON.stringify(this.state.tables)); alternative.
Problem: For some reason the this.setState(...) does not change the tables value.
I do know the setState is asynchronous, this is why I used the callback and within it, the console.log(...) to check the updated value.
console.log(...) still emits the old value.
private handleTableFieldOnChange(val: boolean | string | number | [number, string], tblRowIndex: number, tblIndex: number, tblColINdex: number) {
const cloneDeep = require('lodash.clonedeep');
const newTables = cloneDeep(this.state.tables);
if (newTables && newTables[tblIndex] && newTables[tblIndex].items ) {
newTables[tblIndex].items![tblRowIndex][tblColINdex].value = val;
}
this.setState( {tables: newTables}, () => {
console.log(this.state.tables)
})
}
state: State = {
tables: [],
report: this.props.report,
};
constructor(props: DetailProp, state: State) {
super(props, state);
this.initFieldsAndTabels();
}
private initFieldsAndTabels() {
if (this.state.report && this.state.report.extraction_items) {
this.state.tables = [];
this.state.report.extraction_items.forEach((extractionItems) => {
this.state.tables.push(extractionItems);
});
}
}
The code in handleTableFieldOnChange looks fine to me.
However in initFieldsAndTabels you are applying push on state directly instead of calling setState which may probably cause the issues:
this.state.report.extraction_items.forEach((extractionItems) => {
this.state.tables.push(extractionItems); //#HERE
});
Also as React.Component docs state you should not call setState in constructor (you are calling initFieldsAndTabels in constructor. Instead you could use componentDidMount.
P.S. If you want to add those extraction items in the constructor then you need something like this:
constructor(props) {
super(props);
// method should return a new array/object, but not modify state
const tables = this.initFieldsAndTabels();
this.state = {
tables,
}
}
As a practice project, I've started building a small Pokedex app in React.
import React, { Component} from 'react';
import './App.css';
import Card from './components/card/Card.component';
class App extends Component{
constructor(){
super();
this.state = {}
}
componentDidMount(){
let pokeDataArr = []
const getPokemonData = async() => {
const dataResponse = await fetch(
'https://pokeapi.co/api/v2/pokemon?limit=10'
);
const dataArr = await dataResponse.json();
const dataArr2 = await dataArr.results.forEach(i => {
fetch(i.url)
.then(dataResponse => dataResponse.json())
.then(json => pokeDataArr.push(json))
})
this.setState({ pokeDataArr }, () => console.log(this.state))
}
getPokemonData();
}
render(){
return(
<div>Pokedex!</div>
)
}
}
I'm having trouble accessing data from a specific index in an array.
When I log the entire state object to the console, I can see all the data I have retrieved from the AJAX call.
this.setState({ pokeDataArr }, () => console.log(this.state))
And this is the result in the console:
console result
However, if I try to log out data from an index in the array with:
this.setState({ pokeDataArr }, () => console.log(this.state.pokeDataArr[0]))
I get "undefined" in the console:
console result 2
As far as I'm aware, whatever function you run in the this.setState method's callback, it should run after setState has finished.
My goal is to use the data from this.state.pokeDataArr to make cards that display the info of each individual pokemon, but it seems like I'm stuck until I find a way to extract the data from the array and I have no clue what I'm missing.
Thank you for your time.
I think you messed up with your react state.
Usually, what people do is they set up their react state as an object with other elements (arrays, objects, strings, whatever) inside it. This looks something like this:
constructor(){
super();
this.state = {
myObject: {},
somethingElse: "",
anArray: []
}
}
This enables you to access parts of your state like this: this.state.myObject for instance. (this would return {})
In your example, you defined your state as an empty object.
constructor(){
super();
this.state = {}
}
And later, you set this object to an object, with an array inside:
this.setState({ pokeDataArr });
This will set your state to this: {[(your array)]}
To prevent this initialize your state like this:
constructor(){
super();
this.state = { pokeDataArr : {} }
}
And set your values like this:
this.setState({ pokeDataArr: pokeDataArr }, () => console.log(this.state.pokeDataArr[0]))
read more here: https://reactjs.org/docs/state-and-lifecycle.html
You'll need to use updater to use the callback instead of plain state update:
this.setState(
() => ({ pokeDataArr }),
() => console.log(this.state.pokeDataArr[0])
)
Read the note from the docs in the linked example:
Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the current state, we recommend using the updater function form
Currently I get my data from an API in a JSON-format when running my saga. The fetching process begins, when the component did mount. That means the component renders two times.
Now, when the data is available as props. I can use it in order to render it.
My approach to this is like following, I have got a:
Constructor with the initial state
I fetch data in "componentDidMount"
I got a function that takes the JSON properties from props and puts it into new variables
I run this function in my render() function, when the props contain the fetched data
The Problem in this approach: Once the component runs the function where the data becomes "structured", the render-function loops and then after some time, the values of the properties get displayed with a warning message in the console.
My Questions:
How to prevent the looping when render() runs once?
How can I design this, so that particular properties of the fetched object merge into a new object and how to
I hope I described the most important things about my issue. Here is the code:
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
deviceInfo: {
name: "Initial Name",
batLevel: "78%",
}
}
}
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = (this.props.data.data);
if(devices){
const newDeviceInfo = this.state.deviceInfo;
newDeviceInfo.name = devices[0].shadow.desired.payload.refAppData.name;
newDeviceInfo.batLevel = devices[0].shadow.reported.payload.refAppData.batteryState.level;
this.setState({
deviceInfo: newDeviceInfo,
});
}
}
render() {
this.updateDeviceInfoWithState()
return (
<div className='container'>
<p> {this.state.deviceInfo.name} </p>
<p> {this.state.deviceInfo.batLevel} </p>
</div>
)
}...
Updating the state in the render method is not a good practice, since it might cause an infinite loop.
In your case state is redundant, since you only take the data from props, or replace it with defaults. Instead of using the state return the name and batLevel in the updateDeviceInfoWithState method, and use it in the render method.
Example (not tested):
class Dashboard extends React.Component {
componentDidMount() {
this.props.requestApiData();
}
updateDeviceInfoWithState (){
const devices = this.props.data.data;
if(devices){
const device = devices[0].shadow;
return {
name: device.desired.payload.refAppData.name,
batLevel: device.reported.payload.refAppData.batteryState.level
};
}
return {
name: "Initial Name",
batLevel: "78%",
};
}
render() {
const { name, batLevel } = this.updateDeviceInfoWithState();
return (
<div className='container'>
<p> {name} </p>
<p> {batLevel} </p>
</div>
);
}...
Note 1: If you want to decouple your component from the state, it's better to enforce simple properties as input for the data. For example, this component needs as properties the name and batLevel. It doesn't need to be aware of the array of devices, shadow, payload, etc... You can prepare the data when you receive it in the saga, or use a redux selector in mapStateToProps.
Note 2: If you really need the data in your state, you can use the getDerivedStateFromProps life-cycle method (React 16.3), or update the state in the componentWillReceiveProps if you use an older version.
For this case you can use ComponentWillRecieveProps method like this
componentWillRecieveProps(nextProps) {
// Condition as per ur requirement.
If(this.props.data != nextProps.data) {
this.updateDeviceInfoWithState(nextProps)
}
}
This method will only run whenever ur component props are changed.
I have a component with two functions which should update state object:
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
categoryData: [],
objects: [],
object:[],
};
}
componentDidMount() {
this.setState({
data:data.Dluga,
categoryData: data.Dluga.basic,
objects:data,
})
}
changeCategory(event) {
event.preventDefault();
this.setState({
categoryData: this.state.data[(event.currentTarget.textContent).split(' ')[1]],
});
}
changeObject(event) {
event.preventDefault();
const objectOne = Object.assign({}, this.state.objects[event.currentTarget.parentElement.parentElement.children[0].children[0].value]);
this.setState({
objects: this.state.objects,
object:objectOne,
});
};
render() {
return (
<div className='categories'>
<SelectObejct onValueChange={this.changeObject}/>
<ul>
{Object.keys(this.state.data).map((item) => {
return (
<li className='category' key={item}
onClick={this.changeCategory.bind(this)}>
<span className='category-item'> {item}</span>
</li>
)})
}
</ul>
<div>
<CategoryData categoryData={this.state.categoryData}/>
</div>
</div>
);
}
}
When I update state with changeObject I have in state object two properties: objects and object, but initially it was 4 properties... Next when I update state with changeCategory I have initial properties from componentDidMount and updated categoryData but object is empty... I can't update state in one function because it's two onClick elements. What should I do to update state correctly?
The primary thing you're doing incorrectly is updating state based on existing state without using the callback version of setState. State updates can be asynchronous, and can be combined (batched). Any time you're setting state derived from the current state, you must use the callback form. E.g.:
changeCategory(event) {
event.preventDefault();
this.setState(prevState = > {
return {
categoryData: prevState.data[(event.currentTarget.textContent).split(' ')[1]]
};
});
}
Note that we're passing it a function, which will get called later (only a tiny bit later, but later), and will get the then-current state passed to it as a parameter; and we return the new state as a return value.
When I update state with changeObject I have in state object two properties: objects and object, but initially it was 4 properties...
That's absolutely normal. It's common to only specify a subset of your state properties when calling setState. In fact, changeObject should be:
changeObject(event) {
event.preventDefault();
this.setState(prevState => {
const objectOne = Object.assign({}, prevState.objects[event.currentTarget.parentElement.parentElement.children[0].children[0].value]);
return { object: objectOne };
});
}
Note that I didn't specify objects: prevState.objects. There's no reason to if you're not changing it.
Next when I update state with changeCategory I have initial properties from componentDidMount and updated categoryData but object is empty.
object will only be empty (whatever "empty" means) if you set it to that at some point. I suspect resolving the above will resolve this issue, but if not, and if you can't figure it out with further debugging, I suggest posting a new question with an [mcve] demonstrating that problem (you can do a runnable one with Stack Snippets; here's how).
I have an array, I have 2 components(child and parent). I iterate through array within parent component, I render child components, I give them props with data from array.
Child components have their props translated to state and then have increment and decrement that state.
Parent component can add new item into array and re-render. BUT. If i unshift() new item in front of the array, i get last item from array added to the screen instead of new one to the front.
QUESTION: Why it renders good with .push() and bad with .unshift(). Also everything is ok with concat and [newItem, ...oldArray], but bad with same things when i add items in front of the array? Also how to properly .unshift() new items(comments, counters, images, posts, eg anything) into state, so they render first?
PS: Anything i do (concat, slice, ...array, unshift, react's immutability helper) doesn't seem to work properly. Mobx and Redux didn't help.
PS: This also occurs with Mithril, Inferno and Aurelia.
import React from 'react'
import {render} from 'react-dom'
var Component = React.Component
var data = [0, 12, -10, 1, 0, 1]
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
counter: data
}
this.addCounter = this.addCounter.bind(this)
}
addCounter(e){
let newArr = [1, ...this.state.counter]
this.setState({
counter: newArr
})
}
render() {
if(this.state.counter){
return (
<div>
<button onClick={this.addCounter}>add counter</button>
{this.state.counter.map(e=>{
return(
<Counter count={e}/>
)
})}
</div>
)
} else {
return(
<div>loading...</div>
)
}
}
}
class Counter extends Component {
constructor(props) {
super(props)
this.state = {
count: this.props.count
}
this.increment = this.increment.bind(this)
this.decrement = this.decrement.bind(this)
}
increment(e){
this.setState({count: this.state.count + 1})
}
decrement(e){
this.setState({count: this.state.count - 1})
}
render() {
return (
<span>
<b onClick={this.increment}>+</b>
<i>{this.state.count}</i>
<b onClick={this.decrement}>-</b>
</span>
)
}
}
render(<App/>, document.getElementById('root'))
The main problem isn't the way you are prepending items to your array, it's that you are not providing a key when rendering the child component.
What happens when you render the initial array is that the child component gets instantiated once per item in your array. However, React has no way of mapping the values in your array to those instances.
Let's call the first instance A. When you prepend to your list and render again, the first child instance (in the array resulting from your this.state.counter.map) will still be instance A, just with the prop e set to a new value. You can verify this by for example logging this.props.e in your child's render method. After prepending the new item, the first logged value should correspond to the prepended value.
Since your child component is stateful, and does not do anything to handle componentWillReceiveProps, having the e prop changed will not do anything to change each instance's previous state.
The reason why it works when you append is because the already existing instances will still map 1-to-1 with the items in your counter array, and the new item will be rendered as a new instance of Counter.
You would have the same problem if you were to rearrange the order of the items in counter, for example. The resulting child instances would not change order.
So, the solution is to provide a unique key to Counter, for each item. Since your items do not have an intrinsic identity, my suggestion would be to put a
let currentId = 0
above your App component, and have each item in your counter array be an object of {value, id: currentId++}, then pass id as the key to Counter.