Fetch data periodically from server and re-render view in reactjs - reactjs

I want to fetch data from server periodically and refresh rows when data is fetched by setState() but the rows doesn't re-render after setState().
constructor(props) {
super(props);
this.state = {
rows: []
}
this.refreshList = this.refreshList.bind(this);
}
refreshList() {
req.get('/data').end(function (error, res) {
// type of res is array of objects
this.setState({
rows: res
});
});
}
// call this method on button clicked
handleClick() {
this.refreshList();
}
render() {
return(
<div>
<button onClick={this.handleClick}>Refresh List</button>
<Table rows={this.state.rows}/>
</div>
);
}
when call refreshList() new feteched data doesn't render.
My table component is:
// Table component
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Thanks a lot for your help. How can I refresh list on click button?

Your table component never changes its state after the construction. You can fix it easily by updating the state from new props:
export class Table extends Component {
constructor(props) {
super(props);
this.state = {
rows: props.rows
}
}
componentWillReceiveProps(newProps) {
this.setState({
rows: newProps.rows
});
}
render() {
return (
<div>
{this.state.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
However, if your table component is so simple, you can make it stateless and use props directly without setState():
export class Table extends Component {
render() {
return (
<div>
{this.props.rows.map((row, i) => (
<div>{row.title}</div>
))}
</div>
)
}
}
Note there is no need for constructor now. We could actually make it a functional component.

Use an arrow function:
req.get('/data').end((error, res)=> {
// type of res is array of objects
this.setState({
rows: res
});
});
With the ES5 style callback function, the context of this gets lost.
You could also bind this directly to a local variable, i.e., var that = this and stick with the function syntax, but I think most would agree what the ES6 arrow syntax is nicer.

Related

Add component on button click

I am making a front end application using typescript and react. I have a component A which amongst other html elements has a textbox. I want to add this component A on click of a button. So if the user clicks the button multiple times, i want a new component A to be created on every click. Also I want to be able to store the text data so that I can later fetch it and process it.
I tried to make a list of this component but it gives me an error.
interface State {
componentList?: ComponentA[];
}
export class ComponentList extends React.Component<Props, State> {
  constructor(props) {
    super(props);
    this.onClick = this.onClick.bind(this);
  }
public onClick(event) {
const componentList = this.state.componentList;
this.setState({
componentList: componentList.concat(<ComponentA key=
{componentList.length} />)
});
}
  public render() {
    return (
      <React.Fragment>
        <button onClick={this.onClick}>Add component</button>
{this.state.componentList.map(function(component, index)
{
return ComponentA
})}
      </React.Fragment>
    );
  }
}
You might want to make two changes in your code.
First initialise your state in the constructor,
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = { componentList: [] }
}
So that react can track that data.
Second thing is, you are returning wrong item from the map in the render function.
Try returning component, which is different copies of <ComponentA ../> that you pushed every time you clicked the button,
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.state.componentList.map(function(component, index)
{
return component;
})}
</React.Fragment>
);
}
Keep the component count in the state:
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
numComponents: 0
}
}
Add a new function which creates an array of the component for rendering later:
clickedComponents = () => {
let componentArray = [];
for (let i=0; i<this.state.numComponents; i++) {
componentArrays.push(<ComponentA />);
}
return componentArray;
}
Increment the component count with your onClick function:
public onClick(event) {
this.setState({numComponents: this.state.numComponents + 1});
}
Render the component array:
public render() {
return (
<React.Fragment>
<button onClick={this.onClick}>Add component</button>
{this.clickedComponents()}
</React.Fragment>
);
}

Call a function on application startup in react

I'm trying to call a function from application startup. The function reads data from JSON via dataVar (set elsewhere) and tries to load it into {items} for further consumption:
const dataVar = JSONStuff;
class Global extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
items: []
}
this.init();
}
// componentDidMount() {
// This doesn't work either!
// this.init();
// }
init() {
let { items } = dataVar;
this.setState({items});
}
render() {
return (
<div className="Global">
<Gallery items={this.state.items}/>
</div>
)
}
}
Then in Gallery.js:
import React, { Component } from 'react';
class Gallery extends Component {
render() {
return (
<div>
<h3>gallery:</h3>
{
this.props.items.map((item, index) => {
let {title} = item.name;
return (
<div key={index}>{title}</div>
)
})
}
</div>
)
}
}
export default Gallery;
Not sure why Global can't call a function inside of itself. I've tried with and without "this." I either get error to where the app won't complile or I get:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
First of all, it's a warning, you probably better not call setState in componentDidMount.
My suggestion 1: assign value to state in constructor
constructor(props) {
super(props);
this.state = {
query: '',
items: dataVar.items,
};
}
Suggestion 2:
Do inside the componentWillReceiveProps
componentWillReceiveProps(nextProps) {
const { dataVar: items } = nextProps; // pass dataVar as props
this.setState({
items,
});
}
Plus try to debug your props and pay attention on your console for errors.

Re-rendering react-redux component

