Home > database >  Large base64 image produces "Maximum call stack size exceeded" in React
Large base64 image produces "Maximum call stack size exceeded" in React

Time:10-28

I am storing webpage content as a byte[] logo and an html body in database. When I click to load a specific category group, I get "Maximum call stack size exceeded" if the logo image is too large (in this case > 200kB). But if I store the same image as a base64 tagged image inside the html body, it doesn't fail. I've searched and searched but I cant find an explanation for this behavior. I searched for other answers. I changed the lay I am storing the image. I tested different sizes. I expect the logo to load async regardless of size. I'm newer to React; any guidance would be appreciated.

Here is the MemberBenefit object:

export default interface MemberBenefit {
    Id: string;
    Name: string;
    CategoryId: string;
    Logo: ImageFile;
    Detail: string;
}

export interface MemberBenefitCategory{
    id: string;
    Name: string;
    ParentId: string;
}

export interface ImageFile{
    data: number[];
    contentType: string;
    fileName: string;
}

The code where the logo and body are rendered is here:

<Accordion.Collapse eventKey={benefit.Id.toString()}>
    <Card.Body>
        {benefit.Logo && 
            <>
                <img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
            </>
        }
        <section 
            className="not-found-controller"
            dangerouslySetInnerHTML={{ __html: benefit.Detail }}
        />
    </Card.Body>
</Accordion.Collapse>

Here is the whole TSX file for reference. I'm sure I am being very inefficient in may places so I appreciate all constructive feedback.

import React, { useEffect, useState } from "react"
import {Container, Row, Col, Breadcrumb, Button, Card, Accordion} from "react-bootstrap"
import {Redirect, Link} from "react-router-dom"
import ClientApi from "../../api/ClientApi"
import {ContainsAuthExpiredError} from "../../models/ErrorDetail"
import BenefitGroupPicker from "../../views/benefits/BenefitGroupPicker"
import PageHeading from "../../views/PageHeading"

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import MemberBenefit, { MemberBenefitCategory } from "../../models/MemberBenefit"
import { faBriefcase, faCar, faCarrot, faCircle, faDollarSign, faFaceGrinWide, faHeartPulse, faHouseChimney, faPlaneUp, faSailboat, faSquare, faWrench } from "@fortawesome/free-solid-svg-icons"



