'Why is an element found using querySelector null in Nextjs/React app when document has been loaded? [duplicate]

I am using the Observer API.

I try to find the div with the id of plTable, but it returns null.

I thought this is because the document has not been loaded so I made sure document has been loaded, then I call document.querySelector inside the conditional.

However, it still cannot find the div with id of plTable, even when I can find the div on in the document.

I know when using Next.js, the DOM/document renders late (read this on another stackoverflow question)? So I check if it has been loaded first.


Therefore, when used in endObserver.observe(plTable),

I get the error:

TypeError: Failed to execute 'observe' on 'IntersectionObserver': parameter 1 is not of type 'Element'.


The code is below:

const LineItemTable: React.FC<LineItemTableProps> = ({ reportName }) => {
  const classes = useStyles({});
  const dispatch = useDispatch();
  const [page, setPage] = useState<number>(0);

  const selectedCompanyId = useSelector((state) => state.company.selectedId);
  const company = useSelector((state) => state.company.current);

  useEffect(() => {
    if (reportName && selectedCompanyId) {
      dispatch(
        getReportByName({
          name: reportName,
          includeLineItems: true,
          page: page,
        }),
      );
    }
  }, [reportName, selectedCompanyId]);

  let plTable: any = 'kk';

  if (typeof document !== 'undefined') {
    plTable = document.querySelector('#plTable') as Element;
    console.log('doc', document); // **** this is logged properly *****
    console.log('plTable', plTable); // **** this NULL ******
  }

  useEffect(() => {
    const endObserver = new IntersectionObserver(
      (entries) => {
        const [entry] = entries;
        if (!entry.isIntersecting) {
          
          console.log('not visible');
        } else {
      
          // setPage(page + 1);
          console.log('visible');
        }
      },
      { root: null, threshold: 1 },
    );
    if (typeof document !== 'undefined') {
      console.log('ah', plTable); // ****** this is null ******
      endObserver.observe(plTable); // ******* this Throws an error *******
    }
  });

  const getLineItems = useMemo(() => makeGetAllLineItemsByReport(reportName), [
    reportName,
  ]);

  const lineItems = useSelector((state) => getLineItems(state));

  if (!lineItems) return null;

  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'flex-end',
        margin: 'auto 0px',
      }}
    >
      <Grid container spacing={3}>
        <Grid item xs={12}>
          <Card
            sx={{
              padding: '20px',
            }}
          >
            <CardContent
              sx={{
                alignItems: 'center',
                display: 'flex',
                height: '1000px',
              }}
            >
              <Scrollbar className={classes.scrollBar}>
                <Table className={classes.root}>
                  <TableHead>
                    <TableRow>
                      <th>
                        <TableCell className={classes.headerStyle}>
                          ANALYSIS CATEGORY
                        </TableCell>
                        <TableCell
                          className={classes.headerStyle}
                          sx={{ marginRight: '10px' }}
                        >
                          NAME
                        </TableCell>
                        {company &&
                          company.dates.map((header) => (
                            <TableCell
                              className={classes.headerStyle}
                              sx={{
                                width: '200px !important',
                                marginLeft: '10px',
                              }}
                              key={header}
                            >
                              {header}
                            </TableCell>
                          ))}
                      </th>
                    </TableRow>
                  </TableHead>
                  <TableBody>
                    {lineItems.map((lineItem, i) => (
                      <TableRow key={lineItem.id}>
                        <LineItemRow
                          i={i}
                          id={lineItem.id}
                          reportName={reportName}
                          level={lineItem.level}
                        />
                        {i === 19 ? (
                          <div id="plTable"> // ****** here is the div ******
                            hihi
                          </div>
                        ) : (
                          <></>
                        )}
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </Scrollbar>
            </CardContent>
          </Card>
        </Grid>
      </Grid>
    </div>
  );
};

const LineItemRow: React.FC<{
  id: number;
  reportName: ReportName;
  //colSpan: number;
  level?: number;
  i: number;
}> = React.memo(({ id, reportName, level = 0 }) => {
  const dispatch = useDispatch();
  const lineItem = useSelector((state) =>
    lineItemSelectors.selectById(state, id),
  );
  const company = useSelector((state) => state.company.current);

  // TODO: find more consistent way to show total rows
  const classes = useStyles({
    level: lineItem?.name.includes('Total') ? level - 1 : level,
  });

  const handleAnalysisCategoryChange = useCallback(
    (
      event: React.ChangeEvent<{
        name?: string;
        value: unknown;
        event: Event | SyntheticEvent<Element, Event>;
      }>,
    ) => {
      const cat = event.target.value as string;
      dispatch(
        updateAnalysisCategory({
          lineItemId: id,
          catId: cat && cat !== '' ? Number(cat) : null,
        }),
      );
    },
    [dispatch, id],
  );

  if (!lineItem) return null;

  return (
    <div>
      <TableCell
        className={clsx([
          classes.tableCell,
          classes.tableCellAnalysisCat,
          classes.indent,
        ])}
      >
        <AnalysisCategorySelect
          reportName={reportName}
          onChange={handleAnalysisCategoryChange}
          value={lineItem.analysisCategoryId || ''}
        />
      </TableCell>
      <TableCell
        className={clsx([
          classes.tableCell,
          classes.tableCellName,
          classes.indent,
        ])}
      >
        <Typography color="textPrimary" variant="subtitle2">
          {lineItem.name}
        </Typography>
      </TableCell>
      {!lineItem.isGroup &&
        company &&
        company.dates.map((date, i) => (
          <TableCell className={classes.tableCell} key={`${lineItem.id}-${i}`}>
            <Typography color="textPrimary" variant="subtitle2">
              <NumberFormat
                value={lineItem.entries[date]}
                displayType={'text'}
                thousandSeparator={true}
                prefix={'$'}
              />
            </Typography>
          </TableCell>
        ))}
      {lineItem.isGroup &&
        company &&
        company.dates.map((date, i) => (
          <TableCell
            className={classes.tableCell}
            key={`${lineItem.id}-${i}`}
          ></TableCell>
        ))}
    </div>
  );
});

export default LineItemTable;

Why is the plTable div found using querySelector returning null, when clearly, document is defined, and the div is viewable in the document.

How can I fix this?



Solution 1:[1]

Instead of using querySelector you could use useRef

const ref = useRef();

useEffect(() => {
  console.log(ref.current)
},[])

<div ref={ref} id='plTable'>
...
</div>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 khierl