I have a parent component that has an array in state. It maps over the array and passes items to child components.
import React, { useState, useEffect } from "react";
import { Channel } from "../../../constants";
import { CommandLineArguments } from "../../../main/ipcHandlers";
import { Conversion, Converter } from "../Converter/Converter";
export function App() {
const [commandLineArguments, setCommandLineArguments] = useState<null | CommandLineArguments>(null);
const [conversions, setConversions] = useState<Conversion[]>([]);
function setConversion(filepath: string, partial: Partial<Conversion>) {
const updated = conversions
.filter((conversion) => conversion.filepath === filepath)
.map((conversion) => ({ ...conversion, ...partial }));
const rest = conversions.filter((conversion) => conversion.filepath !== filepath);
setConversions([...rest, ...updated]);
}
useEffect(function getCommandLineArgumentsEffect() {
async function asyncReadSvgFile() {
const args = await window.bridgeToMainProcessApi.invoke(Channel.GetCommandLineArguments);
const s = (args.input || []).map((path) => {
return { filepath: path };
});
setConversions(s);
}
asyncReadSvgFile();
}, []);
return (
<div>
{conversions.map((c) => (
<Converter
proxy=""
setConversion={setConversion}
key={c.filepath}
filepath={c.filepath}
svg={c.svg}
processedSvg={c.processedSvg}
tgml={c.tgml}
/>
))}
</div>
);
}
The children invoke the callback to update the conversions.
import React, { useEffect } from "react";
import compose from "lodash/fp/compose";
import { XmlView, XmlType, ViewType } from "../XmlView";
import { Channel, defaultProxy } from "../../../constants";
import { prepareSvg, convertSvg } from "../../../process";
import { filenameWithoutExtension, filenameFromPath } from "../App/files";
export type Conversion = {
filepath: string;
svg?: string;
processedSvg?: string;
tgml?: string;
};
type Props = Conversion & {
proxy: string;
setConversion(filepath: string, conversion: Partial<Conversion>): void;
};
export function Converter(props: Props) {
const { filepath, svg, processedSvg, tgml, proxy, setConversion } = props;
useEffect(
function readSvgFileEffect() {
console.log("read1");
async function asyncReadSvgFile() {
console.log("read2");
const files = await window.bridgeToMainProcessApi.invoke(Channel.ReadFiles, [filepath]);
const svg = files[0].content;
setConversion(filepath, { svg });
}
asyncReadSvgFile();
},
[filepath]
);
useEffect(
function prepareSvgEffect() {
async function asyncprepareSvg() {
if (!svg) {
return;
}
const processedSvg = await prepareSvg(svg, defaultProxy ? defaultProxy : proxy);
setConversion(filepath, { processedSvg });
}
asyncprepareSvg();
},
[svg]
);
useEffect(
function convertSvgEffect() {
async function asyncConvertSvg() {
if (!processedSvg) {
return;
}
const tgml = await convertSvg(processedSvg, compose(filenameWithoutExtension, filenameFromPath)(filepath));
setConversion(filepath, { tgml });
}
asyncConvertSvg();
},
[processedSvg]
);
return (
<div>
{svg && <XmlView serialized={svg} xmlType={XmlType.Svg} viewType={ViewType.Image} />}
{processedSvg && <XmlView serialized={processedSvg} xmlType={XmlType.ProcessedSvg} viewType={ViewType.Image} />}
{tgml && <XmlView serialized={tgml} xmlType={XmlType.Tgml} viewType={ViewType.Image} />}
{svg && <XmlView serialized={svg} xmlType={XmlType.Svg} viewType={ViewType.Code} />}
{processedSvg && <XmlView serialized={processedSvg} xmlType={XmlType.ProcessedSvg} viewType={ViewType.Code} />}
{tgml && <XmlView serialized={tgml} xmlType={XmlType.Tgml} viewType={ViewType.Code} />}
</div>
);
}
I don't understand why this causes an infinite rendering loop. I understand that calling setConversions
causes the parent to re-render and pass new props to the children. I guess that might cause all the children to be recreated from scratch. Feel free to provide a better explanation of what is happening.
Regardless, my main question is: how do I get around the infinite re-rendering?