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 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