const Index: React.FC = () => {
    const {errors, payload} = ClientApi.Account.UseAccountOverview()    
    const accountOverview = payload
    const redirectToDisabled = accountOverview?.MembershipSummary.IsDelinquent ?? false 
    
    const [categories, set_categories] = useState<MemberBenefitCategory[] >([]);    
    const [selectedParentCategoryIndex, set_selectedParentCategoryIndex] = useState<number | null>(null);
    const [loadedMemberBenefits, set_loadedMemberBenefits] = useState<MemberBenefit[] | null>(null);

    useEffect(() => {
        let mounted = true
        const fetchData = async () => {
            var [_getCategories, _error] = await ClientApi.Benefits.GetMemberBenefitCategories();           

            if (mounted) {
                if (_error.length > 0) {
                    //set_benefitsLoadingErrors(allErrors)
                } else {
                    set_categories(_getCategories ?? [])
                }
            }
        }
        fetchData()

        return () => {
            mounted = false
        }
    }, [])

    const LoadParent = async (parentCategoryId:string) =>{
        var [_getMemberBenefits, _error] = await ClientApi.Benefits.GetMemberBenefits(parentCategoryId ?? 0);
        set_loadedMemberBenefits(_getMemberBenefits)
        set_selectedParentCategoryIndex(categories.findIndex(category=> category.id == parentCategoryId))
    }
    
    // maybe fold these into 1 redirection checker function?
    if (ContainsAuthExpiredError(errors)) {
        return <Redirect to='/login' />
    }
    if (redirectToDisabled) {
        return <Redirect to='/memberBenefits/Disabled' />
    }
    if (accountOverview && accountOverview.MembershipSummary.IsTerminated) {
        return <Redirect to='/myMembership/PayDues' />
    }

    return (
        <div className='component_BenefitsIndex'>
            <PageHeading>
                Member <b>Benefits</b>
            </PageHeading>
            
            <Container className='pageContent'>
                <div className="d-lg-none d-xl-none ">
                    {selectedParentCategoryIndex == null  &&
                        <>
                            <div id="benefitsPicker" style={{marginTop:"15px"}}>
                                <Row style={{textAlign:"center",lineHeight:"48px"}}>
                                    <Col>
                                        <button onClick={() => LoadParent("5")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}}>
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faWrench} size='2x'/>                        
                                            </span>
                                            <div>Equipment</div>
                                        </button>                                
                                    </Col>
                                    <Col>
                                        <button onClick={() => LoadParent("1")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                        
                                                <FontAwesomeIcon style={{color:"white", fontSize:"30px", top:"-15px", left:"-15px"}} icon={faSailboat}/>                            
                                                <FontAwesomeIcon style={{color:"#FF671E", fontSize:"25px",top:"10px", left:"10px"}}  icon={faSquare}  />                                        
                                                <FontAwesomeIcon style={{color:"#FF671E", fontSize:"35px", top:"10px", left:"10px"}} icon={faCar} />                                                    
                                                <FontAwesomeIcon style={{color:"#FF671E", width:"100px", height:"10px", top:"15px", left:"-7px"}}  icon={faSquare}  />
                                                <FontAwesomeIcon style={{color:"white", fontSize:"30px", top:"10px", left:"10px"}} icon={faCar} />
                                            </span>
                                            <div>Auto/Boat</div>
                                        </button>           
                                    </Col>
                                    <Col>
                                        <button onClick={() => LoadParent("2")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faPlaneUp} size='2x'/>                       
                                            </span>
                                            <div>Travel</div>
                                        </button>           
                                    </Col>
                                </Row>
                                <Row style={{textAlign:"center",lineHeight:"48px"}}>
                                    <Col>
                                        <button onClick={() => LoadParent("6")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faDollarSign} size='2x'/>                        
                                            </span>
                                            <div>Financial</div>    
                                        </button>       
                                    </Col>
                                    <Col>
                                        <button onClick={() => LoadParent("3")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faHouseChimney} size='2x'/>                      
                                            </span>
                                            <div>Home/Farm</div>    
                                        </button>       
                                    </Col>
                                    <Col>
                                        <button onClick={() => LoadParent("7")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faHeartPulse} size='2x'/>                        
                                            </span>
                                            <div>Health</div>   
                                        </button>       
                                    </Col>
                                </Row>
                                <Row  style={{textAlign:"center",lineHeight:"48px"}}>
                                    <Col>
                                        <button onClick={() => LoadParent("4")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faFaceGrinWide} size='2x'/>                      
                                            </span>
                                            <div style={{marginTop: "11px",lineHeight: "25px"}}>Attractions/<br/>Sports</div>       
                                        </button>   
                                    </Col>
                                    <Col>
                                        <button onClick={() => LoadParent("8")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faBriefcase} size='2x'/>                     
                                            </span>
                                            <div>Career</div>
                                        </button>           
                                    </Col>
                                    <Col>
                                        <button onClick={() => LoadParent("9")}>
                                            <span className="fa-layers MemberBenefitsNav" style={{width:"6em"}} >
                                                <FontAwesomeIcon style={{color:"#FF671E"}}  icon={faCircle} size="4x" />                            
                                                <FontAwesomeIcon style={{color:"white"}} icon={faCarrot} size='2x'/>                        
                                            </span>
                                            <div>FAMA</div>     
                                        </button>   
                                    </Col>
                                </Row>
                            </div>
                        </>
                    }
                    {selectedParentCategoryIndex != null &&
                        <>
                            <button onClick={()=>set_selectedParentCategoryIndex(null)}>back</button>
                            <div className='blockNavContent'>
                                        <h2>{categories[selectedParentCategoryIndex].Name}</h2>
                                        
                                            {
                                                categories.filter(category=>{return  category.ParentId == categories[selectedParentCategoryIndex].id}).map(category=>
                                                    <Accordion key={category.id "sub"}>
                                                        <Card>
                                                            <Card.Header>
                                                                {/* This was Accordion.Toggle in old version */}
                                                                <Accordion.Toggle as={Button} variant="link" eventKey={category.id.toString()}>
                                                                    {category.Name} 
                                                                </Accordion.Toggle>
                                                            </Card.Header>
                                                            <Accordion.Collapse eventKey={category.id.toString()}>
                                                                <Card.Body>
                                                                    {
                                                                    loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == category.id.toString()).map(benefit=>                                           
                                                                        <Accordion key={benefit.Id  "subbenefit"}>
                                                                            <Card >
                                                                                <Card.Header>
                                                                                    {/* This was Accordion.Toggle in old version */}
                                                                                    <Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
                                                                                        {benefit.Name}
                                                                                    </Accordion.Toggle>
                                                                                </Card.Header>
                                                                                <Accordion.Collapse eventKey={benefit.Id.toString()}>
                                                                                    <Card.Body>
                                                                                        {benefit.Logo && 
                                                                                            <>
                                                                                                <img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
                                                                                            </>
                                                                                        }
                                                                                        <section 
                                                                                            className="not-found-controller"
                                                                                            dangerouslySetInnerHTML={{ __html: benefit.Detail }}
                                                                                        />
                                                                                    </Card.Body>
                                                                                </Accordion.Collapse>
                                                                            </Card>
                                                                        </Accordion>
                                                                    )
                                                                    }
                                                                </Card.Body>
                                                            </Accordion.Collapse>
                                                        </Card>
                                                    </Accordion>
                                                )
                                            }
                                            {
                                                loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == categories[selectedParentCategoryIndex].id.toString()).map(benefit=>                                            
                                                    <Accordion key={benefit.Id  "sub"}>
                                                        <Card >
                                                            <Card.Header>
                                                                {/* This was Accordion.Toggle in old version */}
                                                                <Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
                                                                    {benefit.Name}
                                                                </Accordion.Toggle>
                                                            </Card.Header>
                                                            <Accordion.Collapse eventKey={benefit.Id.toString()}>
                                                                <Card.Body>
                                                                    
                                                                    {benefit.Logo && 
                                                                        <>
                                                                            <img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
                                                                        </>
                                                                    }
                                                                    <section
                                                                        className="not-found-controller"
                                                                        dangerouslySetInnerHTML={{ __html: benefit.Detail }}
                                                                    />
                                                                </Card.Body>
                                                            </Accordion.Collapse>
                                                        </Card>
                                                    </Accordion>
                                                )
                                            }
                                        </div>
                        </>
                    }
                </div>
                <Row className="d-none d-lg-block">
                    <Col>
                        <div className="blockNavGrid">
                            <div className="blockNavsList">
                                {
                                    categories.filter(category=>{return category.ParentId == ""}).map(category=>
                                        
                                        <div key={category.id}>
                                            <Button onClick={() => LoadParent(category.id)} >
                                                {category.Name}
                                            </Button>
                                        </div>
                                        
                                    )
                                }
                            </div>
                            {selectedParentCategoryIndex == null &&
                            <>  
                                <div className='blockNavContent'>
                                    <div style={{marginBottom: "48px"}}>
                                        <img
                                            src='https://apps.floridafarmbureau.com/_cdn/img/MembershipMeansMoreHeader.svg'
                                            alt='Membership Means More'
                                        />
                                    </div>
                                    <p>
                                        Membership with Florida Farm Bureau means that you are part of a
                                        growing community of member families in Florida. Members, just
                                        like you, advocate for agriculture and grow as better leaders in
                                        our state through their membership. As a thank you for your
                                        membership, you are provided with access to over 30 companies to
                                        provide benefits and discounts.
                                    </p>
                                    <p>Click a section for more information. </p>
                                </div>
                            </>
                            }
                            {selectedParentCategoryIndex != null &&
                            <>  
                                <div className='blockNavContent'>
                                    <h2>{categories[selectedParentCategoryIndex].Name}</h2>
                                    
                                        {
                                            categories.filter(category=>{return  category.ParentId == categories[selectedParentCategoryIndex].id}).map(category=>
                                                <Accordion key={category.id "sub"}>
                                                    <Card>
                                                        <Card.Header>
                                                            {/* This was Accordion.Toggle in old version */}
                                                            <Accordion.Toggle as={Button} variant="link" eventKey={category.id.toString()}>
                                                                {category.Name} 
                                                            </Accordion.Toggle>
                                                        </Card.Header>
                                                        <Accordion.Collapse eventKey={category.id.toString()}>
                                                            <Card.Body>
                                                                {
                                                                loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == category.id.toString()).map(benefit=>                                           
                                                                    <Accordion key={benefit.Id  "subbenefit"}>
                                                                        <Card >
                                                                            <Card.Header>
                                                                                {/* This was Accordion.Toggle in old version */}
                                                                                <Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
                                                                                    {benefit.Name}
                                                                                </Accordion.Toggle>
                                                                            </Card.Header>
                                                                            <Accordion.Collapse eventKey={benefit.Id.toString()}>
                                                                                <Card.Body>
                                                                                    {benefit.Logo && 
                                                                                        <>
                                                                                            <img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
                                                                                        </>
                                                                                    }
                                                                                    <section 
                                                                                        className="not-found-controller"
                                                                                        dangerouslySetInnerHTML={{ __html: benefit.Detail }}
                                                                                    />
                                                                                </Card.Body>
                                                                            </Accordion.Collapse>
                                                                        </Card>
                                                                    </Accordion>
                                                                )
                                                                }
                                                            </Card.Body>
                                                        </Accordion.Collapse>
                                                    </Card>
                                                </Accordion>
                                            )
                                        }
                                        {
                                            loadedMemberBenefits?.filter(benefit=>benefit.CategoryId == categories[selectedParentCategoryIndex].id.toString()).map(benefit=>                                            
                                                <Accordion key={benefit.Id  "sub"}>
                                                    <Card >
                                                        <Card.Header>
                                                            {/* This was Accordion.Toggle in old version */}
                                                            <Accordion.Toggle as={Button} variant="link" eventKey={benefit.Id.toString()}>
                                                                {benefit.Name}
                                                            </Accordion.Toggle>
                                                        </Card.Header>
                                                        <Accordion.Collapse eventKey={benefit.Id.toString()}>
                                                            <Card.Body>
                                                                
                                                                {benefit.Logo && 
                                                                    <>
                                                                        <img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
                                                                    </>
                                                                }
                                                                <section
                                                                    className="not-found-controller"
                                                                    dangerouslySetInnerHTML={{ __html: benefit.Detail }}
                                                                />
                                                            </Card.Body>
                                                        </Accordion.Collapse>
                                                    </Card>
                                                </Accordion>
                                            )
                                        }
                                    </div>
                                </>
                            }

                        </div>
                    </Col>
                </Row>
            </Container>
        </div>
    )
}