I am using react-redux code structure and this is my first try with react-redux.I have cloned a github repository from Here and started editing it.
My directory structure:
Here schema is parent component and header and tables are 2 child components.Tables is showing data from localstorage through redux store.
Initializing store:
const initialState = JSON.parse(window.localStorage.getItem('test'));
const store = createStore(Reducers, initialState, compose(applyMiddleware(...middleware), extension));
Now an event is triggered from Header and sent to schema and in the response of this event schema is updating localstorage by requesting to server and saving server's response in localstorage as follows:
Schema.js:
class Schema extends PureComponent {
constructor(props) {
super(props);
this.table = '';
getTables();
}
myCallback = () => {//triggered by child(Header)
getTables();
}
getTables = () => {
axios.get(url)
.then((response) => {
if(response.data.status==0){
window.localStorage.setItem('test',JSON.stringify(response.data));
this.tables=JSON.parse(window.localStorage.getItem('test'))
});
}
render() {
console.log(this.tables);//this is giving updated value at each updation
return (
<div className='container-fluid'>
<Header callbackFromParent={ this.myCallback } />
<br />
<br />
<Tables val={ this.tables } />
</div>
);
}
}
Here is code for Tables.js:
class Tables extends Component {
props: Props
render() {
let {tables,val } = this.props;
console.log(JSON.parse(window.localStorage.getItem('test')));//this is giving updated value at each updation in localstorage
console.log(val);//this is also giving updated value at each updation in localstorage
tables=val;
console.log(tables);this is also updating in realtime.
return (
<div className='table-wrapper'>
{ tables.map((table) => (
<Table
key={ table.id }
data={ table }
/>
))}
</div>
);
}
}
type Props = {
tables: Array<TableType>
};
The issue is whenever header triggers callback, schema updates value of localstorage, this updation also re render Tables component. Also an updated value can be seen in render of Tables component but the tables which are shown are from previous saved value. To get current value in tables, we need to refresh the page.
is it a mistake in code flow or i need something else for this?
The idea is that react will trigger rendering of component whenever the component state or props is updated.
If the component props are updated in parent component you will still need to update the component state to make the next render in inner component
The key of this is using componentWillReceiveProps
I updated your code with the code below:
Basically i did the following:
1- I used component state for Schema, Tables, and Table
2- I used this.setState whenever i need to make updates to state (this is very important)
3- I make sure that when a component props is updated in parent i update the component state as well using componentWillReceiveProps and this will make the new render with updated data
Schema component:
class Schema extends Component {
constructor(props) {
super(props);
this.state = { tables : { } }
this.getTables = this.getTables.bind(this);
this.myCallback = this.myCallback.bind(this);
}
componentWillMount(){
this.getTables();
}
myCallback = () => {
//triggered by child(Header)
this.getTables();
}
getTables = () => {
axios.get(url)
.then((response) => {
if(response.data.status==0)
{
window.localStorage.setItem('test',JSON.stringify(response.data));
this.setState({
tables : JSON.parse(window.localStorage.getItem('test'))
});
}
);
}
render() {
//this is giving updated value at each updation
console.log(this.state.tables);
return (
<div className='container-fluid'>
<Header callbackFromParent={ this.myCallback } />
<br />
<br />
<Tables tables={this.state.tables} />
</div>
);
}
}
Tables Component
class Tables extends Component {
constructor(props) {
super(props);
this.state = { tables : { } }
}
componentWillMount(){
this.setState({
tables : this.props.tables
})
}
componentWillReceiveProps(nextProps){
this.setState({
tables : nextProps.tables
})
}
render() {
console.log(JSON.parse(window.localStorage.getItem('test')));//this is giving updated value at each updation in localstorage
console.log(this.state.tables);//this is also giving updated value at each updation in localstorage
return (
<div className='table-wrapper'>
{ this.state.tables.map((table) => (
<Table key={ table.id } data={ table } />
))
}
</div>
);
}
}
And finally a dummy Table component to show that you will also need to handle props update using componentWillReceiveProps to make sure each individual table component did render after props update
And probably this is where you have the issue ... because the tables are showing old data but the console.log of Tables component is logging new data which means Each individual Table component is not rending after the update
class Table extends Component {
constructor(props) {
super(props);
this.state = { data : { } }
}
componentWillMount(){
this.setState({
data : this.props.data
})
}
componentWillReceiveProps(nextProps){
this.setState({
data : nextProps.data
})
}
render() {
console.log(this.state.data);
return (
<table className='table'>
{this.state.data}
//use your table data here
</table>
);
}
}
Important Edit:
As mentioned by react documentation componentWillReceiveProps might get called even if the props have not changed, thats why in some situation you might need to consider comparing this.props with nextProps to make sure that you really got new updated props and based on that you update the component state ....

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

Get the object clicked from the child component in React

I'd like to trigger the function handleDisplayProduct on click and pass to it the object clicked. So far it calls the function handleDisplayProduct when the list is generated for all the objects but the function is not triggered on the click event.
So how do i bind the event onclick with the Container and passing to it the element clicked?
Container
// Method to retrieve state from Stores
function getAllProductState(){
return {
products: ProductStore.getProducts(),
};
}
export default class ProductAllContainer extends Component {
constructor(props){
super(props);
this.state = getAllProductState();
}
handleDisplayProduct(data){
console.log(data);
// ProductActions.selectProduct(data)
}
render(){
const products = this.state.products;
return(
<div>
{ products.map(function(product,i){
return (
<ProductThumbnail
product = { product }
key = { i }
**onDisplayProduct = { this.handleDisplayProduct(product) }**
/>
)
},this)}
</div>
)
}
}
View
const ProductThumbnail = (props)=>{
return(
<div>
<LinksTo to="/" **onClick={props.onDisplayProduct}**>
<h1>{props.product.headline}</h1>
<img src={props.product.images[0].imagesUrls.entry[1].url} alt="Thumbnail small pic"/>
</LinksTo>
</div>
)
}
You need to bind the event listener to the react class. You can do it this way.
constructor(props){
super(props);
this.state = getAllProductState();
this.handleDisplayProduct = this.handleDisplayProduct.bind(this);
}
or alternatively using es6, you can use an arrow function instead.
handleDisplayProduct = (data) => {
console.log(data);
// ProductActions.selectProduct(data)
}
Note: Class properties are not yet part of current JavaScript standard. So the second example wouldn't work unless you add a babel-plugin-transform-class-properties babel plugin
Edit: Also #ryanjduffy pointed out a crucial mistake in your code. Refer his comment.

Resources