setState is in use but DataTables doesn't refresh after Add or Edit function.
Please focus on:
listOfCurrency
Function ws
Function onClickSubmitAddCurrency
Component JSTable
Tag in JSTable.js
Currency.js
componentWillMount() {
this.setState(
{
listOfCurrency: [],
currency: { currency: '', symbol: '', status: '' },
myCurrencyRate: { exchangeRate: 0, adminFee: 0 },
currencyRateRequest:{exchangeRate:'',processFee:'',earnrate:'',earnAdminFee:''},
myCurrency: { currency:''},
isAdmin:false,
message: '',
snackbar: {
open: false,
vertical: null,
horizontal: null,
},
}
);
var checkAccessRightUrl=properties.domain+properties.checkAccessRightUrl;
this.wsCheckUrlAccess(checkAccessRightUrl,'Currency');
var wsListUrl = properties.domain + properties.currencyList;
this.ws(wsListUrl, false, false, 'get', null, false,'list');
var populateMyMembershipCurrencyRate = properties.domain + properties.populateMembeshipCurrencyRate;
this.wsbind(populateMyMembershipCurrencyRate,'currencyRate');
var myMembershipCurrency = properties.domain + properties.showMyMembershipCurrency;
this.wsbind(myMembershipCurrency,'myMembershipCurrency');
var checkIsAdminUrl = properties.domain + properties.populateCheckIsAdmin;
this.wsbind(checkIsAdminUrl,'checkIsAdmin');
}
ws(wsurl, jsonLibrary, toggle, process, senditem, snackbar,processName) {
var accessToken = sessionStorage.getItem('accessToken');
var reqs;
if (process === 'post') {
reqs = Request.post(wsurl)//wsurl
} else if (process === 'get') {
reqs = Request.get(wsurl)//wsurl
}
reqs.set('Authorization', 'Bearer ' + accessToken)
.set('Content-Type', 'application/json')
if (senditem != null) {
reqs.send(senditem)
}
reqs.end((err, res) => {
if (res.status === 200) {
if(processName === 'currencyRate'){
this.setState(
{
isLoading:false,
currencyRateRequest: res.body,
message: (res.body===null?'':res.body.message),
snackbar: {
vertical: 'top',
horizontal: 'right',
open: snackbar
},
}
);
}else{
this.setState(
{
isLoading:false,
listOfCurrency: (jsonLibrary ? JSON.parse(JSON.stringify(res.body.responses)) : res.body),
message: (res.body===null?'':res.body.message),
snackbar: {
vertical: 'top',
horizontal: 'right',
open: snackbar
},
}
);
}
if (toggle) {
this.togglePrimary();
}
} else {
this.checkSession(res);
console.log('do logout function here');
console.log(err);
}
});
}
onClickSubmitAddCurrency() {
var wsListUrl = properties.domain + properties.addCurrency;
this.ws(wsListUrl, true, true, 'post', this.state.currency, true);
};
render() {
return (
<div className="animated fadeIn">
<CardBody>
<JSTable id='#currencyTable'
dataSet={this.state.listOfCurrency} columns={columns}
onEdit={this.onClickGotoEdit.bind(this)} onDelete={this.onClickSubmitDeleteCurrency.bind(this)} />
</CardBody>
</div>
);
}
JSTable.js
import React, { Component } from 'react';
import { Card, CardBody, CardHeader, Col, Row,Button } from 'reactstrap';
import '../../css/jquery.dataTables.css';
import '../../css/dataTables.responsive.css';
const $ = require('jquery');
var ReactDOM = require('react-dom');
$.Datatable = require('datatables.net-responsive');
class JSTable extends Component {
constructor(props) {
super(props);
this.state = {
//ajaxurl : props.ajaxurl,
id : props.id,
dataSet : props.dataSet,
columns : props.columns,
onEdit : props.onEdit,
onDelete: props.onDelete,
onTopUp : props.onTopUp,
render : {
edit :props.onEdit==null?{display:'none'}:{display:''},
delete :props.onDelete==null?{display:'none'}:{display:''},
topup :props.onTopUp==null?{display:'none'}:{display:''},
},
indicator:{
edit :props.onEdit==null?true:false,
delete :props.onDelete==null?true:false,
topup :props.onTopUp==null?true:false,
}
};
}
componentDidMount() {
this.$el = $(this.el);
this.$el.DataTable({
// ajax: this.state.ajaxurl,
data: this.state.dataSet,
columns: this.state.columns,
responsive: true,
columnDefs: [{
targets: 0,
createdCell: (td, cellData, rowData, row, col) =>
ReactDOM.render(
<div>
<Button color="primary" style={this.state.render.edit}
onClick={this.state.indicator.edit?this.empty:this.state.onEdit.bind(this.celldata,this,rowData,this.row,this.col)}
className="mr-1">Edit</Button>
<Button color="primary" style={this.state.render.delete}
onClick={this.state.indicator.delete?this.empty:this.state.onDelete.bind(this.celldata,this,rowData,this.row,this.col)}
className="mr-1">Delete</Button>
<Button color="primary" style={this.state.render.topup}
onClick={this.state.indicator.topup?this.empty:this.state.onTopUp.bind(this.celldata,this,rowData,this.row,this.col)}
className="mr-1">Top Up</Button>
</div>, td
),
}
],
});
}
// componentWillUnmount() {
// this.$el.DataTable.destroy(true);
// }
empty(){
console.log('===========no function=================');
}
render() {
return (
<div className="animated fadeIn">
<table data={this.state.dataSet} className="display" width="100%" ref={el => this.el = el}>
</table>
</div>
);
}
}
export default JSTable;
I can get the update data. However, it doesn't update the table. Unless I refresh the page.
Please advise.
The simplest way to refresh or rerender component is a key.
Your key can be your value in your state
state={counter:null}
Then when data is loaded from your API
Use setState and increment your counter
<table key={this.state.counter}>
//your code
</table>
React Current Image in Image Gallery
You can use this.forceUpdate() for rerender in reactJs.
Related
I'm starting with React and this problem has taken my sleep away.
From what I understand, the state (this.state) is only accessible in the component in which it was created, but in my project this is not happening. A child component is able to identify changes made to the state of the parent component, and this has slowed my application, let me try to explain it better with the code:
In the parent component, I render the page title, a modal for adding new records and include the child component responsible for assembling a datatable (provided by MUI-Datatable and data loaded via API):
https://github.com/gregnb/mui-datatables
Parent component:
import React from 'react';
import Typography from '#material-ui/core/Typography';
import DialogContent from '#material-ui/core/DialogContent';
import DialogTitle from '#material-ui/core/DialogTitle';
import Button from '#material-ui/core/Button';
// Components
import CadcliFieldContent from './CadcliFieldContent';
class CadcliField extends React.Component {
state = {
isLoading: false,
isNew: true,
dialog: false,
formData: {
name: 'Test'
}
};
openDialog = () => { this.setState({ dialog: true }); }
closeDialog = () => { this.setState({ dialog: false }); }
formDataChange = name => event => {
var formDataTemp = this.state.formData;
formDataTemp[name] = event.target.value;
this.setState({ formData: formDataTemp });
};
render () {
return (
<div className="flex flex-1 w-full">
<div className="flex items-center justify-end">
<Button
className="normal-case"
variant="contained"
color="secondary"
role="button"
onClick={this.openDialog}
>
<Icon>add</Icon>
<span className="mx-1">{t('NEW_FIELD')}</span>
</Button>
</div>
<CadcliFieldContent
urlBase="cadcli-field/"
/>
<Dialog
className='max-w-lg w-full m-24'
onClose={this.closeDialog}
open={this.state.dialog}
>
<DialogTitle component="div" className="p-0">
<Typography className="text-16 ml-8">Fields</Typography>
</DialogTitle>
<DialogContent className="p-16 sm:p-24">
<div className="flex items-center mb-24">
<TextField
type="text"
name="name"
id="input-name"
value={formData.name}
onChange={this.formDataChange('name')}
variant="outlined"
fullWidth
required
/>
</div>
</DialogContent>
</Dialog>
</div>
);
};
}
export default CadcliField;
Child component
import React from 'react';
import axios from 'axios';
import MUIDataTable from "mui-datatables";
import { CircularProgress, Typography } from '#material-ui/core';
class CadcliContent extends React.Component {
state = {
urlApi: process.env.REACT_APP_API_URL,
page: 0,
count: 1,
rowsPerPage: 10,
denseTable: true,
idDeleteModal: 0,
sortOrder: {},
filter: {},
search: '',
data: [['Loading...']],
columns: [
{name: 'id', label: this.props.t('utils:ACTIONS')},
{name: 'id', label: 'ID', options: { filterType : 'textField' }},
],
isLoading: false,
};
componentDidMount() {
this.getData(this.state.urlApi, 0);
}
// get data
getData = async (url, page) => {
this.setState({ isLoading: true });
const res = await this.axiosRequest(url, page);
this.setState({ data: res.data, isLoading: false, count: res.total });
};
axiosRequest = (url, page, sortOrder = {}, filter = {}, searchText = '') => {
url = url + '?page_size=' + this.state.rowsPerPage;
url += '&page=' + (page + 1);
// Ordering
if (sortOrder.name !== undefined) {
if (sortOrder.direction === 'asc') {
url += '&ordering=' + sortOrder.name;
} else {
url += '&ordering=-' + sortOrder.name;
}
}
// Filtering
if (filter.length > 0) {
var columnsTable = this.state.columns;
filter.forEach(function(value, key){
if (value.length > 0) {
url += '&' + columnsTable[key].name + '=' + value;
}
});
}
// Generic Searching
if (searchText !== "" && searchText !== null) {
url += '&search=' + searchText;
}
return new Promise((resolve, reject) => {
axios
.get(url)
.then(response => {
resolve({
data: response.data.results,
page: page,
total: response.data.count,
});
}).catch(function (error) {
resolve({
data: [],
page: 0,
total: 0,
});
});
});
};
sort = (page, sortOrder, filter) => {
this.setState({ isLoading: true });
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
data: res.data,
page: res.page,
sortOrder,
filter,
isLoading: false,
count: res.total,
});
});
};
changePage = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
filterChange = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
searchTable = (page, sortOrder, columnsFilter, searchText) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, columnsFilter, searchText).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter: columnsFilter,
search: searchText,
data: res.data,
count: res.total,
});
});
}
render() {
const { data, count, isLoading, rowsPerPage, sortOrder } = this.state;
const options = {
filter: true,
filterType: 'dropdown',
responsive: 'vertical',
serverSide: true,
download: false,
count: count,
rowsPerPage: rowsPerPage,
rowsPerPageOptions: [],
sortOrder: sortOrder,
resizableColumns: false,
confirmFilters: true,
onTableChange: (action, tableState) => {
switch (action) {
case 'changePage':
this.changePage(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'sort':
this.sort(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'search':
this.searchTable(tableState.page, tableState.sortOrder, tableState.filterList, tableState.searchText);
break;
default:
console.log('[' + action + '] action not handled.');
}
},
setTableProps: () => {
return {
size: this.state.denseTable ? 'small' : 'medium'
};
},
};
return (
<div className="w-full flex flex-col">
<MUIDataTable
title={
<Typography variant="h6">
Listing
{isLoading && <CircularProgress size={24} />}
</Typography>
}
data={data}
columns={this.state.columns}
options={options}
/>
</div>
);
}
}
export default CadcliContent;
What happens:
Every time I change the state in the parent component, for example, type in TextField and change the this.state.formData.name property, or click the button to open the modal (change this.state.dialog) the onTableChange function of the MUI Datatables is triggered and generates a processing in my table, I realize this because I put a console.log () to inform this:
onTableChange: (action, tableState) => {
switch (action) {
/* .... */
default:
console.log('[' + action + '] action not handled.');
}
},
(Excerpt from the code where console.log is triggered)
This disturbs me because MUI-Datatable understands that it has changed something in this.state and does a very heavy processing, even leaving the processing of typing in the delayed input.
Any idea what might be going on?
The solution was simpler than I imagined, and I ended up solving it by studying and understanding the React state concept.
Even though I still didn't understand how the MUI-Datables component observed the entire state, the simple fact of encapsulating the Dialog inside a new component, with a new isolated state, the magic happened.
So if someone goes through the same problem the solution was this:
CadcliField.js
<CadcliFieldContent
urlBase="cadcli-field/"
{...this.props}
/>
<CadcliFieldFormNew
isNew={this.state.isNew}
dialog={this.state.dialog}
closeDialog={this.closeDialog}
/>
I am having a callback function which updates my array in the SetState and it works fine as I can see in the console log in the handleDaysValueChange all is good. But when I try to access it in another function i.e handleValuesChange the values are not up to date. I am missing a async or something.
import React from 'react';
import PropTypes from 'prop-types';
import AppService from 'services/app-service';
import Enum from 'enum';
import { ApiData } from 'data';
import { isEmpty, boolValue } from 'core/type-check';
import { notifySuccess } from 'core/errors';
import { withDrawerForm } from 'components/_hoc';
import { ExtendedAvatar } from 'components/extended-antd';
import { Tabs } from 'antd';
import { FormTemplateAudit } from '../templates';
import ShiftRosterFormDetails from './shiftroster-form-details';
import Item from 'antd/lib/list/Item';
const { TabPane } = Tabs;
class ShiftRosterForm extends React.Component {
constructor(props) {
super(props);
this.formInputRef = React.createRef();
this.state = {
daysValue:[]
};
this.handleDaysValueChange = this.handleDaysValueChange.bind(this);
}
handleDaysValueChange(daysValues) {
this.setState({ daysValue: daysValues }, () => {
console.log("Data" + this.props.customData);
console.log("Hello World " + JSON.stringify(this.state.daysValue));
});
}
componentDidMount() {
// Update the parent form state with a reference to the
// formInputRef so we can call this later for validation
// before saving the data to the server.
const { onFormStateChange } = this.props;
onFormStateChange({ formInputRef: this.formInputRef.current });
}
//Shift Roster Detail
handleValuesChange = (props, changedValues, allValues) => {
// Update the parent form state with the changed item
// details and mark the form as dirty
const { itemData, onFormStateChange } = this.props;
console.log("Hey" + this.state.daysValue);
onFormStateChange({
isFormDirty: true,
itemData: {
...itemData,
...changedValues,
...this.state.daysValue
}
});
}
render() {
const { itemData, customData } = this.props;
const isSR = (!isEmpty(itemData) && itemData.id > 0);
return (
<Tabs className="bhp-tabs" defaultActiveKey="1" animated={false}>
<TabPane key="1" tab="Details">
<ShiftRosterFormDetails
ref={this.formInputRef}
dataSource={itemData}
onValuesChange={this.handleValuesChange}
handleDaysValueChange={this.handleDaysValueChange}
/>
</TabPane>
<TabPane key="2" tab="Audit" disabled={!isSR}>
<FormTemplateAudit
itemData={itemData}
/>
</TabPane>
</Tabs>
);
}
}
ShiftRosterForm.propTypes = {
itemId: PropTypes.number, // Passed in by the HOC. The loaded Shift Roster id
itemData: PropTypes.object, // Passed in by the HOC. The loaded Shift Roster data
customData: PropTypes.object, // Temporary store to hold the changed Shift Roster
isFormDirty: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is dirty
isLoading: PropTypes.bool, // Passed in by the HOC. Flags if the parent form is loading
daysValue: PropTypes.object,
onFormStateChange: PropTypes.func // Passed in by the HOC. Callback to update the parent form state.
};
ShiftRosterForm.defaultProps = {
itemId: -1,
itemData: {},
customData: {},
isFormDirty: false,
isLoading: false,
daysValue: {},
onFormStateChange() { }
};
const ShiftRosterFormTitle = ({ data }) => {
const name = (!isEmpty(data) && data.id > 0) ? `${data.name}` : 'New Shift Roster';//`${data.name}`
return isEmpty(data)
? <ExtendedAvatar type="icon" size="large" />
: <span><ExtendedAvatar name={name} type="letter" size="large" />{name}</span>
}
const saveShiftRoster = (shiftrosterId, shiftroster, rosterdays) => {
return ApiData.saveShiftRoster(shiftrosterId, shiftroster, rosterdays)
.then(response => {
notifySuccess('Save Successful', 'Site Work Package saved successfully');
return response;
})
.catch(error => {
throw error;
});
}
const saveForm = (formState, setFormState) => {
const { isFormDirty, itemData, customData, formInputRef } = formState;
const typeName = "Dynamic";
const actualType = itemData.type;
let rosterdays = [];
if (actualType !== typeName) {
rosterdays = GetDaysForRoster(itemData);
console.log("My Values" + JSON.stringify(rosterdays));
}
const shiftRosterId = itemData.id;
const isExistingShiftRoster = shiftRosterId > 0;
return new Promise((resolve, reject) => {
if (isExistingShiftRoster && !isFormDirty) {
// No Changes
notifySuccess('Save Successful', 'Site Work Package saved successfully');
resolve(itemData);
}
else {
// Validate and Save
formInputRef.validateFields((error, values) => {
if (!error) {
// Form validated successfully.
// Save form changes
const shiftrosterRecord = saveShiftRoster(shiftRosterId, values, rosterdays);
resolve(shiftrosterRecord);
}
else {
// Form validation error.
// Return data as is.
resolve(itemData);
}
});
}
});
}
const GetDaysForRoster = (itemsData) => {
const result = [];
const keys = Object.keys(itemsData);
for (const k in keys) {
if (Number(k) == k) {
result[k] = itemsData[k]
}
}
return result.filter(function (el) { return el != null });
}
const WrappedShiftRosterForm = withDrawerForm({
containerClassName: 'bhp-equipment-type-form',
title: (record) => <ShiftRosterFormTitle data={record} />,
onLoad: (itemId, setFormState) => ApiData.getShiftRoster(itemId),
onSave: (formState, setFormState) => { return saveForm(formState, setFormState); },
canView: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Read] }),
canCreate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Create] }),
canUpdate: () => AppService.hasAccess({ [Enum.SecurityModule.EquipmentTypeDetails]: [Enum.SecurityPermission.Update] })
})(ShiftRosterForm);
WrappedShiftRosterForm.propTypes = {
containerClassName: PropTypes.string,
itemId: PropTypes.number,
visible: PropTypes.bool,
onSave: PropTypes.func,
onClose: PropTypes.func
};
WrappedShiftRosterForm.defaultProps = {
containerClassName: null,
itemId: -1,
visible: false,
onSave() { },
onClose() { }
};
export default WrappedShiftRosterForm;
//ShiftRosterFormDetails
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ApiData } from 'data';
import { Form, Input, Select, Button, space, InputNumber } from 'antd';
import ShiftDays from './shiftdays'
const ShiftRosterFormDetails = ({ form, dataSource, onValueChange, handleDaysValueChange }) => {
const { getFieldDecorator } = form;
const formLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 6 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
console.log("My datasource" + dataSource.shiftRosterDays);
//const daysRoster = dataSource.shiftRosterDays || [{ daysIn: 1, daysOut: 1, category: "Day Shift" }];
const [inputList, setInputList] = useState([{ daysIn: 1, daysOut: 1, category: "Day Shift" }]);
const [selectedType, setSelectedType] = useState(dataSource.type || 'Fixed Single');
const [isTotalDaysRequired, SetTotalDaysRequired] = useState(dataSource.type === 'Dynamic' ? true : false);
const [isTotalDaysRequiredMessage, setIsTotalDaysRequiredMessage] = useState(dataSource.type === 'Dynamic' ? 'Please enter the Total Days' : '');
const handleTypeChanged = (value, e) => {
setSelectedType(value);
if (value === "Dynamic") {
SetTotalDaysRequired(true);
setIsTotalDaysRequiredMessage('Please enter the Total Days');
}
if (value === "Fixed Single") {
if (inputList.length > 1) {
const list = [...inputList];
console.log("Total" + inputList.length);
list.splice(1, inputList.length);
setInputList(list);
console.log("My List" + JSON.stringify(list));
console.log("Input List" + JSON.stringify(inputList));
handleDaysValueChange(list);
}
}
else {
SetTotalDaysRequired(false);
setIsTotalDaysRequiredMessage('');
}
};
return (
<div className='bhp-equipment-type-form-details bhp-content-box-shadow'>
<Form {...formLayout}>
<Form.Item label='Name' hasFeedback>
{getFieldDecorator('name', {
initialValue: dataSource.name,
rules: [{
required: true,
message: 'Please enter the Name'
}],
})(
//disabled={dataSource.id > 0}
<Input placeholder='Name' />
)}
</Form.Item>
<Form.Item label='Status' hasFeedback>
{getFieldDecorator('status', {
initialValue: dataSource.status,
rules: [{
required: true,
message: 'Please enter the Status'
}],
})(
<Select>
<Select.Option value="Active">Active</Select.Option>
<Select.Option value="InActive">InActive</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Type' hasFeedback>
{getFieldDecorator('type', {
initialValue: dataSource.type || 'Fixed Single',
rules: [{
required: true,
message: 'Please select the Type'
}],
})(
<Select onChange={handleTypeChanged}>
<Select.Option value="Fixed Single">Fixed Single</Select.Option>
<Select.Option value="Fixed Multiple">Fixed Multiple</Select.Option>
<Select.Option value="Dynamic">Dynamic</Select.Option>
</Select>
)}
</Form.Item>
<Form.Item label='Total Days' hasFeedback style={selectedType === 'Dynamic' ? { display: '' } : { display: 'none' }}>
{getFieldDecorator('totaldays', {
initialValue: dataSource.totalDays,
rules: [{
required: isTotalDaysRequired,
message: isTotalDaysRequiredMessage
}],
})(
<InputNumber min={1} max={365} />
)}
</Form.Item>
<ShiftDays inputList={inputList} setInputList={setInputList} selectedType={selectedType} handleDaysValueChange={handleDaysValueChange} getFieldDecorator={getFieldDecorator} />
</Form>
</div>
)};
const onFieldsChange = (props, changedFields, allFields) => {
if (props.onFieldsChange) {
props.onFieldsChange(props, changedFields, allFields);
}
};
const onValuesChange = (props, changedValues, allValues) => {
if (props.onValuesChange) {
props.onValuesChange(props, changedValues, allValues);
}
};
ShiftRosterFormDetails.propTypes = {
form: PropTypes.object,
dataSource: PropTypes.object,
onFieldsChange: PropTypes.func,
onValuesChange: PropTypes.func
};
ShiftRosterFormDetails.defaultProps = {
form: {},
dataSource: {},
onFieldsChange() { },
onValuesChange() { }
};
export default Form.create({
onValuesChange,
onFieldsChange
})(ShiftRosterFormDetails);
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
}
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 })
}
});
}
}
Trying to create a list of todos using react-infinite-scroller. The list will display 20 todos, the next todos will be displayed while scrolling. I fetch Todos from 'https://jsonplaceholder.typicode.com/todos'. The fetched todos are saved in the todos variable.
I've modeled this example: https://github.com/CassetteRocks/react-infinite-scroller/blob/master/docs/src/index.js.
Demo here: https://cassetterocks.github.io/react-infinite-scroller/demo/.
I can not see Loading.. appearing and fetching further tasks.
Code here: https://stackblitz.com/edit/react-tcm9o2?file=index.js
import InfiniteScroll from 'react-infinite-scroller';
import qwest from 'qwest';
class App extends Component {
constructor (props) {
super(props);
this.state = {
todos: [],
hasMoreTodos: true,
nextHref: null
}
}
loadTodos(page){
var self = this;
const url = 'https://jsonplaceholder.typicode.com/todos';
if(this.state.nextHref) {
url = this.state.nextHref;
}
qwest.get(url, {
linked_partitioning: 1,
page_size: 10
}, {
cache: true
})
.then((xhr, resp) => {
if(resp) {
var todos = self.state.todos;
resp.map((todo) => {
todos.push(todo);
});
if(resp.next_href) {
self.setState({
todos: todos,
nextHref: resp.next_href
});
} else {
self.setState({
hasMoreTodos: false
});
}
}
});
}
render () {
const loader = <div className="loader">Loading ...</div>;
console.log(this.state.todos);
var items = [];
this.state.todos.map((todo, i) => {
items.push(
<div className="track" key={todo.id}>
<p className="title">{todo.title}</p>
</div>
);
});
return (
<InfiniteScroll
pageStart={0}
loadMore={this.loadTodos.bind(this)}
hasMore={this.state.hasMoreTodos}
loader={loader}>
<div className="tracks">
{items}
</div>
</InfiniteScroll>
)
}
}
UPDATED MY QUESTION
How can I set these places commented in the code?
/*if(this.state.nextHref) {
url = this.state.nextHref;
}*/
and
/*if(resp.next_href) {
self.setState({
todos: todos,
nextHref: resp.next_href
});*/
How can I set the page to change from 1 to 2, next from 2 to 3?
class App extends Component {
constructor (props) {
super(props);
this.state = {
todos: [],
hasMoreTodos: true,
page: 1
}
}
loadTodos(page){
var self = this;
const url = `/api/v1/project/tasks?expand=todos&filter%5Btype%5D=400&${page}&${per_page}`;
/*if(this.state.nextHref) {
url = this.state.nextHref;
}*/
axios.get(url, {
'page': 1,
'per-page': 10
})
.then(resp => {
if(resp) {
var todos = [self.state.todos, ...resp];
/*if(resp.next_href) {
self.setState({
todos: todos,
nextHref: resp.next_href
});*/
} else {
self.setState({
hasMoreTodos: false
});
}
}
});
}
render () {
const loader = <div className="loader">Loading ...</div>;
var divStyle = {
'width': '100px';
'height': '300px';
'border': '1px solid black',
'scroll': 'auto'
};
const items = this.state.todos.map((todo, i) =>
<div className="track" key={todo.id}>
<p className="title">{todo.title}</p>
</div>);
return (
<div style={divStyle}>
<InfiniteScroll
pageStart={1}
loadMore={this.loadTodos.bind(this)}
hasMore={this.state.hasMoreTodos}
loader={loader}>
<div className="tracks">
{items}
</div>
</InfiniteScroll>
</div>
)
}
}
UPDATED 2
Code here: https://stackblitz.com/edit/react-4lwucm
import InfiniteScroll from 'react-infinite-scroller';
import axios from 'axios';
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
hasMoreTodos: true,
page: 1,
nextPage: 1,
finishedLoading: false
};
}
loadTodos = (id) => {
if(!this.state.finishedLoading) {
const params = {
id: '1234',
page: this.state.nextPage,
'per-page': 10,
}
axios({
url: `/api/v1/project/tasks`,
method: "GET",
params
})
.then(res => {
let {todos, nextPage} = this.state;
if(resp) { //resp or resp.data ????
this.setState({
todos: [...todos, ...resp], //resp or resp.data ????
nextPage: nextPage + 1,
finishedLoading: resp.length > 0 //resp or resp.data ????
});
}
})
.catch(error => {
console.log(error);
})
}
}
render() {
const loader = <div className="loader">Loading ...</div>;
const divStyle = {
'height': '300px',
'overflow': 'auto',
'width': '200px',
'border': '1px solid black'
}
const items = this.state.todos.map((todo, i) =>
<div className="track" key={todo.id}>
<p className="title">{todo.title}</p>
</div>);
return (
<div style={divStyle} ref={(ref) => this.scrollParentRef = ref}>
<div>
<InfiniteScroll
pageStart={0}
loadMore={this.loadTodos}
hasMore={true || false}
loader={<div className="loader" key={0}>Loading ...</div>}
useWindow={false}
getScrollParent={() => this.scrollParentRef}
>
{items}
</InfiniteScroll>
</div>
</div>
);
}
}
EDIT: I just realized you used this as a starting point:
Why you get everything at once
Well, because your source is just a big JSON.
The logic in the script you're using is adapted to SoundCloud's API, which paginates (aka. returns chunk-by-chunk) the enormous list.
Your API doesn't paginate, hence why you're receiving everything at once.
See an example result from the SoundCloud API:
{
"collection": [
{
"kind": "track",
"id": 613085994,
"created_at": "2019/04/29 13:25:27 +0000",
"user_id": 316489116,
"duration": 476281,
[...]
},
[...]
],
"next_href": "https://api.soundcloud.com/users/8665091/favorites?client_id=caf73ef1e709f839664ab82bef40fa96&cursor=1550315585694796&linked_partitioning=1&page_size=10"
}
The URL in next_href gives the URL of the next batch of items (the URL is the same but the 'cursor' parameter is different each time). That's what makes the infinite list work: each time, it gets the next batch.
You have to implement server-side pagination to achieve this result.
In response to your edit:
UPDATED MY QUESTION
How can I set these places commented in the code?
/*if(this.state.nextHref) {
url = this.state.nextHref; }*/
and
/*if(resp.next_href) { self.setState({
todos: todos,
nextHref: resp.next_href });*/
Forget about loading the nextHref, you don't have that. What you have is the current page number (up to which page you have loaded the contents).
Try something along the lines of:
if (!self.state.finishedLoading) {
qwest.get("https://your-api.org/json", {
page: self.state.nextPage,
'per-page': 10,
}, {
cache: true
})
.then((xhr, resp) => {
let {todos, nextPage} = self.state;
if(resp) {
self.setState({
todos: [...todos, ...resp],
nextPage: nextPage + 1,
finishedLoading: resp.length > 0, // stop loading when you don't get any more results
});
}
});
}
A few nitpicks
Here you are mutating self.state (bad), in addition to using Array.map with a side-effect (and for that side-effect only) :
var todos = self.state.todos;
resp.map((todo) => {
todos.push(todo);
});
You could write it as:
var todos = [self.state.todos, ...resp];
Same usage of map here:
var items = [];
this.state.todos.map((todo, i) => {
items.push(
<div className="track" key={todo.id}>
<p className="title">{todo.title}</p>
</div>
);
});
Should be written:
const items = this.state.todos.map((todo, i) =>
<div className="track" key={todo.id}>
<p className="title">{todo.title}</p>
</div>);