export default Index

Error Screen

Error Text:

RangeError: Maximum call stack size exceeded
(anonymous function)
C:/Source/Repos/Federation.Homestead/MyFfbfPortal/my-ffbf-portal/src/pages/benefits/Index.tsx:233
  230 | 
  231 | {benefit.Logo && 
  232 |     <>
> 233 |         <img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   btoa(String.fromCharCode.apply(null,  benefit.Logo.data)) }></img>
      | ^  234 |    </>
  235 | }
  236 | <section


Index
C:/Source/Repos/Federation.Homestead/MyFfbfPortal/my-ffbf-portal/src/pages/benefits/Index.tsx:175
  172 | 
  173 | {
  174 |     categories.filter(category=>{return  category.ParentId == categories[selectedParentCategoryIndex].id}).map(category=>
> 175 |         <Accordion key={category.id "sub"}>
      | ^  176 |            <Card>
  177 |                 <Card.Header>
  178 |                     {/* This was Accordion.Toggle in old version */}


▶ 18 stack frames were collapsed.
LoadParent
C:/Source/Repos/Federation.Homestead/MyFfbfPortal/my-ffbf-portal/src/pages/benefits/Index.tsx:48
  45 | const LoadParent = async (parentCategoryId:string) =>{
  46 |  var [_getMemberBenefits, _error] = await MyFfbfClientApi.Benefits.GetMemberBenefits(parentCategoryId ?? 0);
  47 |  set_loadedMemberBenefits(_getMemberBenefits)
> 48 |  set_selectedParentCategoryIndex(categories.findIndex(category=> category.id == parentCategoryId))
     | ^  49 | }
  50 | 
  51 | // maybe fold these into 1 redirection checker function?

CodePudding user response:

I think the problem is this: String.fromCharCode.apply it gets invoked for each byte/number in benefit.Logo.data inline and at a certain size the call stack exceeds.

The apply() method calls the specified function with a given this value.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

But beware: by using apply this way, you run the risk of exceeding the JavaScript engine's argument length limit. The consequences of applying a function with too many arguments (that is, more than tens of thousands of arguments) varies across engines. (The JavaScriptCore engine has hard-coded argument limit of 65536.)

So internally it looks like this:

// `null` will be replaced with the global object
btoa(window.String.fromCharCode(benefit.Logo.data[0])   window.String.fromCharCode(benefit.Logo.data[1])   ...)

Maybe you can store the image as raw binary, like in Image() or ArrayBuffer() or similiar and call toString("base64") on that?

Pseudo code:

export interface ImageFile{
    data: ArrayBuffer;
    contentType: string;
    fileName: string;
}

<img style={{width:"100%"}} src={"data: " benefit.Logo.contentType "; base64,"   benefit.Logo.data.toString("base64") }>

References:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

https://developer.mozilla.org/en-US/docs/Web/API/Blob

https://developer.mozilla.org/en-US/docs/Glossary/Base64

  • Related