Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RangeError: Invalid array length at Array.push (<anonymous>) #2974

Open
andremorescocia opened this issue Nov 21, 2024 · 8 comments
Open

RangeError: Invalid array length at Array.push (<anonymous>) #2974

andremorescocia opened this issue Nov 21, 2024 · 8 comments

Comments

@andremorescocia
Copy link

Describe the bug
Updating to Version 4.x results in Error:

RangeError: Invalid array length
at Array.push ()
at y (yoga-wasm-base64-esm.js:1273:1)
at 00047d12:0xd3fc
at 00047d12:0xb7e
at 00047d12:0x9b62
at 00047d12:0xa5a5
at 00047d12:0x5a9c
at 00047d12:0x769c
at 00047d12:0xc3cf
at 00047d12:0x76d7

The error occurs when generating a PDF with a very large volume of data (array.length > 5000). Even after taking more than 3 minutes, it eventually causes the error.

We tested using manual pagination, such as chunks, by defining the number of items per page, and it worked in a time 10 times faster. However, this solution is not viable because we cannot fix the number of items per page, as each generated PDF contains fields with variable sizes.

Expected behavior
It is expected that the error mentioned above does not occur and that the PDF generation has better performance using its native pagination functions.

Desktop (please complete the following information):

  • OS: [MacOS, Windows]
  • Browser [chrome, safari]
  • React-pdf/renderer at anything over 3.4.5
@diegomura
Copy link
Owner

diegomura commented Nov 26, 2024

@andremorescocia can you provide a minimum reproducible case? This worked in version 3? Seems like yoga is hitting a limit. Nothing there changed recently

@maciejkuran
Copy link

maciejkuran commented Nov 27, 2024

Hey @diegomura, I hope you're doing okay :) I will bump up this thread because I get the same RangeError. Please, find below more details. Massive thanks in advance for your help.

Im using latest 4.1.5 version of @react-pdf/renderer. I tried to downgrade to lower versions, but nothing helped.

The error I get.

pdf.worker.ts:20 RangeError: Invalid array length
    at Array.push (<anonymous>)
    at y (yoga-wasm-base64-esm.js:63:248)
    at 00047d12:0xd3fc
    at 00047d12:0xb7e
    at 00047d12:0x9b62
    at 00047d12:0xa5a5
    at 00047d12:0x5a9c
    at 00047d12:0x769c
    at 00047d12:0xc3cf
    at 00047d12:0x76d7

The error above indicates an exceeding of limits with internal data structures in yoga - I assume.

It happens for super complex trees that I recursively render in PDF document - for less complex trees this render algo works very well as expected, enabling download. I am attaching a test tree.txt file (JS object), that actually produces this error log.

*UPDATE (uploaded object with the proper structure, ready for processing)
tree.txt

I'm implementing a PDF download in Next.js app 14.2.3. The <ObjectItem/> is the component that produces the following error. It accepts the treeData through the props which is the tree.txt object I attached above.

Input data structure interface that I iterate through.

export interface TransformedNode {
  name: string;
  object_id: string;
  node_id?: string;
  work_id: string;
  object_type: string;
  object_subtype?: string;
  size: number;
  error?: string;
  total_size?: number;
  number_of_objects?: number;
  skipped_objects?: boolean;
  all_infected_symbols?: string[];
  infected_symbols?: string[];
  infected?: boolean;
  hashes: {
    sha256: string;
    [key: string]: any;
  };
  match_search?: boolean;
  children?: TransformedNode[] | [];
  ok?: {
    object_metadata: {
      name?: string;
      format?: string;
      natural_language?: string;
      programming_language?: string;
      nsfw_verdict?: string;
      [key: string]: any;
    };
    [key: string]: any;
  };
  relation_metadata: {
    name?: string;
    [key: string]: any;
  };
  navigation_nodes?: NavigationNode[];
  [key: string]: any;
}

Component code

import { View, Text } from '@react-pdf/renderer';

import { styles } from './ObjectItem.styles';

