I have an Ul component which contains an array of objects with different values in it. Each item called TestCase in this list has a button which makes a restcall and updates its object. However not all TestItems need to be updated. Only those whose button are clicked. The state of this array is stored in a parent continer component called TestCaseContainer. My button will updates the state accordingly to the effected TestItem in the array, however. This causes the whole list to rerender. How can I only have the changed TestItems rendered, instead of having the entire ul rendered every time an element is updated. I read about using useMemo so the component can memoize the passed down props, however I don't know how to implement this properly.
How can I stop all the rerenders?
Regression.js - Holds all the state
const Testing = forwardRef((props,ref) => {
const templateTestItem = {id:0,formData:{date:'',env:'',assetClass:'',metric:'',nodeLevel:'',nodeName:'',testName:'',dataType:'',tradeId:''},results:[],isLoading:false}
const testCaseRef = useRef()
const [isRun, setIsRun] = useState(false)
const [testItems, setTestItems] = useState([ templateTestItem])
const [stats,setStats] = useState(null)
const addTestItem = () => {
const newIndex = testItems.length
// console.log(newIndex)
const templateTestItem = {id:newIndex,formData:{date:'',env:'',assetClass:'',metric:'',nodeLevel:'',nodeName:'',testName:'',dataType:'',tradeId:''},results:[],isLoading:false}
setTestItems([...testItems, templateTestItem])
}
const addUploadCases = (cases) => {
setTestItems([])
const UploadedItems = cases.map((item,index)=>{
return{
id:index,
formData:{
date:item['date'],
env:item['env'],
assetClass:item['asset_class'],
metric:item['metric'],
nodeLevel:item['node_level'],
nodeName:item['node_name'],
testName:item['test_name'],
dataType:item['dataType'],
tradeId:item['tradeId']
},
results:[]
}
})
setTestItems(UploadedItems)
}
const runAllTests = () => {
testCaseRef.current.runAll()
}
const clearTestCases = () => {
// console.log('Clear Test cases')
setTestItems([])
if (testItems.length == 0) {
setTestItems([templateTestItem])
}
}
const extractAllResults =()=>{
testCaseRef.current.extractAllResults()
}
const updateTestResults = useCallback( (result, index) => {
console.log('Index:', index)
setTestItems(prevObjs=>(prevObjs.map((item)=>{
let updatedItem = { ...item, results: result }
if(item.id==index) return updatedItem
return item
})))
},[])
return (
<div style={{ 'backgroundColor': '#1b2829', 'display': 'flex', }} className={styles.dashboard}>
<Grid>
<Row stretched style={{}} className={styles.buttonConsole}>
{<ButtonConsole addTest={addTestItem} addUploadCases={addUploadCases} runAllTests={runAllTests} clearTestCases={clearTestCases} extractAllResults={extractAllResults} />}
</Row>
<Row centered>
<TestRunStats stats={stats}/>
</Row>
<Row style={{ 'display': 'flex', 'flex-direction': 'column' }} ><TestCaseContainer countTestRunStats={countTestRunStats} updateTestResults={updateTestResults} isRun={isRun} ref={testCaseRef} testItems={testItems} /> </Row>
{/*
<Row></Row>
<Row></Row> */}
</Grid>
</div>
);
})
TestContainer.js
const TestCaseContainer = forwardRef((props, ref) => {
const testCaseRef = useRef([])
useImperativeHandle(ref, () => ({
extractAllResults: async () => {
const data = {
data:[],
summary:[]
}
testCaseRef.current.forEach(async (item, index) => {
try {
const workbook = item.extractAllResults()
const summary = workbook['summary']
workbook['data'].forEach(testData => {
data['data'].push(testData)
})
data['summary'].push(summary)
} catch (err) {
console.log(err)
}
})
await axios.post('http://localhost:9999/api/downloadresults', data).then(res => {
console.log('res', res)
const byteCharacters = atob(res.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'application/vnd.ms-excel' });
saveAs(blob, 'TestResults.xlsx')
})
},
runAll: () => {
testCaseRef.current.forEach(async (item, index) => {
await item.runAll()
})
}
}));
const runTestCase = async (date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key) => {
let testKey = key
console.log('FEtCHING', testKey)
try {
const params = {
nodeName,
date,
env,
nodeLevel,
assetClass,
metric,
dataType,
tradeId,
testName
}
const endpoint ={
sensitivities:'sensitivities'
}
if (metric == 'DELTA_SENSITIVITIES') {
const result = await axios.get('example.net/api/sensitivities', { params, }).then(response => {
console.log('response.data', response.data)
return response.data
})
if (result.data == 'none') {
toast.error(`${date}-${metric}-${nodeName} failed queried! No valutations for trades`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
} else if (result.data != 'none') {
// setTestResult(result)
props.updateTestResults(result, testKey)
// updateTestResults(false,testKey,'isLoading')
toast.success(`${date}-${metric}-${nodeName} Successfully queried!`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
// setTestResult(result.data)
} else {
await axios.get(`http://localhost:9999/api/metric/${metric}`, { params, }).then(response => {
if (response.data != 'none') {
props.updateTestResults(response.data, testKey)
toast.success(`${date}-${metric}-${nodeName} Successfully queried!`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
} else {
toast.error(`${date}-${metric}-${nodeName} failed queried! No valutations for trades`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
})
}
} catch (error) {
toast.error(`${date}-${metric}-${nodeName} failed queried! -${error
}`, {
autoClose: 8000,
position: toast.POSITION.TOP_RIGHT
});
}
}
return (
<Segment style={{ 'display': 'flex', 'width': 'auto', 'height': '100vh' }} className={styles.testCaseContainer}>
<div style={{ 'display': 'flex', }}>
</div>
<ul style={{overflowY:'auto',height:'100%'}} className='testItemContainer'>
{
// memoTestTwo
// testList
props.testItems.map((item, index) => {
let testName
if (item['formData']['testName'] == '') {
testName = `testRun-${index}`
} else {
testName = item['formData']['testName']
}
return <TestCase testResult={item['results']} runTestCase={runTestCase} isRun={props.isRun} ref={el => (testCaseRef.current[index] = el)} testKey={index} key={index} date={item['formData']['date']} env={item['formData']['env']} assetClass={item['formData']['assetClass']} metric={item['formData']['metric']} nodeLevel={item['formData']['nodeLevel']} nodeName={item['formData']['nodeName']} testName={testName} dataType={item['formData']['dataType']} tradeId={item['formData']['tradeId']} hierarchy={hierarchy} />
})
}
</ul>
</Segment>
)
})
TestCase.js - the individual item rendered from mapping!
const TestCase = forwardRef((props, ref) => {
const [isLoading, setIsLoading] = useState(false)
const inputRefs = useRef()
const outputRefs = useRef()
useImperativeHandle(ref, () => ({
extractAllResults: () => {
return outputRefs.current.extractAllResults();
},
runAll: () => {
inputRefs.current.runAll()
},
}));
const runSingleTestCase = async (date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key) => {
setIsLoading(true)
await props.runTestCase(date, env, nodeLevel, nodeName, assetClass, metric, dataType, tradeId, testName, key)
setIsLoading(false)
}
const convertDate = (date) => {
if (date) {
const newDate = date.split('/')[2] + '-' + date.split('/')[0] + '-' + date.split('/')[1]
return newDate
} else {
return date
}
}
return (
<Segment color='green' style={{ 'display': 'flex', 'flexDirection': 'column', }}>
<div style={{ 'display': 'flex', 'justify-content': 'space-between' }}>
<div style={{ 'display': 'flex', 'height': '30px' }}>
<Button
// onClick={props.deleteSingleTest(props.testKey)}
icon="close"
inverted
size="tiny"
color='red'
></Button>
</div>
<RegressionInput runSingleTestCase={runSingleTestCase} isRun={props.isRun} testKey={props.testKey} ref={inputRefs} nodeNames={props.hierarchy} runTestCase={props.runTestCase} date={convertDate(props.date)} testName={props.testName} env={props.env} assetClass={props.assetClass} metric={props.metric} nodeLevel={props.nodeLevel} nodeName={props.nodeName} dataType={props.dataType} tradeId={props.tradeId} />
<TestCheck pass={props.testResult ? props.testResult['CHECK'] : null} />
</div>
{
isLoading ? (<Loading type={'circle'} style={{ 'display': 'flex', 'flexDirecton': 'column', 'justify-content': 'center', 'align-items': 'center', 'marginTop': '50' }} inline />) : (
<RegressionOutput ref={outputRefs} testName={props.testName} testResult={props.testResult} />
)
}
</Segment>
)
})
This article might help you understand React rendering behavior better:
Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior
React's default behavior is that when a parent component renders, React will recursively render all child components inside of it!
To change that behavior you can wrap some of your components in React.memo(). So React will do a shallow compare on the props object and only re-render that if one of the top level properties of the props object has changed.
That's not always possible or recommended, especially if you are using props.children.
const TestItem = React.memo(({id,value}) => {
console.log(`Rendering TestItem ${id}...`);
return(
<div>TestItem {id}. Value: {value}</div>
);
});
const App = () => {
console.log("Rendering App...");
const [items,setItems] = React.useState([
{ id: 1, value: "INITIAL VALUE" },
{ id: 2, value: "INITIAL VALUE" },
{ id: 3, value: "INITIAL VALUE" },
]);
const testItems = items.map((item,index) =>
<TestItem key={index} id={item.id} value={item.value}/>
);
const updateTest = (index) => {
console.clear();
setItems((prevState) => {
const newArray = Array.from(prevState);
newArray[index].value = "NEW VALUE";
return newArray
});
};
return(
<React.Fragment>
<div>App</div>
<button onClick={()=>{updateTest(0)}}>Update Test 1</button>
<button onClick={()=>{updateTest(1)}}>Update Test 2</button>
<button onClick={()=>{updateTest(2)}}>Update Test 3</button>
<div>
{testItems}
</div>
</React.Fragment>
);
};
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
Without the React.memo() call. Every re-render of the App component would trigger a re-render in all of the TestItem component that it renders.
Related
I have a data in a table and i want to add a pagination option to it. In the user interface, user should be able to choose data per page and page index that's why i added some handle methods to it and try to render it but i faced with the above error.
Whole jsx file is too long to share therefore i only shared codes which seemed important to me and replaced other codes with ... if you need anything else you can ask it in the comments.
function Activity(props) {
...
const variables = useMemo(() => ({
projectId,
language,
env: environment,
pageSize: 20,
filter,
...getSortFunction(),
}), [projectId, language, environment, filter, getSortFunction()]);
const {
data, hasNextPage, loading, loadMore, refetch,
} = useActivity(variables);
//pagination section
const [pagination, setPagination] = useState({
currentPage: 1,
dataPerPage: 10,
indexOfLastData: 9,
indexOfFirstData: 0,
})
const [totalPages, setTotalPages] = useState(1);
const [paginationString, setPaginationString] = useState(`Current page ${pagination.currentPage}, total number of data ${pagination.dataPerPage}`)
const handlePagination = (page, dataPerPage, currentPagination) => {
if (currentPagination.currentPage != page) handlePaginationCurrentPage(page, currentPagination);
if (currentPagination.dataPerPage != dataPerPage) handlePaginationDataPerPage(dataPerPage, currentPagination);
const dataToBeUsed = [...data].slice(pagination.indexOfFirstData, pagination.indexOfLastData);
setTotalPages(Math.ceil(3 / pagination.dataPerPage));
return dataToBeUsed;
}
const handlePaginationString = () => {
setPaginationString(`current page ${pagination.currentPage}, total number of data ${pagination.dataPerPage}`)
}
const handlePaginationCurrentPage = (page, pagination) => {
useEffect(() => {
setPagination({
...pagination,
currentPage: Number(page),
indexOfLastData: pagination.dataPerPage * Number(page) - 1,
indexOfFirstData: pagination.dataPerPage * Number(page) - pagination.dataPerPage
})
})
}
const handlePaginationDataPerPage = (dataPerPage, pagination) => {
useEffect(() => {
setPagination({
...pagination,
dataPerPage: dataPerPage,
indexOfLastData: dataPerPage * pagination.currentPage - 1,
indexOfFirstData: dataPerPage * pagination.currentPage - pagination.dataPerPage
})
})
}
const resetPagination = (e) => {
e.stopPropagation();
handlePagination(1, 10, pagination);
};
...
const renderActions = row => (
<ActivityActionsColumn
outdated={ isUtteranceOutdated(row.datum) }
datum={ row.datum }
handleSetValidated={ handleSetValidated }
onDelete={ handleDelete }
onMarkOoS={ handleMarkOoS }
data={ handlePagination(1, 10, pagination) }
getSmartTips={ utterance => getSmartTips({
nluThreshold, endTime, examples, utterance,
}) }
/>
);
...
const columns = [
{ key: '_id', selectionKey: true, hidden: true },
{
key: 'confidence',
style: { width: '51px', minWidth: '51px' },
render: renderConfidence,
},
{
key: 'intent',
style: { width: '180px', minWidth: '180px', overflow: 'hidden' },
render: renderIntent,
},
{
key: 'conversation-popup', style: { width: '30px', minWidth: '30px' }, render: renderConvPopup,
},
{
key: 'text',
style: { width: '100%' },
render: renderExample,
},
...(can('incoming:w', projectId) ? [
{
key: 'actions',
style: { width: '110px' },
render: renderActions,
},
] : []),
];
const renderTopBar = () => (
<div className='side-by-side wrap' style={ { marginBottom: '10px' } }>
...
<Accordion className='pagination-accordion'>
<Accordion.Title
active={ activeAccordion }
onClick={ () => handleAccordionClick() }
data-cy='toggle-pagination'
className='pagination-accordian-title'
>
<Icon name='dropdown' />
<span className='toggle-pagination'>
{ activeAccordion
? `Hide Pagination Options `
: `Show Pagination Options ` }
</span>
<span className="toggle-pagination pagination-string">
{ activeAccordion
? `${paginationString}`
: `${paginationString}` }
</span>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */ }
<span
data-cy='reset-pagination'
onClick={ e => resetPagination(e) }
role='button'
tabIndex='0'
className='reset-button'
>
<Icon name='redo' size='small' /> Reset
</span>
</Accordion.Title>
</Accordion>
</div>
);
return (
<>
{ !!openConvPopup && <ConversationSidePanel utterance={ openConvPopup } onClose={ () => setOpenConvPopup(false) } /> }
{ renderTopBar() }
{ data && data.length ? (
<>
<DataTable
ref={ tableRef }
columns={ columns }
data={ handlePagination(1, 10, pagination) }
hasNextPage={ hasNextPage }
loadMore={ loading ? () => { } : loadMore }
onScroll={ handleScroll }
selection={ selection }
onChangeSelection={ (newSelection) => {
setSelection(newSelection);
setOpenConvPopup(false);
} }
/>
)}
I can't use data in pagination beacuse it is used in so many places and in those places everything designed assumed data is in its full length so i should use it seperately (ex./ in handlePagination i get it using data.slice() function )
Thanks!
I changed
<DataTable
ref={ tableRef }
columns={ columns }
data={ handlePagination(1, 10, pagination) }
hasNextPage={ hasNextPage }
loadMore={ loading ? () => { } : loadMore }
onScroll={ handleScroll }
selection={ selection }
onChangeSelection={ (newSelection) => {
setSelection(newSelection);
setOpenConvPopup(false);
} }
/>
to
<DataTable
ref={ tableRef }
columns={ columns }
data={ dataToBeShowed }
hasNextPage={ hasNextPage }
loadMore={ loading ? () => { } : loadMore }
onScroll={ handleScroll }
selection={ selection }
onChangeSelection={ (newSelection) => {
setSelection(newSelection);
setOpenConvPopup(false);
} }
/>
and added
const [dataToBeShowed, setDataToBeShowed] = useState(data.slice(pagination.indexOfFirstData, pagination.indexOfLastData));
useEffect(() => {
setDataToBeShowed(data.slice(pagination.indexOfFirstData, pagination.indexOfLastData));
setPaginationString(`Current page ${pagination.currentPage}, total number of data ${pagination.dataPerPage}`)
setTotalPages(Math.ceil(data.length / pagination.dataPerPage));
}, [data, pagination.indexOfFirstData, pagination.indexOfLastData]);
This solved becuase I saw that handlePagination(1, 10, pagination) doesn't render in Activity but in Window which means It can't see the states, therefore i added dataToBeShown and assigned it to real data and since i added useEffect, it follows the change in data (real data).
(if you use onClick vs. you can simply add () => {handleOnBruh()} vs this way it binds itself with the file.)
Thanks!
When I run the app then it goes into an infinite loop
This is because pointsData is inside the useEffect.
How can this situation be fix ?
function useGetPoints() {
const [pointsData, setPointsData] = useState<PointTbleType[]>([]);
React.useEffect(() => {
loadDataFromPointTables()
}, [])
const loadDataFromPointTables = () => {
(async () => {
try {
const points = await PointTable.getPointList()
setPointsData(points);
// if (!points.length) {
// Toast.show({ type: 'info', text1: 'There is no data in the table point' })
// }
} catch (error) {
console.warn('Error from loadDataFromPointTables(): ', error);
}
})();
}
return {
pointsData
}
}
export const PointModal = (props: any) => {
const { pointsData } = useGetPoints();
const [tableHead] = useState(['Area', 'Site', 'Collection Point', 'Location']);
const [tableData, setTableData] = useState<any[]>([]);
const arrangeData = () => {
let rows: any[] = [];
pointsData.forEach(e => {
let row = [e.Area, e.Site, e.GatheringPoint, e.Location];
rows.push(row);
});
setTableData(rows);
}
useEffect(() => {
arrangeData();
}, [pointsData]);
return (
<Modal
animationType={'slide'}
transparent={false}
visible={props.pointModalVisible}
onRequestClose={() => {
console.log('Modal has been closed.');
}}>
<View style={styles.modal}>
{pointsData.length ?
<ScrollView style={styles.item}>
<View style={styles.tableView}>
<Table borderStyle={{ borderWidth: 2, borderColor: '#c8e1ff' }}>
<Row data={tableHead} style={styles.head} textStyle={styles.text} />
<Rows data={tableData} textStyle={styles.text} />
</Table>
</View>
</ScrollView>
:
<ActivityIndicator size="large" color='white' />}
</View>
<Button
title="CLOSE"
onPress={props.onClose}
/>
</Modal>
);
};
It is very obvious that it will run into an infinite loop. You're giving pointsData as dependency of useEffect and loadDataFromPointTables is dispatching value to pointsData. Remove pointsData as dependency and every will work as expected.
This is because you don't check if pointsData is not empty.
First of all I would set pointsData to null or undefined in the beginning:
const [pointsData, setPointsData] = useState<PointTbleType[]>(undefined);
and later on you have to check if it is not null or undefined and then call function to fetch data:
React.useEffect(() => {
if(!pointsData){
loadDataFromPointTables();
}
}, [pointsData])
Second option is to just remove pointsData from dependancy array in useEffect so it will run only once.
This is because you haven't set any condition while updating pointsData. Give a try with below code it should work
function useGetPoints() {
const [pointsData, setPointsData] = useState<PointTbleType[]>([]);
React.useEffect(() => {
loadDataFromPointTables()
}, [pointsData])
const loadDataFromPointTables = () => {
(async () => {
try {
const points = await PointTable.getPointList()
if(points != pointsData){
setPointsData(points);
}
} catch (error) {
console.warn('Error from loadDataFromPointTables(): ', error);
}
})();
}
return {
pointsData
}
}
Error image
const Home = () => {
React.state = {
activeIndex: 0,
animating: false,
}
React.setActiveIndex = (i) => {
this.setState({ activeIndex: i });
}
React.setAnimating= (v) => {
this.setState({ animating: v });
}
React.next = () => {
if (this.state.animating) return;
const nextIndex =
this.state.activeIndex === items.length - 1
? 0
: this.state.activeIndex + 1;
this.setActiveIndex(nextIndex);
};
React.previous = () => {
if (this.state.animating) return;
const nextIndex =
this.state.activeIndex === 0
? items.length - 1
: this.state.activeIndex - 1;
this.setActiveIndex(nextIndex);
};
React.goToIndex = (newIndex) => {
if (this.state.animating) return;
this.setActiveIndex(newIndex);
};
React.slides = items.map((item) => {
return (
<CarouselItem
onExiting={() => this.setAnimating(true)}
onExited={() => this.setAnimating(false)}
key={item.src}
>
<img src={item.src} alt={item.altText} />
<CarouselCaption
captionText={item.caption}
captionHeader={item.caption}
/>
</CarouselItem>
);
});
useEffect(() => {
Aos.init({ duration: 2000 })
}, [])
return (
<>
<div style={{ position: "relative" }}>
<div style={{ position: "absolute", width: "100%", height: "100%", backgroundColor: "#29323cb3", zIndex: 2 }}>
</div>
<Carousel style={{ position: "relative" }}
activeIndex={this.state.activeIndex}
next={this.next}
previous={this.previous}
>
<CarouselIndicators
items={items}
activeIndex={this.state.activeIndex}
onClickHandler={this.goToIndex}
/>
{this.slides}
<CarouselControl
direction="prev"
directionText="Previous"
onClickHandler={this.previous}
/>
<CarouselControl
direction="next"
directionText="Next"
onClickHandler={this.next}
/>
</Carousel>
</div> </>
);
}
Home.propTypes = {};
export default Home;
I wanted to change this component from class to function and then I got this error.
I also got "ReferenceError: state is not defined" and "TypeError: Cannot read property 'state' of undefined"
I wanted to change from class to function to add aos animation.
So how can I solve these errors.
The problem is mainly with the code of bootstrap slider.
There is no this inside the function component. And you need to refactor your state using useState hook.
import React, {useState} from 'react';
const [state, setState] = useState({
activeIndex: 0,
animating: false,
})
To to the state changes you can do it like this
if you need to change activeIndex,
const setActiveIndex = (i) => {
setState((prevState) => { ...prevState, activeIndex: i });
}
if you need to change activeIndex,
const setAnimating= (v) => {
setState((prevState) => { ...prevState, animating: v });
}
You should change this.state.activeIndex instances to state.activeIndex. Same applies to animating
You should remove this from function references as well. For Ex:
Instead of,
this.setActiveIndex(nextIndex);
it should be,
setActiveIndex(nextIndex);
Hope this helps to do the refactoring!!
On dataView true dataViewModel is rendered
{popUps.dataView && popupsFactory.dataView}
Rendering of model is very slow, which have and table with dataSource around 10k-20k or more.
/* eslint-disable react/prop-types */
/* eslint-disable indent */
/* eslint-disable linebreak-style */
import React, { useState, useEffect, useRef } from 'react'
import { Modal, Select, Table, Input, Button, Icon, Tooltip } from 'antd'
//import Highlighter from 'react-highlight-words'
import { connect } from 'react-redux'
import actionDispatcher from 'client/app-actions'
import { isFiltered } from 'client/routes/app/constants/deck-layers'
//import TableLayout from './Table'
import styled from './styled-component'
//import './style.css'
const { Option } = Select
const dataViewModal = (props) => {
const {
visibility
} = props
const [layerId, setLayerId] = useState('')
const [tableColumns, setTableColumns] = useState(null)
const [tableData, setTableData] = useState(null)
const [searchText, setSearchText] = useState('')
const [searchedColumn, setSearchedColumn] = useState('')
const [filteredInfo, setFilteredInfo] = useState(null)
const [sortedInfo, setSortedInfo] = useState(null)
const [pagination, setPagination] = useState({ pageSize: 20, current: 1 })
const [currentDataSource, setCurrentDataSource] = useState(null)
const searchInput = useRef(null)
useEffect(() => {
// console.log(currentDataSource)
}, [currentDataSource])
/**
* initialize layer
*/
useEffect(() => {
setLayerId(props.layers.length ? props.layers[0].uniqueKey : null)
}, [props.layers])
/**
* whenerver layerId changes clear the sort and filter for the columns
*/
useEffect(() => {
clearAll()
}, [layerId])
const tempLayer = props.layers.find(item => item.uniqueKey === layerId)
useEffect(() => {
const layer = props.layers.find(item => item.uniqueKey === layerId)
if (layer?.props?.coordinates) {
showMapDataToUser(layer, false)
} else {
setTableColumns(null)
setTableData(null)
}
}, [tempLayer, sortedInfo, filteredInfo])
const showMapDataToUser = (layer, exportToCsv) => {
const filteredColumnSet = layer.columnSet.filter(
item => item.variableAdded
)
const showDataToUser = []
const showColumns = []
filteredColumnSet.forEach((item, index) => {
if (item.showToUser) {
showColumns.push(`${item.displayName}`)
}
})
showDataToUser.push([showColumns])
layer.props.coordinates.forEach((it) => {
const showData = []
filteredColumnSet.forEach((item, index) => {
if (item.showToUser) {
if (it.geometry &&
it.geometry.data[layer.props.columnSet + isFiltered]) {
if (showColumns.includes('Network GMP')) {
if (item.columnName.toUpperCase() === 'OLYMPUS_ID') {
const splitLatLong = it.geometry.data[index].split('_')
if (splitLatLong.length > 2
&& !isNaN(splitLatLong[splitLatLong.length - 1])
&& !isNaN(splitLatLong[splitLatLong.length - 2])) {
splitLatLong.splice(-2, 2)
}
const splitLatLongString = splitLatLong.join('_')
showData.push(`${splitLatLongString}`.replace(/,/g, ';'))
} else if (item.columnName.toUpperCase() === 'RETAINABILITY_KPI' && !isNaN(it.geometry.data[index])) {
const calculativeDCR = 100 - (it.geometry.data[index])
showData.push(`${calculativeDCR}`.replace(/,/g, ';'))
} else {
showData.push(`${it.geometry.data[index]}`.replace(/,/g, ';'))
}
} else {
showData.push(`${it.geometry.data[index]}`.replace(/,/g, ';'))
}
} else if (it[layer.props.columnSet + isFiltered]) {
if (showColumns.includes('Network GMP')) {
if (item.columnName.toUpperCase() === 'OLYMPUS_ID') {
const splitLatLong = it[index].split('_')
if (splitLatLong.length > 2
&& !isNaN(splitLatLong[splitLatLong.length - 1])
&& !isNaN(splitLatLong[splitLatLong.length - 2])) {
splitLatLong.splice(-2, 2)
}
const splitLatLongString = splitLatLong.join('_')
showData.push(`${splitLatLongString}`.replace(/,/g, ';'))
} else if (item.columnName.toUpperCase() === 'RETAINABILITY_KPI' && !isNaN(item[index])) {
const calculativeDCR = 100 - (item[index])
showData.push(`${calculativeDCR}`.replace(/,/g, ';'))
} else {
showData.push(`${it[index]}`.replace(/,/g, ';'))
}
} else {
showData.push(`${it[index]}`.replace(/,/g, ';'))
}
}
}
})
if (showData.length) {
showDataToUser.push([showData])
}
})
//-------------------------------------------
if (!exportToCsv) {
//create table data
const columnNames = filteredColumnSet.filter(col => col.showToUser).map(col => col.columnName)
const data = showDataToUser.slice(1).map((dataSet, index) => {
//items[0]: array converted to obj
const itemsObj = { key: index + 1 }
dataSet[0].forEach((item, i) => {
itemsObj[columnNames[i]] = item
})
return itemsObj
})
// console.log(data)
setTableData(data)
//
//create table columns
const sortedInfoNew = sortedInfo || {}
const filteredInfoNew = filteredInfo || {}
const columns = []
columns.push({
title: 'No.',
dataIndex: 'key',
width: 70,
// fixed: 'left',
key: 'key',
})
const len = filteredColumnSet.filter(col => col.showToUser).length
filteredColumnSet.filter(col => col.showToUser).forEach((item, index) => {
if (item.showToUser) {
// console.log(item)
const columnObj = index === 0 ? ({
title: item.displayName,
width: 150,
// fixed: 'left',
displayName: item.displayName,
dataIndex: item.columnName,
key: item.columnName,
sorter: item.columnName ? (a, b) => sortByColumnType(a, b, item) : null,
sortDirections: ['descend', 'ascend'],
sortOrder: sortedInfoNew.columnKey === item.columnName && sortedInfoNew.order,
filteredValue: filteredInfoNew[item.columnName] || null, // to clear search
...getColumnSearchProps(item.columnName)
})
: index === len - 1 ?
({
title: () => columnTitle(item, data),
width: 150,
//fixed: 'right',
displayName: item.displayName,
dataIndex: item.columnName,
key: item.columnName,
filters: getFilters(data, item.columnName),
sorter: item.columnName ? (a, b) => sortByColumnType(a, b, item) : null,
onFilter: (value, record) => record[item.columnName].indexOf(value) === 0,
sortDirections: ['descend', 'ascend'],
sortOrder: sortedInfoNew.columnKey === item.columnName && sortedInfoNew.order,
filteredValue: filteredInfoNew[item.columnName] || null,
}) :
({
title: () => columnTitle(item, data),
width: 150,
displayName: item.displayName,
dataIndex: item.columnName,
key: item.columnName,
filters: getFilters(data, item.columnName),
sorter: item.columnName ? (a, b) => sortByColumnType(a, b, item) : null,
onFilter: (value, record) => record[item.columnName].indexOf(value) === 0,
sortDirections: ['descend', 'ascend'],
sortOrder: sortedInfoNew.columnKey === item.columnName && sortedInfoNew.order,
filteredValue: filteredInfoNew[item.columnName] || null,
})
columns.push(columnObj)
}
})
// console.log(columns)
setTableColumns(columns)
//
}
//-------------------------------------------
// console.log(showDataToUser)
return showDataToUser
}
const getFilters = (data, columnName) => {
const uniqueValuesSet = new Set(data.map(dataObj => dataObj[columnName]))
const filters = Array.from(uniqueValuesSet).map(value => ({ text: value, value }))
return filters
}
const sortByColumnType = (a, b, item) => {
if (item.columnType === 'NUMERIC') {
return a[item.columnName] - b[item.columnName]
} else if (item.columnType === 'TEXT') {
if (!a[item.columnName]) {
// console.log(item, a)
}
return a[item.columnName]?.localeCompare(b[item.columnName])
}
// console.log(item)
return null
}
/**
* set to initial values
*/
const clearAll = () => {
setFilteredInfo(null)
setSortedInfo(null)
setPagination({
pageSize: 20,
current: 1
})
setCurrentDataSource(null)
}
function getColumnSearchProps(dataIndex) {
return {
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={searchInput}
placeholder={`Search ${dataIndex}`}
value={selectedKeys && selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon="search"
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</div>
),
filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current.select())
}
},
// render: text =>
// searchedColumn === dataIndex ? (
// <Highlighter
// highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
// searchWords={[searchText]}
// autoEscape
// textToHighlight={text.toString()}
// />
// ) : (
// text
// ),
}
}
function handleSearch(selectedKeys, confirm, dataIndex) {
confirm()
setSearchText(selectedKeys[0])
setSearchedColumn(dataIndex)
}
function handleReset(clearFilters) {
clearFilters()
setSearchText('')
}
const handleCancel = () => {
props.updatePopUpMode('dataView')
}
const handleChange = (value) => {
// console.log(`selected ${value}`)
setLayerId(value)
}
function onChange(pager, filters, sorter, extra) {
// console.log(pager, filters, sorter, extra)
setFilteredInfo(filters)
setSortedInfo(sorter)
setPagination(pager)
setCurrentDataSource(extra.currentDataSource)
}
const handleDownloadLayer = () => {
//const layer = props.layers.find(item => item.uniqueKey === layerId)
if (tableColumns?.length) {
//export in csv
const showDataToUser = []
showDataToUser.push(tableColumns.map(({ displayName }) => displayName).slice(1)) // don't include key column ref: line 138
const dataSource = currentDataSource || tableData
showDataToUser.push(...dataSource.map(item => tableColumns.map(({ key }) => (item[key])).slice(1))) // don't include key column values ref: line 138
let csvContent = ''
showDataToUser.forEach((row) => {
csvContent += row.join(',')
csvContent += '\n'
})
const encodedUri = URL.createObjectURL(new Blob([csvContent], { type: 'text/plain' }))
const a = document.createElement('a')
a.href = encodedUri
a.target = '_Blank'
a.download = 'mapdata.csv'
a.click()
//export in csv
}
}
const selectedLayer = props.layers.find(layer => layer.uniqueKey === layerId)
const columnTitle = (item, data) => {
const columnData = data.map(dataObj => dataObj[item.columnName])
// console.log(columnData)
const nullColumnData = columnData.filter(value => value === 'null')
const nullPercent = (nullColumnData.length / columnData.length) * 100
return (
<div>
<Tooltip placement="top" title={item.displayName}>
<styled.columnDisplayName>
{item.displayName}
</styled.columnDisplayName>
</Tooltip>
<div style={{ fontSize: '10px', color: '#ff5c00a1' }}>
{Math.round(nullPercent)}% null rows
</div>
</div>
)
}
const LayerNames = () => (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Select defaultValue={selectedLayer?.uniqueKey} style={{ width: 120 }} onChange={handleChange}>
{
props.layers.map(item =>
<Option value={item.uniqueKey}>{item.displayName}</Option>
)
}
</Select>
</div>
)
return (
<styled.modelWrapper
width="98%"
style={{ top: '20px' }}
title={<LayerNames/>}
visible={visibility}
onCancel={handleCancel}
footer={null}
>
<div className="Model Content" style={{ height: '590px' }}>
<styled.modalContentHeader>
<div style={{ float: 'right' }}>
<Button onClick={handleDownloadLayer}>Export to CSV</Button>
</div>
</styled.modalContentHeader>
<styled.tableWrapper style={{
overflow: 'auto',
height: '88%'
}}
>
{!tableData?.length ?
'NO DATA'
:
<styled.table
className="table-striped-rows"
columns={tableColumns}
dataSource={tableData}
pagination={pagination}
size="small"
onChange={onChange}
scroll={{ y: 410, x: 1340 }}
rowClassName={(record, index) => (index % 2 === 0 ? 'table-row-light' : 'table-row-dark')}
/>
}
</styled.tableWrapper>
</div>
</styled.modelWrapper>
)
}
const mapStateToProps = (store, ownProps) => {
const appViewState = store.appStore.appView
const layers = store.layerStore.layers
return {
selectedLayer: layers.length && layers.find(it => it.id === appViewState.variableSelectLayerId),
visibility: appViewState.popups.dataView,
layers
}
}
const mapDispatchToProps = {
updatePopUpMode: actionDispatcher.updatePopUpMode,
updateCoordinateDataFilter: actionDispatcher.updateCoordinateDataFilter
}
const DataView = connect(
mapStateToProps,
mapDispatchToProps
)(dataViewModal)
export default DataView
All operations like sorting, filtering, and pagination change are very slow. Filters take too long to render. Is there something I can do to optimize code. I tried to do calculation part of table dataSource before rendering and then passing it as a prop from Redux store, but that didn't work either.
The goal of my small React experiment is "clear the initial value of this.state.numString (outputs an empty string), then concatenate the clicked numbers into this.state.numString". To make it execute asynchronously, I took advantage of this.setState's callback where the concatenation of number strings happen.
class App extends Component {
state = {
numString: '12'
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
this.setState({
numString: ''
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
The result was not what I expected; it only adds the current number I click into the empty string then change it into the one I click next, no concatenation of string numbers happens.
Here is one way of doing this. As I said in my comment you are resetting the string in every setState. So, you need some kind of condition to do that.
class App extends React.Component {
state = {
numString: '12',
resetted: false,
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if ( !this.state.resetted ) {
this.setState({
numString: '',
resetted: true,
}, () => {
this.setState( prevState => ({
numString: prevState.numString.concat(num)
}))
})
} else {
this.setState(prevState => ({
numString: prevState.numString.concat(num)
}))
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = { padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem' };
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data-num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
You can do something like below to clear the state immediately and concatenate the state with previous state value
this.setState({
numString: ''
}, () => {
this.setState( prevState => ({
numString: prevState.numString + num
}));
});
The code above in your question , in first setState you are setting variable to empty and in the second setState it is concatenating new value with empty string state. Thats why it is not working.
Try something like below:
class App extends Component {
state = {
numString: '12',
isFirstTime: true
}
displayAndConcatNumber = (e) => {
const num = e.target.dataset.num;
if(this.state.isFirstTime){
this.setState({
numString: '',
isFirstTime: false
}, () => {
this.setState({
numString: this.state.numString.concat(num)
})
})
}else{
this.setState({
numString: this.state.numString.concat(num)
})
}
}
render() {
const nums = Array(9).fill().map((item, index) => index + 1);
const styles = {padding: '1rem 0', fontFamily: 'sans-serif', fontSize: '1.5rem'};
return (
<div>
<div>
{nums.map((num, i) => (
<button key={i} data- num={num} onClick={this.displayAndConcatNumber}>{num}</button>
))}
</div>
<div style={styles}>{this.state.numString}</div>
</div>
);
}
}