I have this issue with my React component I cannot figure out. It scrolls back to the top each time it re-renders. And also, I can't figure out why it is re-rendering in the first place. Basically I have a grandparent, parent, and grandchild component. The grandchild has a click event that displays a different child of the grandparent, if that makes any sense. That click event does a few things, but it is causing a re-render of the grandparent which causes the parent component to scroll to the top. I will add in my code which will hopefully clear up what I'm saying here. Just wanted to give some context.
So the main issue is the scrolling to the top. If we can figure out why it is re-rendering and stop that, that is a bonus.
Grandparent (StaticLine.js):
import React, { useEffect, useState } from 'react';
import StaticOrderColumn from '../ordercolumn/StaticOrderColumn';
import { useGlobalLineTypeActionsContext } from '../../context/GlobalLineTypeContext';
import Details from '../details/DetailsColumn';
import { ModalStyles } from '../modals/ModalStyles';
import {
useScheduleActionsContext,
useScheduleContext
} from '../../context/ScheduleContext';
import PalletCount from './PalletCount';
import OrderButtons from './OrderButtons';
import PropTypes from 'prop-types';
import {
useGlobalShowDetailsActionsContext,
useGlobalShowDetailsContext
} from '../../context/GlobalShowDetailsContext';
import { useTraceUpdate } from '../../utils/hooks';
const StaticLine = (props) => {
useTraceUpdate(props);
console.log('top of StaticLine');
const { type, setTitle } = props;
const { orderType, orders } = useScheduleContext();
const { setOrders, setOrderType } = useScheduleActionsContext();
const setGlobalLineType = useGlobalLineTypeActionsContext();
const showDetails = useGlobalShowDetailsContext();
const setShowDetails = useGlobalShowDetailsActionsContext();
const [lineID, setLineID] = useState(-1);
const [lineTitle, setLineTitle] = useState('');
const [highlightDest, setHighlightDest] = useState(false);
const ordersCol = React.useRef();
let lineCountOffset = 0;
let numLines = 4;
let pageTitle = '';
switch (type) {
case 'bagline':
pageTitle += 'Bag Lines';
break;
case 'toteline':
pageTitle += 'Tote Lines';
lineCountOffset = 10;
break;
case 'otherline':
pageTitle += 'Other Lines';
numLines = 3;
lineCountOffset = 4;
break;
default:
}
useEffect(() => {
setLineID(-1);
}, [type]);
const globalLineType = type + 's';
useEffect(() => {
setGlobalLineType(globalLineType);
setTitle(pageTitle);
const title = `${process.env.REACT_APP_BASE_TITLE ||
'title'} - ${pageTitle}`;
document.title = title;
}, [type, setGlobalLineType, pageTitle, setTitle, globalLineType]);
const selectLine = (e) => {
setShowDetails(false);
setHighlightDest(false);
const lineNum = e.target.value.substring(4);
setLineID(parseInt(lineNum, 10));
setLineTitle(
orderType.charAt(0).toUpperCase() +
orderType.slice(1) +
' Orders - Line ' +
parseInt(lineNum, 10)
);
};
const selectOrderType = (e) => {
const selectedOrderType = e.target.value;
setOrderType(selectedOrderType);
setShowDetails(false);
setLineTitle(
selectedOrderType.charAt(0).toUpperCase() +
selectedOrderType.slice(1) +
' Orders - Line ' +
lineID
);
};
const OrderColWithRef = React.forwardRef((props, ref) => (
<StaticOrderColumn
{...props}
title={lineTitle}
lineID={lineID}
orders={orders}
ref={ref}
/>
));
return (
<div
className={`staticLines p-1 no-gutters d-flex flex-nowrap${
orderType === 'completed' ? ' completed' : ''
}`}
>
<div className={'radio-col no-border'}>
<div className={'radio-container p-2'}>
<div className={'radios'}>
// lots of irrelevant code here
</div>
</div>
</div>
{lineID > -1 && (
<>
<div
className={'col lines no-gutters order-col'}
>
<OrderColWithRef ref={ordersCol} />
</div>
<div className={'col row lines no-gutters order-details'}>
<Details
setOrders={setOrders}
orders={orders || []}
customStyles={ModalStyles}
highlightDest={highlightDest}
setHighlightDest={setHighlightDest}
errLocation={'top-center'}
/>
{orderType === 'completed' && showDetails && (
<OrderButtons
setLineID={setLineID}
setOrders={setOrders}
orders
lineNum={lineID}
/>
)}
</div>
<div className={'col lines no-gutters d-flex no-border'}>
{orderType === 'scheduled' && (
<PalletCount
type={'Bag'}
lineNum={lineID}
orders={orders}
setTitle={setTitle}
setHighlightDest={setHighlightDest}
/>
)}
</div>
</>
)}
</div>
);
};
StaticLine.propTypes = {
type: PropTypes.string.isRequired,
orders: PropTypes.array,
setTitle: PropTypes.func.isRequired
};
export default StaticLine;
Parent (StaticOrderColumn.js):
import React from 'react';
import PropTypes from 'prop-types';
import StaticOrder from '../order/StaticOrder';
import '../../scss/App.scss';
import { useGlobalSpinnerContext } from '../../context/GlobalSpinnerContext';
const StaticOrderColumn = (props) => {
const { title, lineID, orders } = props;
const isGlobalSpinnerOn = useGlobalSpinnerContext();
const sortedOrdersIDs = orders
.filter((o) => o.lineNum === lineID)
.sort((a, b) => a.linePosition - b.linePosition)
.map((o) => o.id);
return (
<div id={'line-0'} className={'col order-column'}>
<header className={'text-center title'}>
{title}{' '}
{sortedOrdersIDs.length > 0 && (
<span> ({sortedOrdersIDs.length})</span>
)}
</header>
<div className={'orders'}>
{orders &&
sortedOrdersIDs &&
sortedOrdersIDs.map((orderID, index) => {
const order = orders.find((o) => o.id === orderID);
return (
<StaticOrder
key={orderID}
order={order}
index={index}
/>
);
})}
{!sortedOrdersIDs.length && !isGlobalSpinnerOn && (
<h3>There are no orders on this line.</h3>
)}
</div>
</div>
);
};
StaticOrderColumn.propTypes = {
title: PropTypes.string.isRequired,
lineID: PropTypes.number.isRequired,
orders: PropTypes.array.isRequired,
ref: PropTypes.instanceOf(Element).isRequired
};
export default StaticOrderColumn;
This file is where the click event happens and causes the re-render of StaticLine and scroll to top for StaticOrderColumn. Grandchild (StaticOrder.js):
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import '../../scss/App.scss';
import { getFormattedDate } from '../../utils/utils';
import { useGlobalActiveOrderActionsContext } from '../../context/GlobalActiveOrderContext';
import { useGlobalShowDetailsActionsContext } from '../../context/GlobalShowDetailsContext';
// import { stringTrunc } from '../../utils/utils';
const MyOrder = styled.div`
background-color: #193df4;
transition: background-color 1s ease;
`;
const devMode = true;
// const devMode = false;
const StaticOrder = (props) => {
const {
id,
item,
desc,
cust,
palletsOrd,
bagID,
chemicals,
totalBagsUsed,
linePosition,
palletsRem,
palletCount,
requestDate,
orderNumber,
comments
} = props.order;
const setActiveOrder = useGlobalActiveOrderActionsContext();
const setShowDetails = useGlobalShowDetailsActionsContext();
const orderID = id + '';
// show the details section when user clicks an order
// THIS IS WHERE THE ISSUE IS HAPPENING, WHEN THE ORDER IS CLICKED,
// THIS FUNCTION RUNS AND THE StaticLine COMPONENT RE-RENDERS AND THE StaticOrderColumn SCROLLS TO THE TOP
const showDetails = (orderID) => {
setActiveOrder(parseInt(orderID, 10));
setShowDetails(true);
};
return (
<MyOrder
id={orderNumber}
className={'order static'}
onClick={(e) => showDetails(orderID, e)}
>
{/*<div className={'orderID'}>{id}</div>*/}
<p className={'item-number'}>
{item !== '' ? `Item Number: ${item}` : ''}
</p>
<p>{desc !== '' ? `NPK: ${desc}` : ''}</p>
<p>{cust !== '' ? `Customer: ${cust}` : ''}</p>
<p>
{palletsOrd !== '' ? `Pallets Ordered: ${palletsOrd}` : ''}
</p>
<p>{bagID !== '' ? `Bag ID: ${bagID}` : ''}</p>
<p>{chemicals !== '' ? `Chemical : ${chemicals}` : ''}</p>
<p>
{requestDate !== ''
? `Request Date: ${getFormattedDate(new Date(requestDate))}`
: ''}
</p>
{devMode && (
<>
<div className={'id-line-num-pos'}>
<p>OrderID: {orderNumber}</p>
</div>
</>
)}
<div className={'total-bags'}>Total Bags: {totalBagsUsed}</div>
<div className={'pallets-remaining'}>
Pallets Left: {palletsRem}
</div>
<div className={'pallets-done'}>
Pallets Done: {palletCount}
</div>
<div className={'line-position'}>{linePosition + 1}</div>
{comments.length > 0 && (
// bunch of SVG code
)}
</MyOrder>
);
};
StaticOrder.propTypes = {
order: PropTypes.object,
id: PropTypes.number,
index: PropTypes.number,
title: PropTypes.string,
orderID: PropTypes.string
};
export default StaticOrder;
Edit: I'm adding a picture of the problem to help you all visualize it as well. The order boxes are on the left side of this image. By default the "Order Details" is hidden. When an order on the left is clicked, it loads that order into the Order Details and shows that component. When that happens, that orders column on the left scrolls back to the top. On subsequent order clicks, it does not scroll to the top. Only when it shows or hides the "Order Details" does it scroll back to the top.