import { TransformedNode } from '@/app/(workspace)/analysis/_shared/types';

import { forbiddenObjectKeys } from '@/app/(workspace)/analysis/_shared/components/StackSection/AccordionItem';
import { globalPdfStyles } from '@/_shared/styles/globalPdfStyles';

interface ObjectItemProps {
  treeData: TransformedNode;
}

const ObjectItem = ({ treeData }: ObjectItemProps): JSX.Element => {
  const shouldRender = (value: any): boolean => {
    if (value === null || value === '' || value === undefined) return false;
    if (Array.isArray(value)) {
      return value.some(item => shouldRender(item));
    }
    if (typeof value === 'object' && Object.keys(value).length === 0) return false;
    return true;
  };

  const renderValue = (value: any, indentLevel: number): JSX.Element | string => {
    if (Array.isArray(value)) {
      return (
        <View style={[styles.arrayWrapper, { marginLeft: indentLevel * 2 }]}>
          {value.map((item, index) => (
            <View wrap={false} key={index}>
              {typeof item === 'object' ? (
                <View style={[styles.objectWrapper, { marginLeft: (indentLevel + 1) * 2 }]}>
                  {Object.entries(item)
                    .filter(([nestedKey, nestedValue]) => shouldRender(nestedValue))
                    .map(([nestedKey, nestedValue]) => (
                      <View wrap={false} key={nestedKey} style={styles.objectField}>
                        <Text style={globalPdfStyles.boldText}>{nestedKey.replace(/_/g, ' ')}</Text>
                        {renderValue(nestedValue, indentLevel + 2)}
                      </View>
                    ))}
                </View>
              ) : (
                <View wrap={false} style={styles.valueWrapper}>
                  <Text style={styles.value}>{item.toString()}</Text>
                </View>
              )}
            </View>
          ))}
        </View>
      );
    }

    if (typeof value === 'object' && value !== null) {
      return (
        <View style={[styles.objectWrapper, { marginLeft: indentLevel * 2 }]}>
          {Object.entries(value)
            .filter(([nestedKey, nestedValue]) => shouldRender(nestedValue))
            .map(([nestedKey, nestedValue]) => (
              <View key={nestedKey} style={styles.objectField}>
                <Text style={globalPdfStyles.boldText}>{nestedKey.replace(/_/g, ' ')}</Text>
                {renderValue(nestedValue, indentLevel + 1)}
              </View>
            ))}
        </View>
      );
    }

    //Known problem with word break NOT SUPPORTED. A workaround solution taken from -> https://github.com/diegomura/react-pdf/issues/248 e.g. to make long strings like sha... just word break
    return (
      <View wrap={false} style={styles.valueWrapper}>
        <Text style={styles.value}>
          {value
            ?.toString()
            .split('')
            .map((s: string, index: number) => {
              return <Text key={index}>{s}</Text>;
            }) || 'N/A'}
        </Text>
      </View>
    );
  };

  const renderObject = (obj: TransformedNode, indentLevel: number): JSX.Element[] => {
    return Object.entries(obj)
      .filter(
        ([key, value]) => ![...forbiddenObjectKeys, 'children'].includes(key) && shouldRender(value)
      )
      .map(([key, value]) => (
        <View key={key} style={[styles.objectField, { marginLeft: indentLevel * 2 }]}>
          <Text style={[globalPdfStyles.boldText, { color: '#0053D8' }]}>
            {key.replace(/_/g, ' ')}
          </Text>
          {renderValue(value, indentLevel + 1)}
        </View>
      ));
  };

  return (
    <View style={[styles.container]}>
      <View style={[styles.wrapper]}>
        <Text
          id={`object-${treeData.node_id}`}
          style={[globalPdfStyles.subHeading, { fontSize: 13 }]}
        >
          ({treeData.node_id}) {treeData.object_type}{' '}
          {treeData.object_subtype && `(${treeData.object_subtype})`}
        </Text>
        {renderObject(treeData, 0)}
      </View>
      {treeData.children?.map((child, index) => (
        <ObjectItem key={`${child.object_id}-${index}`} treeData={child} />
      ))}
    </View>
  );
};

