-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d0a0cad
commit f14c8bb
Showing
12 changed files
with
432 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,3 +32,6 @@ yarn-error.log* | |
.vercel | ||
|
||
.env | ||
|
||
*.zip | ||
*.rar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,5 +18,9 @@ | |
{ | ||
"name": "useReducer", | ||
"link": "/usereducer" | ||
}, | ||
{ | ||
"name": "memo", | ||
"link": "/memo" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { useEffect, useState } from 'react' | ||
|
||
import characters from '@/data/characters.json' | ||
import styles from '../../tablesdemo/TablesDemo.module.css' | ||
|
||
function FullTable () { | ||
const [data, setData] = useState(characters) | ||
const [headers, setHeaders] = useState([]) | ||
|
||
useEffect(() => { | ||
if (headers.length === 0) { | ||
setHeaders(Object.keys(data[0]).map((key, id) => ({ | ||
id, | ||
name: key | ||
}))) | ||
} | ||
}, [headers, data]) | ||
|
||
const handleCellUpdate = (rowId, field, newValue) => { | ||
if (data[rowId][field] === parseFloat(newValue)) return | ||
|
||
setData(prev => | ||
prev.map(row => | ||
row.id === rowId ? { ...row, [field]: parseFloat(newValue) } : row | ||
) | ||
) | ||
} | ||
|
||
const handleKeyDown = (e, rowIndex, colIndex) => { | ||
// Move cursor to next row | ||
const { keyCode } = e | ||
if (keyCode !== 13) return | ||
|
||
const nextIndex = (rowIndex === data.length - 1) | ||
? 0 : rowIndex + 1 | ||
|
||
const nextId = `cell-${nextIndex}-${colIndex}` | ||
const next = document.getElementById(nextId) | ||
next?.focus() | ||
} | ||
|
||
return ( | ||
<div className={styles.container}> | ||
<div className={styles.subDescription}> | ||
<h3>Full Table re-rendering</h3> | ||
<ul> | ||
<li>On edit, this table renders the object array data using map(), rendering the full table.</li> | ||
</ul> | ||
</div> | ||
|
||
<form autoComplete='off'> | ||
<table> | ||
<thead> | ||
<tr> | ||
{headers?.map(column => ( | ||
<th key={column.id}> | ||
{column.name} | ||
</th> | ||
))} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{data.map((player, rowIndex) => ( | ||
<tr key={player.id}> | ||
{headers?.map((field, colIndex) => ( | ||
<td key={field.id}> | ||
{(['id', 'name'].includes(field)) | ||
? player[field] | ||
: <input | ||
id={`cell-${rowIndex}-${colIndex}`} | ||
type="text" | ||
defaultValue={player[field.name]} | ||
onFocus={(e) => e.target.select()} | ||
onBlur={(e) => { | ||
const { value } = e.target | ||
handleCellUpdate(rowIndex, field.name, value) | ||
}} | ||
onKeyDown={(e) => handleKeyDown(e, rowIndex, colIndex)} | ||
/> | ||
} | ||
</td> | ||
))} | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</form> | ||
</div> | ||
) | ||
} | ||
|
||
export default FullTable |
80 changes: 80 additions & 0 deletions
80
client/src/features/memo/components/memoizedtable/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { useEffect, useState, useCallback } from 'react' | ||
|
||
import TableRow from '../tablerow' | ||
|
||
import characters from '@/data/characters.json' | ||
import styles from '../../tablesdemo/TablesDemo.module.css' | ||
|
||
const MemoizedTable = () => { | ||
const [players, setData] = useState(characters) | ||
const [headers, setHeaders] = useState([]) | ||
|
||
useEffect(() => { | ||
if (headers.length === 0) { | ||
setHeaders(Object.keys(players[0]).map((key, id) => ({ | ||
id, | ||
name: key | ||
}))) | ||
} | ||
}, [headers, players]) | ||
|
||
// Wrap anonymous functions in useCallback() to prevent re-renders on child components. | ||
// Sometimes, local state may need to be included in its dependency array | ||
const handleCellUpdate = useCallback((rowId, field, newValue) => { | ||
setData((prevData) => { | ||
const tempData = [...prevData] | ||
const updatedValue = parseFloat(newValue) | ||
|
||
// Update only the affected field in an object element | ||
if (tempData[rowId][field] !== updatedValue) { | ||
tempData[rowId] = { | ||
...tempData[rowId], [field]: updatedValue | ||
} | ||
} | ||
|
||
return tempData | ||
}) | ||
}, []) | ||
|
||
return ( | ||
<div className={styles.container}> | ||
<div className={styles.subDescription}> | ||
<h3 style={{ color: 'green' }}>Optimized Table row re-rendering</h3> | ||
<ul> | ||
<li>This table renders the object array data using map().</li> | ||
<li>On edit, it renders only an "updated" table row using a memoized TableRow component.</li> | ||
</ul> | ||
</div> | ||
|
||
<form autoComplete='off'> | ||
<table> | ||
<thead> | ||
<tr> | ||
{headers?.map(column => ( | ||
<th key={column.id}> | ||
{column.name} | ||
</th> | ||
))} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{players?.map((player, rowIndex) => ( | ||
<TableRow | ||
key={player.id} | ||
rowIndex={rowIndex} | ||
nextIndex={(rowIndex === players.length - 1) | ||
? 0 : rowIndex + 1 | ||
} | ||
headers={headers} | ||
player={player} | ||
onEdit={handleCellUpdate} | ||
/> | ||
))} | ||
</tbody> | ||
</table> | ||
</form> | ||
</div> | ||
) | ||
} | ||
|
||
export default MemoizedTable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { memo } from 'react' | ||
import PropTypes from 'prop-types' | ||
|
||
/** | ||
* Notes: | ||
* | ||
* This table row component re-renders only if its props changes. | ||
* props.onEdit, an anonymous function, while looking constant also re-renders | ||
* so be sure to wrap it in a useCallback hook in it's parent component. | ||
* | ||
* Try: | ||
* Observe this component's re-renders on the React Profile with and without the memo() hook. | ||
*/ | ||
function TableRow ({ | ||
nextIndex, | ||
rowIndex, | ||
headers, | ||
player, | ||
onEdit, | ||
key, | ||
idPrefix = 'm' | ||
}) { | ||
console.log(`--Re-rendering for update: ${player.name}`) | ||
|
||
const handlePlayerEdit = (e, rowIndex, field) => { | ||
const { value } = e.target | ||
if (player[field] === parseFloat(value)) return | ||
|
||
onEdit(rowIndex, field, value) | ||
} | ||
|
||
const handleKeyDown = (e, fieldIndex) => { | ||
// Move cursor to next row | ||
const { keyCode } = e | ||
if (keyCode !== 13) return | ||
|
||
const nextId = `${idPrefix}-cell-${nextIndex}-${fieldIndex}` | ||
const next = document.getElementById(nextId) | ||
next?.focus() | ||
} | ||
|
||
return ( | ||
<tr key={key}> | ||
{headers?.map((field, fieldIndex) => ( | ||
<td key={player.id}> | ||
{(['id', 'name'].includes(field.name)) | ||
? player[field.name] | ||
: <input | ||
id={`${idPrefix}-cell-${rowIndex}-${fieldIndex}`} | ||
type='text' | ||
defaultValue={player[field.name]} | ||
onBlur={(e) => handlePlayerEdit(e, rowIndex, field.name)} | ||
onFocus={(e) => e.target.select()} | ||
onKeyDown={(e) => handleKeyDown(e, fieldIndex)} | ||
/> | ||
} | ||
</td> | ||
))} | ||
</tr> | ||
) | ||
} | ||
|
||
TableRow.propTypes = { | ||
nextIndex: PropTypes.number, | ||
rowIndex: PropTypes.number, | ||
headers: PropTypes.object, | ||
player: PropTypes.arrayOf(PropTypes.object), | ||
onEdit: PropTypes.func, | ||
key: PropTypes.number, | ||
idPrefix: PropTypes.string | ||
} | ||
|
||
export default memo(TableRow) |
83 changes: 83 additions & 0 deletions
83
client/src/features/memo/components/unoptimizedtable/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { useEffect, useState } from 'react' | ||
|
||
import TableRow from '../tablerow' | ||
|
||
import characters from '@/data/characters.json' | ||
import styles from '../../tablesdemo/TablesDemo.module.css' | ||
|
||
const UnoptimizedTable = () => { | ||
const [players, setData] = useState(characters) | ||
const [headers, setHeaders] = useState([]) | ||
|
||
useEffect(() => { | ||
if (headers.length === 0) { | ||
setHeaders(Object.keys(players[0]).map((key, id) => ({ | ||
id, | ||
name: key | ||
}))) | ||
} | ||
}, [headers, players]) | ||
|
||
// Wrap anonymous functions in useCallback() to prevent re-renders on child components. | ||
// Sometimes, local state may need to be included in its dependency array | ||
const handleCellUpdate = (rowId, field, newValue) => { | ||
setData((prevData) => { | ||
const tempData = [...prevData] | ||
const updatedValue = parseFloat(newValue) | ||
|
||
// Update only the affected field in an object element | ||
if (tempData[rowId][field] !== updatedValue) { | ||
tempData[rowId] = { | ||
...tempData[rowId], [field]: updatedValue | ||
} | ||
} | ||
|
||
return tempData | ||
}) | ||
} | ||
|
||
return ( | ||
<div className={styles.container}> | ||
<div className={styles.subDescription}> | ||
<h3 style={{ color: 'red' }}>Table re-rendering all rows (WARNING!)</h3> | ||
<ul> | ||
<li>This table renders the object array data using map().</li> | ||
<li>It‘s using a memoized TableRow component but</li> | ||
<li>it's handleCellUpdate() method, an anonymous function is not memoized using useCallback().</li> | ||
<li>On edit, it renders all table rows.</li> | ||
</ul> | ||
</div> | ||
|
||
<form autoComplete='off'> | ||
<table> | ||
<thead> | ||
<tr> | ||
{headers?.map(column => ( | ||
<th key={column.id}> | ||
{column.name} | ||
</th> | ||
))} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{players?.map((player, rowIndex) => ( | ||
<TableRow | ||
key={player.id} | ||
rowIndex={rowIndex} | ||
nextIndex={(rowIndex === players.length - 1) | ||
? 0 : rowIndex + 1 | ||
} | ||
headers={headers} | ||
player={player} | ||
onEdit={handleCellUpdate} | ||
idPrefix='u' | ||
/> | ||
))} | ||
</tbody> | ||
</table> | ||
</form> | ||
</div> | ||
) | ||
} | ||
|
||
export default UnoptimizedTable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import TablesDemo from './tablesdemo' | ||
|
||
export { | ||
TablesDemo | ||
} |
Oops, something went wrong.