I'm new to using ag-grid and am implementing it into my React project. With this, I've made the following table:
class Table extends Component {
constructor(props) {
super(props)
this.state = {
columnDefs: [
{ headerName: "Column 1", field: "col1"},
],
rowData: [
{ 'col1': 'Hello'},
{ 'col1': 'World'},
],
}
}
render() {
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 605 }}>
<AgGridReact
columnDefs={this.state.columnDefs}
rowData={this.state.rowData}>
</AgGridReact>
</div>
);
}
};
Now, I have the ability to get JSON using .fetch and would like to load the JSON values into my table. For example, say my JSON comes in like so:
{'col1': 'Jeff',
'col1' : 'Sophie',
'col1' : 'Kelly'}
How would I load these values into my table above? I've tried adding the following componentDidMount method to the Table, but the table then greys itself out and says 'loading...' and never finishes loading:
componentDidMount(){
myData= getData() //method calls fetch and returns my JSON items
this.setState({
rowData: myData
});
}
Based on the code you've provided, there's nothing that I can see that would cause this issue. It could be to do with your getData function.
See this sample that sets rowData on componentDidMount by calling fetch:
https://plnkr.co/edit/K2gC2Qc6tKc3huTT
componentDidMount() {
const updateData = (data) => {
this.setState({ rowData: data });
};
fetch('https://www.ag-grid.com/example-assets/olympic-winners.json')
.then((resp) => resp.json())
.then((data) => updateData(data));
}
Related
For some reason my React component seems to remember its old state when going to another tab and back again, instead of reloading completely.
Basically when I click on the "Create" tab in my navbar and back to the "Board" tab data is populated twice instead of once, see image below. When going back the Board component this.state has two of each taskIds, as if it the component state still had the data from the initial page load when loading again. I have a React component looking like this:
const columnOrder = ['todo', 'in-progress', 'in-review', 'done']
const EMPTY_COLUMNS = {
'todo': {
id: 'todo',
title: 'TODO',
taskIds: []
},
'in-progress': {
id: 'in-progress',
title: 'In Progress',
taskIds: [],
},
'in-review': {
id: 'in-review',
title: 'In Review',
taskIds: []
},
'done': {
id: 'done',
title: 'Done',
taskIds: []
}
};
export class Board extends Component {
constructor(props) {
super(props);
this.onLoadEpic = this.onLoadEpic.bind(this);
this.state = {
columnOrder: columnOrder,
columns: {
'in-progress': {
id: 'in-progress',
title: 'In Progress',
taskIds: [],
},
// ...more columns similar to above
},
};
// Load state data on mount
componentDidMount() {
loadEpic(arg1, arg2);
}
// Async function loading items from DB and formatting into useful columns
async loadEpic(arg1, arg2) {
axios.get(...)
.then((response) => {
let data = response.data;
let newTasks = {};
let newColumns = EMPTY_COLUMNS;
data.taskItems.forEach(function(item) {
let id = item.id.toString();
newColumns[item.status]["taskIds"].push(id);
newTasks[id] = {
...item,
id: id
}
});
this.setState({
tasks: newTasks,
columns: newColumns
});
})
}
render() {
// Prints ["7"] on initial load and ["7", "7"] after going back and forth
console.log(this.state.columns["in-progress"].taskIds);
return (
// Simplified, but this is the main idea
<Container>
<DragDropContext onDragEnd={this.onDragEnd}>
{
this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId]
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]
return (
<Column key={column.id} column={column} tasks={tasks}/>
)
}
}
</DragDropContext>
</Container>
)
}
}
and an App.js with Routing looking like this:
export default class App extends Component {
static displayName = App.name;
render () {
return (
<Layout>
<Route exact path='/' component={Board} />
<Route exact path='/create' component={Create} />
</Layout>
);
}
}
Okay, so I figured it out: it's the EMPTY_COLUMNS constant that is bugging out. When the component is re-rendered, the same EMPTY_COLUMNS object is referenced - so the constant is being appended to. Instead, I should make a copy of the empty columns:
// Before - same object is being appended to, doesn't work
let newColumns = EMPTY_COLUMNS;
// After - create a deep copy of the constant, does work
let newColumns = JSON.parse(JSON.stringify(EMPTY_COLUMNS));
I'm getting data from and API and its working fine in console but unable to display data in my frontend table.
Displaying below error:-
TypeError: Cannot read property 'map' of undefined
Now I'm getting array in console as given below:-
My code:-
import React,{component, Component} from 'react';
class List extends Component{
constructor(props){
super(props);
this.state = {
items: [],
isLoaded: false,
}
}
componentDidMount(){
fetch('url')
.then(res => res.json())
.then(data => {
console.log(data)
this.setState({
isLoaded: true,
items: data
})
// console.log(json)
});
}
render(){
var { isLoaded, items} = this.state;
if(!isLoaded){
return <div>Loading...</div>
}
else{
return(
<div className="List">
<ul>
{items[0].map(item =>(
<li key={item.UserId}>
{item.repos_url}
</li>
))}
</ul>
</div>
)
}
}
}
export default List;
Answer will be appreciated!
It looks like data has the following shape:
data = {
Table: [{
UserId: 123,
repos_url: 'foo',
}, {
UserId: 456,
repos_url: 'bar',
}, ...],
};
So one option would be to replace items[0].map(...) with items.Table.map(...).
Alternatively, replace:
this.setState({
isLoaded: true,
items: data,
});
With:
this.setState({
isLoaded: true,
items: data.Table,
});
And then also items[0].map(...) with just items.map(...).
You are referencing a wrong property. Try to map over Table array that is inside "items" state variable
<ul>
{items.Table.map(item =>(
<li key={item.UserId}>
{item.repos_url}
</li>
))}
</ul>
You are using items[0].map(), you need to reference the table element in your items object by doing items.Table.map()
As I am new to react i have been struggling to pass data from my state to the chartjs dynamically...what I need is when ever user updates the Textbox and asks for the Output, the Pie chart should update itself automatically according to the output...i have stored the output values in the state but Pie chart is not allowing me to Pass state as data...
Here's a Link to the Code Sandbox
https://codesandbox.io/s/autumn-mountain-sv1qk?fontsize=14&hidenavigation=1&theme=dark
class Buyer extends Component {
constructor(props) {
super(props);
this.state = {
income: 100000,
percent: 13,
totalTax: '',
grossPrice: '',
otherValues: '',
}
}
PieChart = (Gross,totalTax) => {
let GrossPrice = Gross ;
let TotalTax = totalTax ;
let data = [GrossPrice,TotalTax]
let Pie = {
labels: ['Total Tax', 'Gross Price'],
datasets: [{
data: data,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
],
}]
}
}
handleIncome = (event) => {
let income = event.target.value
this.handleData(income, this.state.percent)
console.log(this.state)
}
handlePercent = (event) => {
let Percent = event.target.value
this.handleSliderData(this.state.income, Percent)
}
// From Slider
sliderIncome = (event) => {
this.setState({ income: event })
this.handleSliderData(event, this.state.percent)
// console.log(this.state)
}
sliderPercent = (event) => {
this.setState({ percent: event })
this.handleSliderData(this.state.income, event)
}
handleData = (income, Percent) => {
this.setState({
income: income,
percent: Percent,
totalTax: //some Calculations
grossPrice: //some Calculations
otherValues: //some Calculations
})
console.log(this.state)
}
handleSliderData = (income, Percent) => {
this.setState({
income: income,
percent: Percent,
totalTax: //some Calculations
grossPrice://some Calculations
otherValues://some Calculations
})
this.PieChart(this.state.grossPrice,this.state.totalTax)
// console.log(this.state)
}
render() {
return (
<div>
<div >
<Card s>
<PieChart data={this.PieChart} width={600} height={300} />
</Card>
</div>
</Col>
)
}
I have tried creating a function for the pie chart but was not able to get through...any help would be appreciated..thanks!!
I think there are a few problems with the code.
this.PieChart function doesn't return anything now. From giving the code a quick glance, I can see that you are trying to pass the props needed for the PieChart component from this.PieChart function. Return whatever you need as prop for the component and also call the function inside the JSX using parenthesis, passing the needed parameters into the function.
PieChart = (Gross,totalTax) => {
let GrossPrice = Gross ;
let TotalTax = totalTax ;
let data = [GrossPrice,TotalTax]
let Pie = {
labels: ['Total Tax', 'Gross Price'],
datasets: [{
data: data,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
],
}]
}
}
return Pie; //or whatever data you need for the component
Also,
<PieChart data={this.PieChart(this.state.grossPrice, this.state.totalTax)} width={600} height={300} />
Also, keep in mind to use proper naming conventions. Functions should be in camel case( this.PieChart should be named this.pieChart ). Try using different names for the component and function. This shall solve a lot of problems you might run into
Update: I have updated the sandbox you have shared. Hope this helps.
https://codesandbox.io/s/friendly-mahavira-79n2s
I think i found the solution for the particular problem...i order to update the data in the piechart dynamically using state we have create a state in the constructor(props) like so...
constructor(props) {
super(props);
this.state = {
income: '',
percent: '',
totalTax: '',
grossPrice: '',
otherValues: '',
Data: {}
}
}
Then i have used componentDidMount() to mount the initial state of the PieChart like So....
componentDidMount() {
let runscore = [100,200];
this.setState(
{
Data: {
labels: [
'Purple',
'Yellow',
],
datasets: [
{
data: runscore,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
]
}
]
}
});
}
then i created a function PieChart = (Gross,totalTax) that fetches data from another functions and used that to set state in Data like so...
PieChart = (Gross,totalTax) => {
let runscore = [Gross,totalTax];
this.setState({
Data: {
labels: [
'Gross Price',
'Total Tax',
],
datasets: [
{
data: runscore,
backgroundColor: [
'#1ca5b6',
'#89ba2b',
]
}
]
}
});
}
For now this is not updating the state in sync with the current state but i gets the work done...i hope this helps others...
I want to add multi language option in mui Datatables. I can change the translations but when I want to change language, I tried to give another object with the other translations (this object if I do console log I can see the changes) but the label texts not change.
I used a contextProvider to change the language selected and then get the specific dictionary with the translations.
Is a class component, so I did a static contextType with the correct provider.
Is there any possibility to re-render the element with another options or something like that?
options = {
textLabels: this.context.translation.dataTables.textLabels
};
return(
<MUIDataTable
title={this.context.language.value}
data={data}
columns={columns}
options={options}
/>
);
The best approach to re-render Mui-Datatables its updating the key of the table
key={this.context.language.value}
<MUIDataTable
key={this.context.language.value}
title={this.context.language.value}
data={data}
columns={columns}
options={options}
/>
You can force React component rendering:
There are multiple ways to force a React component rendering but they are essentially the same. The first is using this.forceUpdate(), which skips shouldComponentUpdate:
someMethod() {
// Force rendering without state change...
this.forceUpdate();
}
Assuming your component has a state, you could also call the following:
someMethod() {
// Force rendering with a simulated state change
this.setState({ state: this.state });
}
use customRowRender Function in the options and manipulate table with respect to language
Override default row rendering with custom function.
customRowRender(data, dataIndex, rowIndex) => React Component
In MUIDataTable, We can override label name by providing label in MUIDataTableColumnDef options while making column.
Example :
const columns: MUIDataTableColumnDef[] = [
{
name: 'Id',
label: 'ID',
options: {
download: false,
customBodyRenderLite: (index: number) => {
const desc: Description = evenMoreAbout[index]
return <BasicInfo obj={desc} setIconClicked={setIconClicked} />
}
}
},
{
name: 'id',
label: 'ID',
options: {
display: 'excluded',
download: true,
customBodyRender: desc => desc.id
}
}]
Even though if we still want to over ride the label name on some condition of data using customHeadLabelRender ... we can as like below example
const columns: MUIDataTableColumnDef[] = [
{
name: 'Id',
label: '',
options: {
download: false,
customBodyRenderLite: (index: number) => {
const desc: Description = evenMoreAbout[index]
return <BasicInfo obj={desc} setIconClicked={setIconClicked} />
},
customHeadLabelRender: (dataIndex: number, rowIndex: number) => {
return 'ID';
}
}
}
]
I am new to react and to get to grips with it, I'm converting existing project from jQuery to React.
I have six select boxes that update sequentially depending on the selection of the previous select box e.g. select option FOO from select box A and select box B must update with items corresponding to FOO.
I'll list some of references at the bottom
What I have so far:
I've got onchange events using fetch to call my api and get the data I want to use to populate the next select box and this is where I'm hitting a wall.
I've written two components MapControls and SelectBox. MapControls has an array of objects in its state that are used to generate a collection of SelectBox instances
Here's the MapControls component:
class MapControls extends React.Component {
state = {
selectBoxes: [
{
id: 'WorkSource',
name: 'WorkSource',
title:'Work Source',
containerId: 'WorkSourceContainer',
className: 'WorkSource',
childControllerMethod: 'GetBusinessTypeDropdown',
items: [{value:0, text:'Select'}, { value: '1', text: 'Routed' }],
child: 'BusinessType'
},
{
id: 'BusinessType',
name: 'BusinessType',
title: 'Business Type',
containerId: 'BusinessTypeContainer',
className: 'BusinessType',
childControllerMethod: 'GetWorkTypeDropdown',
items: [{ value: 0, text: 'Select' }],
child: 'WorkType'
},
//... more items ...
]
}
render() {
return this.state.selectBoxes.map(selectBox =>
<div key={selectBox.id} className='col-xs-2'>
<div id={selectBox.containerId}>
<SelectBox id={selectBox.id} name={selectBox.name} selectBox={selectBox} onChange={this.handleChange} />
</div>
</div>
);
}
};
and here's the SelectBox component. It's in the handleChange event where I want to be able to update the items in another SelectBox instance based on the ref. See the inline comments that describe my stumbling blocks
class SelectBox extends React.Component{
constructor(props) {
super(props);
this.state = { items: this.props.selectBox.items };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const selectedValue = event.target.value;
const url = "/Home/" + event.target.dataset.childControllerMethod;
const data = JSON.stringify({ selectedValue: selectedValue });
fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: data
}).then(response => {
if (response.status >= 400) {
console.log("Bad response from server");
}
return response.json();
}).then(data => {
// This updates the selectbox that was changed, which is not what I want
// this.setState({ items: data})
// This is what I was hoping would work, but I've discovered that BusinessType is a DOM element here, so setState is not valid
// this.refs.BusinessType.setState({ items: data });
// I hardcorded the 'BusinessType' as a ref just for testing because I know it's valid, but I want this to be dynamic
// findDOMNode seems to be somewhat of an anti-pattern, so I'd rather not do this. Not that the below code works because sibling is not a React object
// let sibling = ReactDOM.findDOMNode(this.refs.BusinessType);
// sibling.setState({ items: data });
});
}
render()
{
const optionItems = this.state.items.map((item, index) =>
<option key={index} value={item.value} >{item.text}</option>
);
return <div>
<label htmlFor={this.props.selectBox.id} >{this.props.selectBox.title}</label>
<select onChange={this.handleChange} id={this.props.selectBox.id} ref={this.props.selectBox.child} /*data-child={this.props.selectBox.child}*/ data-child-controller-method={this.props.selectBox.childControllerMethod}>
{optionItems}
</select>
</div>
}
};
ReactDOM.render(<MapControls />,
document.getElementById('mapControls')
);
Places I've looked:
http://jamesknelson.com/react-js-by-example-interacting-with-the-dom/
https://reactjs.org/docs/react-dom.html#finddomnode
https://reactjs.org/docs/refs-and-the-dom.html
http://www.mattmorgante.com/technology/dropdown-with-react
https://davidwalsh.name/get-react-component-element
https://www.carlrippon.com/react-drop-down-data-binding/
What you seem to be wanting is similar to Angular's two way binding using #input #output.
What you can do is the following:
class MapControls extends React.Component{
constructor(props){
super(props); // needed
this.state = {...} // Your declared state above
this.handleChange = this.handleChange.bind(this);
}
handleChange(data){
// Here you should receive data change emitted from child components
}
render(){
...
<SelectBox id={selectBox.id} name={selectBox.name} selectBox={selectBox} onChange={this.handleChange}
}
}
Handle change listener should happen on the parent component, consider moving the fetch command to the parent instead. What you need to emit to parent is the event.target of the child
class SelectBox extends React.Component{
constructor(props) {
super(props);
this.state = { items: this.props.selectBox.items };
this.emitChange = this.emitChange.bind(this);
// Changed funciton's name to emitChange to avoid confusion
}
emitChange(event) {
const selectedValue = event.target.value;
const url = "/Home/" + event.target.dataset.childControllerMethod;
const data = JSON.stringify({ selectedValue: selectedValue });
fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: data
}).then(response => {
if (response.status >= 400) {
console.log("Bad response from server");
}
return response.json();
}).then(data => {
// While you could keep this here, it can be sent to parent, it's your decision
if(!!this.props.onChange){
// Here you'll emit data to parent via a props function
this.props.onChange(data);
}
});
}
render() {
const optionItems = this.state.items.map((item, index) =>
<option key={index} value={item.value} >{item.text}</option>
);
return <div>
<label htmlFor={this.props.selectBox.id} >{this.props.selectBox.title}</label>
<select onChange={this.emitChange} id={this.props.selectBox.id} ref={this.props.selectBox.child} /*data-child={this.props.selectBox.child}*/ data-child-controller-method={this.props.selectBox.childControllerMethod}>
{optionItems}
</select>
</div>
}
};
ReactDOM.render(<MapControls />,
document.getElementById('mapControls')
);
So, this is the general idea, you pass from parent a prop that's a function binded to it (parent), child will have a method that will execute the prop (if exists).
What I left out of this example:
You need to consider where to handle the fetch command accordingly (parent or child), remember that state defined in constructors is not updated if props change.
IF you want state to update on component's prop changes you'll have to use event cycles like "componentWillReceiveProps" (deprecated in recent version) or similar.
My general recommendation is child components should dwell on props, whereas parent component should handle state to be passed to child as props.
Passing function handles as props is a good way to intercommunicate your components, you could also use RXJS and pass Subscription types as props.
So the solution I found is as follows. Thank you to Gabriel for pointing me in the right direction. The final solution could be used for any filter component that needs to react to users' selections
I followed Gabriel's recommendation to call the parent's onChange event and handle the setting of the state in there.
I created the triggerSibling method so that I could hook into the componentDidUpdate() event and cascade the changes down the hierarchy of select boxes. So the onChange and componentDidMount events trigger the same logic.
Then in the MapControls onChange, I followed Gabriel's suggestion to handle the data there.
In the call to the parent's onChange event, I pass the data from the api call, along with the name of the child to target
The children of the parent component are accessible through this.refs and I discovered that I can access the specific child by using its name as a key in the array of children, as follows
this.refs[data.child].setState({ items: data.items })
I used the componentDidMount() event to set the initial value of the first selectBox and trigger the cascade of updates on the initial load
MapControls component:
class MapControls extends React.Component {
constructor(props) {
super(props); // needed
this.state = {
selectBoxes: [
{
id: 'WorkSource',
name: 'WorkSource',
title: 'Work Source',
containerId: 'WorkSourceContainer',
className: 'WorkSource',
childControllerMethod: 'GetBusinessTypeDropdown',
items: [{ value: 0, text: 'Select' }, { value: 'ROUTED', text: 'Routed' }],
child: 'BusinessType'
},
{
id: 'BusinessType',
name: 'BusinessType',
title: 'Business Type',
containerId: 'BusinessTypeContainer',
className: 'BusinessType',
childControllerMethod: 'GetWorkTypeDropdown',
items: [{ value: 0, text: 'Select' }],
child: 'WorkType'
},
... more ...
]
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(data) {
this.refs[data.child].setState({ items: data.items });
}
render() {
return this.state.selectBoxes.map(selectBox =>
<div key={selectBox.id} className='col-xs-2'>
<div id={selectBox.containerId}>
<SelectBox id={selectBox.id} name={selectBox.name} ref={selectBox.name} selectBox={selectBox} onChange={this.handleChange} />
</div>
</div>
);
}
};
SelectBox component:
class SelectBox extends React.Component{
constructor(props) {
super(props);
this.state = { items: this.props.selectBox.items };
this.emitChange = this.emitChange.bind(this);
}
triggerSibling (idOfDropdownToUpdate, selectedValue, url) {
const data = JSON.stringify({ selectedValue: selectedValue });
fetch(url, {
method: 'post',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: data,
}).then(response => {
if (response.status >= 400) {
console.log("Bad response from server");
}
return response.json();
}).then(data => {
if (!!this.props.onChange) {
// add the target to be updated as `child` property in the data passed to the parent
this.props.onChange({ child: this.props.selectBox.child, items: data });
}
});
}
componentDidMount() {
// Set the value of the first selectBox AFTER it has mounted, so that its `onChange` event is triggered and the `onChange` events cascade through the rest of the selectBoxes
if (this.props.name == "WorkSource") {
this.setState({ items: [{ value: 'ROUTED', text: 'Routed' }] });
}
}
// triggered when the component has finished rendering
componentDidUpdate(prevProps, prevState) {
const url = "/Home/" + this.props.selectBox.childControllerMethod;
if (this.props.selectBox.child != "")
this.triggerSibling(this.props.selectBox.child, this.state.items[0].value, url)
}
emitChange(event) {
const idOfDropdownToUpdate = event.target.dataset.child;
const selectedValue = event.target.value;
const url = "/Home/" + event.target.dataset.childControllerMethod;
this.triggerSibling(idOfDropdownToUpdate, selectedValue, url)
}
render()
{
const optionItems = this.state.items.map((item, index) =>
<option key={index} value={item.value} >{item.text}</option>
);
return <div>
<label htmlFor={this.props.selectBox.id} >{this.props.selectBox.title}</label>
<select onChange={this.emitChange} id={this.props.selectBox.id} data-child={this.props.selectBox.child} data-child-controller-method={this.props.selectBox.childControllerMethod}>
{optionItems}
</select>
</div>
}
};