export default ObjectItem;

Additionally attaching styles.

import { StyleSheet } from '@react-pdf/renderer';

export const styles = StyleSheet.create({
  container: {
    padding: 5,
  },
  wrapper: {
    marginBottom: 30,
  },
  objectField: {
    marginBottom: 5,
  },
  valueWrapper: {
    justifyContent: 'center',
    marginLeft: 5,
    padding: '3pt 10pt',
    border: '1pt solid #e7e7e7',
    borderRadius: 5,
  },
  value: {
    fontSize: 9,
    color: '#555',
  },
  arrayWrapper: {
    marginTop: 5,
    padding: 5,
    rowGap: 3,
  },
  objectWrapper: {
    marginTop: 5,
    borderLeft: '1pt solid #e7e7e7',
    padding: 5,
  },
});

Feel free @diegomura to ask more questions, but tbh I don't know what else I should add here. Thanks!

@gabedemesa
Copy link

gabedemesa commented Dec 3, 2024

I am having the same issue. We are trying to generate 10k+ pdfs but also running into memory issues with WASM especially for larger pdfs.

@vedadsose
Copy link

Same issue on 4.1.5, I downgraded to 3.x and seems to be working well for the same content (pdf with ~100 pages)

@maciejkuran
Copy link

maciejkuran commented Dec 5, 2024

@vedadsose I don't think that the number of pages really matter in this case, but what matters is how we render things in the document. I assume, the problem lies in recursion depth limits - maybe not, idk.

I can generate ~200 pages PDF successfully on complex trees, why it fails on SOME specific trees like the one I uploaded, where I believe recursive depth is a key factor. In general, I would say it's a lottery as to what will succeed and what won't.

I also tried to downgrade to lower versions, including 3.x but it didn't work out. Can you please tell what version exactly you're using. I would test it.

@diegomura any update?

@vedadsose
Copy link

@maciejkuran I'm using 3.1.14 now

@andremorescocia
Copy link
Author

Hello everyone!

@diegomura , sorry for the delay in getting back to you. Here is the link to the repository for you to run the tests as requested...

The explanation of how it works will be provided below and also in the README.txt file in the repository.


Explanation:
A JSON file was used to simulate PDF generation.
The JSON has the structure of a header and data...
In the data, we have some subheaders and the listaMovimentos, which contains the large volume of data.

The listaMovimentos in the standard JSON contains 5787 items.

If you remove items until there are 5711 left, the PDF will be generated successfully! It will take almost 4 minutes...
And as you reduce the size of listaMovimentos, the generation time also decreases.

If you remove items down to 5712, the PDF will not be generated, causing the error mentioned in ISSUE 2974.
Above 5712, it will no longer work. And until the error occurs, it takes around 3 to 3.5 minutes.

Formula to calculate how many lines should be removed from the JSON to reach the desired number of items:

1 item in the list = 7 lines in the JSON -> A
Number of items to remove -> B

A * B = Total number of lines that must be removed from the listaMovimentos in the JSON.

In any case, more than 3 minutes to generate a PDF is very slow, especially for the end-user using it in production.

This occurs when clicking Button 1...
File: GeneratorPdf.tsx


Button 2 has a manual pagination solution, setting the number of items per page, among other things. However, it is not the best solution.
Although it is more than 5 times faster, there is no way to determine the size of each line in every report generated, making this type of management unfeasible for all reports.

File: GeneratorPdfManually.tsx


You can try it and notice the difference in execution times between them.

You can delete the node_modules folder and yarn.lock and change the version of @react-pdf/renderer in the package.json.
A version below 4.x will work, while any version above 4.x will not work.

If you have any questions, feel free to reach out.

@maciejkuran
Copy link

@vedadsose thanks! unfortunately it doesn't change anything in my case.

I tested 3.1.14, and the 'range' error is not showing up, however the processing lasts 4ever, literally endless!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants