Basically I have a set of dynamic tables that are being displayed based on the values passed. If there is an empty array passed, it should show No data found. In my case when I send data to the table, all the tables will show "No data found" first then followed by the actual table content. I am not sure what is causing this.
The data is loaded asynchronously , it shows no data found and then the actual content. I have added setInterval to show this asynchronous nature
Sandbox:https://codesandbox.io/s/react-table-row-table-ryiny?file=/src/index.js:0-1322
Can someone help me?
Parent
import * as React from "react";
import { render } from "react-dom";
import DataGrid from "./DataGrid";
const d1 = [{ name: "test", age: "20" }, { name: "test1", age: "15" }];
const d2 = [{ area: "area", pin: "123" }, { area: "area1", pin: "1245" }];
const c1 = [
{ Header: "Name", accessor: "name" },
{ Header: "Age", accessor: "age" }
];
const c2 = [
{ Header: "Area", accessor: "area" },
{ Header: "Pin", accessor: "pin" }
];
const d3 = [];
const c3 = [];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data1: [],
column1: [],
data2: [],
column2: [],
data3: [],
column3: []
};
}
componentDidMount() {
setTimeout(() => {
this.setState({
data1: d1,
column1: c1
});
}, 2000);
setTimeout(() => {
this.setState({
data2: d2,
column2: c2
});
}, 2500);
this.setState({
data3: d3,
column3: c3
});
}
render() {
return (
<>
<DataGrid data={this.state.data1} columns={this.state.column1} />
<DataGrid data={this.state.data2} columns={this.state.column2} />
<DataGrid data={this.state.data3} columns={this.state.column3} />
</>
);
}
}
Child
import * as React from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
export default class DataGrid extends React.Component {
constructor(props) {
super(props);
this.state = {
showMore: false
};
}
toggleState = () => {
this.setState(prevState => ({
showMore: !prevState.showMore
}));
};
formatData = () => {
let arr = [];
if (this.props.data && this.props.data.length > 0)
arr = this.state.showMore ? this.props.data : this.props.data.slice(0, 2);
return arr;
};
render() {
const { showMore } = this.state;
const { data, columns } = this.props;
const showLink = data.length > 2;
const subset = this.formatData();
return (
<>
{showLink && (
<button onClick={this.toggleState}>
Show {showMore ? "Less" : "More"}
</button>
)}
{data && data.length > 0 ? (
<ReactTable
showPagination={false}
data={subset}
columns={columns}
minRows={0}
NoDataComponent={() => null}
loading={false}
/>
) : (
"No data found"
)}
</>
);
}
}
Adding few points to the above answer.
The reason it was behaving in that way is not because of the asynchronous behavior but the life-cycle nature of the React component.which in this case takes place as:
The DataGrid is rendered with initial state of data i.e empty[] array.
No data is shown because empty[] array is passed in this cycle.
Then you are setting the state in componentDidMount.
To show the effect Datagrid is again re rendered with actual data.
Initialize the App state's data with null instead of an empty array (sandbox):
this.state = {
data1: null,
column1: [],
data2: null,
column2: [],
data3: null,
column3: []
};
In the DataGrid method check if the value is falsy (null counts, but empty array is truthy), and return null (nothing to render) if it is:
render() {
const { data, columns } = this.props;
if (!data) return null;
Related
I've been trying to create a react-bootstrap-table2 but I get the following warning: Each child in a list should have a unique "key" prop.
Here is my code:
export const columns = [
{
dataField: "timestamps",
text: "Timestamp",
},
];
class Table extends Component {
constructor(props) {
super(props);
this.state = { timestamps: [] };
}
componentDidMount() {
const database = db.ref().child("timestamped_measures");
database.on("value", (ts_measures) => {
const timestamps = [];
const columns = [{ dataField: "timestamps", text: "Timestamp" }];
ts_measures.forEach((ts_measure) => {
timestamps.push(ts_measure.val().timestamp);
});
console.log(timestamps);
this.setState((prevState) => {
return { timestamps: [...prevState.timestamps, ...timestamps] };
});
});
}
render() {
return (
<div className="App">
<BootstrapTable
keyField="timestamps"
data={this.state.timestamps.map((item) => ({ item }))}
columns={columns}
pagination={paginationFactory()}
/>
</div>
);
}
}
export default Table;
Here is the console with the list of data I am trying to display
So my question is how to give each child in a list an unique key.
Any help would be appreciated!
You keyField should be set to dataField(key) not timestamps(value). Also, no mapping of data is required.
https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html#keyfield-required-string
i.e.
<BootstrapTable
keyField="dataField"
data={this.state.timestamps}
columns={columns}
pagination={paginationFactory()}
/>
You are passing array of integers instead of array of objects that has "timestamp" property.
export const columns = [
{
dataField: "timestamp",
text: "Timestamp",
},
];
class Table extends Component {
constructor(props) {
super(props);
this.state = { timestamps: [] };
}
componentDidMount() {
const database = db.ref().child("timestamped_measures");
database.on("value", (ts_measures) => {
const timestamps = [];
ts_measures.forEach((ts_measure) => {
timestamps.push({ timestamp: ts_measure.val().timestamp });
});
console.log(timestamps);
this.setState((prevState) => {
return { timestamps: [...prevState.timestamps, ...timestamps] };
});
});
}
render() {
return (
<div className="App">
<BootstrapTable
keyField="timestamp"
data={this.state.timestamps}
columns={columns}
pagination={paginationFactory()}
/>
</div>
);
}
}
export default Table;
I'm trying to change the value of an object key from a state array using setState. The change should be in such a way that only a specific value of object should be changed from the array of index i. This index is passed as follows
import React from "react";
import {Button} from 'react-bootstrap';
class StepComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{
step: "Step1",
visited: false
},
{
step: "Step2",
visited: false
},
{
step: "Step3",
visited: false
}
]
};
}
nextStep(i) {
//Change the visited value of object from data[i] array from false to true
//Something like below
this.setState({
data[i]:visited to true
})
}
render(){
let visitSteps = this.state.data;
return(
<div>
{visitSteps.map((visitedStep, index) => (
<div key={index}>
<p>{visitedStep.step}</p>
<Button onClick={() => this.nextStep(i)}>Continue</Button>
</div>
))}
</div>
)
}
}
export default StepComponent
As per the example given aboove on each onClick event the the value of that particular object value of visited is changed from false to true
You can create a variable with the array equals to your data, change the index passed as input and then call a set state passing the new array.
nextStep(i) {
let visitesList = [...this.state.data];
visitesList[i].visited = true;
this.setState({
data: visitesList
})
}
If you just want one step to be true at a time you can use a map function
nextStep(i) {
this.setState({
data: this.state.data.map((e, index) => {
e.visited = i === index;
return e;
})
});
}
Also, when calling the nextStep on the Button, call it by using nextStep(index)
Change specific object property of array.
class App extends React.Component {
state = {
data:[
{
step: "Step1",
visited: false
},
{
step: "Step2",
visited: false
},
{
step: "Step3",
visited: false
}
]
};
handleClick = item => {
const { data } = this.state;
let obj = data.find(a => a.step === item.step);
obj.visited = true;
let filtered = data.filter(a => a.step !== item.step);
this.setState({ data: [obj, ...filtered] });
};
render() {
console.log(this.state.data);
return (
<div>
{this.state.data.map(a => (
<button key={a.step} style={{ color: a.visited ? "red" : "" }} onClick={() => this.handleClick(a)}>
{a.step}
</button>
))}
</div>
);
}
}
Live Demo
I am using react-table for data-grid purposes. I have extracted react-table as a separate component where-in I just pass necessary props to it and it renders the grid.
I am trying to get the info related to a particular row whenever I click on it. I am trying getTrProps but does not seem like working.
Sandbox: https://codesandbox.io/s/react-table-row-table-g3kd5
App Component
import * as React from "react";
import { render } from "react-dom";
import DataGrid from "./DataGrid";
interface IProps {}
interface IState {
data: {}[];
columns: {}[];
}
class App extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
data: [],
columns: []
};
}
componentDidMount() {
this.getData();
}
getData = () => {
let data = [
{ firstName: "Jack", status: "Submitted", age: "14" },
{ firstName: "Simon", status: "Pending", age: "15" },
{ firstName: "Pete", status: "Approved", age: "17" }
];
this.setState({ data }, () => this.getColumns());
};
getColumns = () => {
let columns = [
{
Header: "First Name",
accessor: "firstName"
},
{
Header: "Status",
accessor: "status"
},
{
Header: "Age",
accessor: "age"
}
];
this.setState({ columns });
};
onClickRow = () => {
console.log("test");
};
render() {
return (
<>
<DataGrid
data={this.state.data}
columns={this.state.columns}
rowClicked={this.onClickRow}
/>
</>
);
}
}
render(<App />, document.getElementById("root"));
DataGrid Component
import * as React from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";
interface IProps {
data: any;
columns: any;
rowClicked(): void;
}
interface IState {}
export default class DataGrid extends React.Component<IProps, IState> {
onRowClick = (state: any, rowInfo: any, column: any, instance: any) => {
this.props.rowClicked();
};
render() {
return (
<>
<ReactTable
data={this.props.data}
columns={this.props.columns}
getTdProps={this.onRowClick}
/>
</>
);
}
}
Use this code to get info of a clicked row:
getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: (e, handleOriginal) => {
console.log("row info:", rowInfo);
if (handleOriginal) {
handleOriginal();
}
}
}}}
You can check this CodeSandbox example: https://codesandbox.io/s/react-table-row-table-shehb?fontsize=14
you have quite a few errors in your code but to pass the value back you have to put it into your callback:
onRowClick = (state: any, rowInfo: any, column: any, instance: any) => {
this.props.rowClicked(rowInfo);
};
and read it out like this:
onClickRow = (rowInfo) => {
console.log(rowInfo);
};
Hope this helps.
I have a dynamic form as a functional component which is generated via a class based component. I want to make reset button which clears the input field values and sets the state to null array.
Full code is available here:
https://codesandbox.io/s/beautiful-archimedes-o1ygt
I want to make a reset button, clearing all the input values and initializing the Itemvalues array to null.
Even if I set the values to null, it doesn't clear the input field.
However, the problem I'm facing is that since, it is a dynamic form and a functional component it doesn't have a predefined state for each individual form field making it difficult to set value to null.
Can someone please help, I'm stuck on this from a long time
Here's a codesandbox to show you how to reset the items: https://codesandbox.io/s/romantic-heisenberg-93qi7
I also left a note for you on how to get this to work with your API data, see the comment inside onChangeText()
The problem is that the inputs are not controlled by state as you have deduced. We should create an updated object for each item from your API, giving it a value prop.
index.js
import React from "react";
import ReactDOM from "react-dom";
import Cart from "./Cart";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
Items: [],
itemvalues: [{}]
};
this.onChangeText = this.onChangeText.bind(this);
this.getItems = this.getItems.bind(this);
this.handleReset = this.handleReset.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.findFieldIndex = this.findFieldIndex.bind(this);
this.trimText = this.trimText.bind(this);
}
getItems = () => {
/*if the data is coming from an API, store it in an array then .map() over it.
we can add a value prop to the object like:
so you can do something like:
const newItems = [...apiData].map((item) => {
return {
...item,
value: ""
}
})
this.setState({
Items: newItems
})
*/
this.setState({
Items: [
{
name: "item1",
description: "item1",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item2",
description: "item2",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item3",
description: "item3",
group: "groupB",
dtype: "str",
value: ""
},
{
name: "item4",
description: "item4",
group: "groupB",
dtype: "str",
value: ""
}
]
});
};
onChangeText = e => {
const updatedItems = [...this.state.Items].map(item => {
if (item.name === e.target.name) {
return {
...item,
value: e.target.value
};
} else {
return item;
}
});
const updatedItemValues = [...updatedItems].reduce((obj, curr) => {
if (!obj[curr.group]) {
obj[curr.group] = [];
}
obj[curr.group] = [...obj[curr.group], { [curr.name]: curr.value }];
return obj;
}, {});
this.setState({
...this.state,
Items: updatedItems,
itemvalues: updatedItemValues
});
};
findFieldIndex = (array, name) => {
return array.findIndex(item => item[name] !== undefined);
};
trimText(str) {
return str.trim();
}
handleReset = () => {
const resetedItems = [...this.state.Items].map(item => {
return {
...item,
value: ""
};
});
this.setState(
{
...this.state,
Items: resetedItems,
itemvalues: []
},
() => console.log(this.state)
);
};
handleSubmit = () => {
console.log(this.state.itemvalues);
};
render() {
return (
<div>
{
<Cart
Items={this.state.Items}
getItems={this.getItems}
handleSubmit={this.handleSubmit}
handleReset={this.handleReset}
onChangeText={this.onChangeText}
/>
}
</div>
);
}
}
Cart.js
import React, { useEffect } from "react";
import Form from "./Form";
const Cart = props => {
useEffect(() => {
props.getItems(props.Items);
}, []);
return (
<div>
<Form Items={props.Items} onChangeText={props.onChangeText} />
<button onClick={props.handleSubmit}>Submit</button>
<button onClick={props.handleReset}>Reset</button>
</div>
);
};
export default Cart;
The Cart component can remain mostly the same, we do not need to pass in props.items to useEffect() dependency.
Form.js
import React from "react";
const Form = props => {
return (
<div>
{props.Items.map(item => {
return (
<input
name={item.name}
placeholder={item.description}
data-type={item.dtype}
data-group={item.group}
onChange={e => props.onChangeText(e)}
value={item.value}
/>
);
})}
</div>
);
};
export default Form;
Now in Form component, we provide each input a value prop that is connected to the item our upper-most parent component-state.
That's pretty much all you need to reset the values.
See if that works for you:
Working example on CodeSandbox
Since you were already using hooks in part of your code, I've converted your class into a functional component using hooks (my advice: learn hooks and forget about class components).
I've added a value property to your INITIAL_STATE so it will keep the input value for each inputItem.
Full CODE:
index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormV2 from "./FormV2";
import "./styles.css";
function App() {
const INITIAL_STATE = [
{
name: "item1",
description: "item1",
group: "groupA",
dtype: "str",
value: "" // ADDED VALUE PROPERTY TO KEEP THE INPUT VALUE
},
{
name: "item2",
description: "item2",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item3",
description: "item3",
group: "groupB",
dtype: "str",
value: ""
},
{
name: "item4",
description: "item4",
group: "groupB",
dtype: "str",
value: ""
}
];
const [inputItems, setInputItems] = useState(INITIAL_STATE);
function handleChange(event, index) {
const newValue = event.target.value;
setInputItems(prevState => {
const aux = Array.from(prevState);
aux[index].value = newValue;
return aux;
});
}
function handleReset() {
console.log("Reseting Form to INITIAL_STATE ...");
setInputItems(INITIAL_STATE);
}
function handleSubmit() {
inputItems.forEach(item =>
console.log(
"I will submit input: " + item.name + ", which value is: " + item.value
)
);
}
return (
<FormV2
handleSubmit={handleSubmit}
handleReset={handleReset}
handleChange={handleChange}
inputItems={inputItems}
/>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
FormV2.js
import React from "react";
function FormV2(props) {
const formInputItems = props.inputItems.map((item, index) => (
<div key={item.name}>
{item.name + ": "}
<input
type="text"
data-type={item.dtype}
data-group={item.group}
placeholder={item.description}
value={item.value}
onChange={event => props.handleChange(event, index)}
/>
</div>
));
return (
<React.Fragment>
<form>{formInputItems}</form>
<button onClick={props.handleSubmit}>Submit</button>
<button onClick={props.handleReset}>Reset</button>
<div>State: {JSON.stringify(props.inputItems)}</div>
</React.Fragment>
);
}
export default FormV2;
In order to control the values of the child components (Items) which I presume are input fields you need to be passing down their values from their parent component. So each of your items will have an item.value which is stored in the parent component's state.
That means that in the parent component you will be able to define a method which clears all of the item values it is storing in its state.
That will probably look something like
resetInputs = () => {
this.setState({
inputFields: this.state.inputFields.map(inputField => {
...inputField,
value: ''
}
})
}
Also you'll need to write what kind of tag you want for your code to work, like input.
So what you'll end up with for the code of the child component you shared is something like:
const Form = (props) => {
return (
<div>
{props.Items.map(item => (
<input
name={item.name}
value={item.value}
placeholder={item.description}
onChange={e => props.onChangeText(e)}
/>
)
)}
</div>
);
}
export default Form
You want to manage the state of unknown number N of items, one way to achieve it is by managing a single object which contains all states, for example, setValuesManager manages N inputs and clicking the button reset its state:
function TextAreaManager() {
const [valuesManager, setValuesManager] = useState([...items]);
return (
<Flexbox>
{valuesManager.map((value, i) => (
<TextBoxItem
key={i}
value={value}
onChange={e => {
valuesManager[i] = e.target.value;
setValuesManager([...valuesManager]);
}}
/>
))}
<PinkButton
onClick={() =>
setValuesManager([...Array(valuesManager.length).fill('')])
}
>
Reset All
</PinkButton>
</Flexbox>
);
}
Demo:
I am trying to play around with react's child to parent communication, i am passing three buttons which has unique ids, I want to simply display the values after increment button is clicked. On first click, every button does increment fine, however, after second click on any button it gives
Cannot read property 'value' of undefined
. I am not sure what is happening after first click.
let data = [
{id: 1, value: 85},
{id: 2, value: 0},
{id: 3, value: 0}
]
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1,
data: this.props.data
}
}
componentWillReceiveProps(nextProps) {
if(nextProps !== this.state.data) {
this.setState({data: nextProps})
}
}
handleClick(id) {
this.props.increment(id,
this.state.data[id-1].value = this.state.data[id-1].value +
this.state.counter);
}
render() {
return (
<div>
{data.map(data => {
return (
<div key={data.id}>
{data.value}
<button onClick={() => this.handleClick(data.id)}>+</button>
</div>
)
})}
</div>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
data
}
}
onIncrement(id, newValue) {
this.setState((state) => ({
data: state.data[id-1].value = newValue
}))
}
render() {
return (
<div>
<Counter data={this.state.data} increment={this.onIncrement.bind(this)}/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector("#root")
)
At this sentence:
this.props.increment(id,
this.state.data[id-1].value = this.state.data[id-1].value +
this.state.counter);
You are doing id-1, i think you don't need that, just [id].
In case you are click button with id 1 your are trying to increment value of 1 - 1 and you haven't any data with id 0
The problematic thing in your code is this line
this.state.data[id-1].value = this.state.data[id-1].value + this.state.counter);
what exactly you want to do here ? because you have 3 index 0,1,2 and you are out of index the it's undefined and you got error mention your requirement here.
your code using state in useless manner and i just optimize your code in a good way. Tip: do not use state-full component where not required use function component. it's working fine and serve according to your need.
const Counter = (props) => {
return (
<div>
{props.data.map(d => {
return (
<div key={d.id}>
{d.value}
<button onClick={() => props.increment(d.id, (d.value + 1))}>+</button>
</div>
)
})}
</div>
)
}
class App extends React.Component {
state = {
data: [
{ id: 1, value: 85 },
{ id: 2, value: 0 },
{ id: 3, value: 0 }
]
}
onIncrement = (id, newValue) => {
debugger
var newdata = [...this.state.data];
var d = { ...newdata[id - 1] };
d.value = newValue;
newdata[id - 1] = d;
this.setState({ data: newdata })
}
render() {
return (
<div>
<Counter data={this.state.data} increment={this.onIncrement} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector("#root")
)
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1,
data: this.props.data
};
}
handleClick(id, index) {
this.props.increment(
id,
(this.props.data[id - 1].value =
this.props.data[id - 1].value + this.state.counter), index
);
}
render() {
return (
<div>
{this.props.data.map((data
, index) => {
return (
<div key={data.id}>
{data.value}
<button onClick={() => this.handleClick(data.id, index)}>+</button>
</div>
);
})}
</div>
);
}
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [{ id: 1, value: 85 }, { id: 2, value: 0 }, { id: 3, value: 0 }]
};
}
onIncrement(id, newValue, index) {
let {data} = this.state;
data[index].value = newValue;
this.setState({
data
});
}
render() {
console.log(this.state.data)
return (
<div>
<Counter
data={this.state.data}
increment={this.onIncrement.bind(this)}
/>
</div>
);
}
}
please take a look you are doing state updation in wrong way
I found the issue, you have wrongly implemented componentWillReceiveProps and onIncrement , i have corrected these two functions :
onIncrement(id, newValue) {
let data = this.state.data;
data[id-1].value = newValue;
this.setState({data})
}
componentWillReceiveProps(nextProps) {
if(nextProps.data !== this.props.data) {
this.setState({data: nextProps.data})
}
}
Also see the working demo here : https://repl.it/#VikashSingh1/TemporalReliableLanserver