Edit 2: I figured out if I take out the const showDetails = useGlobalShowDetailsContext(); line and the 2 references to showDetails out of StaticLine.js, this issue goes away. So if that helps anyone figure something out...
Edit 3: I'm slowly edging forward. I figured out how I could remove one of the references to showDetails in the StaticLine.js file. Now if someone could help me figure out how to get that last reference out of that component but keep the functionality that would be amazing!!
For a reminder this is the reference I am talking about:
{orderType === 'completed' && showDetails && (
<OrderButtons
setLineID={setLineID}
setOrders={setOrders}
orders
lineNum={lineID}
/>
)}
Let me know if more info or more code is needed. Any insight would be very much appreciated.
const OrderColWithRef = React.forwardRef((props, ref) => (
<StaticOrderColumn
{...props}
title={lineTitle}
lineID={lineID}
orders={orders}
ref={ref}
/>
));
Move this outside the StaticLine as a top level function.
React is smart enough to avoid recreating html elements and mounting stuff when only parts of the Dom changed. If only a prop changes it will preserve the element and just change its values etc. It is done by comparing the element.type.
What you are effectively doing is creating a new OrderColWithRef function on each render, since it's a local function, the types are not equal. React will unmount and remount a new html element every time anything in StaticLine changes.
Never ever nest component declarations. The only case where declaring a component inside a function would be valid would be a HOC, and even then, the HOC function itself isn't a valid element on its own, only it's return value is.
Hope this clears up things.
My issue was treating the thing taking in new data as a Component and not as a function that returns a component.
Changing <NewsList /> to {newsList()} did the trick.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With