Download SVGs rendered by React - reactjs

I would like to implement an export function on my SPA, consisting on generating a bunch of SVGs (generated using React JSX) and downloading them one at once.
The number of files being huge, I can't display them at the same time on the browser. I have found React Download SVG which permits to download a SVG which is already inthe DOM.
However, the render pipeline of React does not enable me to batch the downloading because I don't control the display cycle of my JSX SVG.
How could I download all my SVGs (zipping them in a file would be an advantage) without displaying them ?
Thanks in advance,

I worked with similiar problem - generate parametrized SVG paths for CNC purpose.
Problem wasn't with download as invoked manually (after DOM update) - batch download (zipped) planned, also.
Problem was: how to display SVG source/xml in another node/component for debugging - updated on every parameter change.
However, the render pipeline of React does not enable me to batch the downloading because I don't control the display cycle of my JSX SVG.
This is true ... is nome sense, even harder while React Fiber can delay some DOM updates - but we have some possibilities to be notified.
componentDidUpdate() - but 'not called for the initial render'
ref callback - but '... defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element'
I prefer 2nd method for a few reasons, f.e. neutral to SVG (functional) component internals - however it's not guaranteed to be called in every use case - it won't be if not needed - OTOH CDM and CDU are guaranteed to have updated/proper refs when called.
Another hint found somewhere (SO?): use setTimeout to be safe/sure callback called after DOM update.
downloadableReference = el => {
console.log("DWNLD_REF ",el);
this.svgElement = el // save in wrapper, prevent old ref usage
if( !!el ) { // not null - second pass, fresh, updated
console.log("DWNLD_REF ready ", el.outerHTML )
setTimeout( ()=>{
this.notifyDownloadableDependants() // safest way - will be called after CDU
}, 0 )
}
}
This can be combined to batch the downloading. For UX I would render them sequentially - with progress visualisation, cancellable processing etc.
Another possibility: - maybe (not tested) use of react-jsx-parser be helpfull?

Related

How does chart updating works within the context of react?

I currently have a Radar chart in chart.js using the react integration.
I was suprised to note that, when I updated the data, instead of showing a completely new plot, it just transitioned smoothly from the previous dataset to the new one.
What I am interested in is to understand how it works under the hood, which honestly I can't understand, at least from looking at the code.
First: my understanding of React is that, when a prop or state changes, it computes the new DOM, and then merges the new DOM and the current DOM, applying only the difference between the two. However, chartjs seem to be implemented as a Canvas element.
The chartjs integration with react does not do much. Taking the Radar plot, this is what it does
export const Radar = /* #__PURE__ */ createTypedChart('radar', RadarController);
which is nothing but declare a <Chart> element and leave it to ChartJS to plot it. In fact, in ChartJS, we have this code, which basically manages the Canvas element and it is smart to perform transitions using animations and so on. This I understand (relatively): a lot of animation and transition helper functions, but this makes sense to me. However, this part is pure JavaScript. There's nothing that is aware of React.
What does not make sense is therefore how the react synchronization system is integrated with this JavaScript library so that the state invalidation of the props/state is synchronised to an animation, instead of a complete rewrite of the Canvas element. I don't seem to find where this magic happens in react-chartjs-2.
As you explained the canvas element does not get changed so it gets reused. To animate the chart chart.js itself has an update method. React-chartjs-2 uses a useeffect function that checks if the data you pass it has changed. If this is the case it calls the update function from chart.js itself and they handle the animations and updates itself:
useEffect(() => {
if (!chartRef.current) return;
if (redraw) {
destroyChart();
setTimeout(renderChart);
} else {
chartRef.current.update();
}
}, [redraw, options, data.labels, data.datasets]);
https://github.com/reactchartjs/react-chartjs-2/blob/4a010540ac01b1e4b299705ddd93f412df4875d1/src/chart.tsx#L78-L87
This is my understanding of the whole process after diving into the code base quite a bit. I've tried to be as detailed as possible with links to the exact line of code I am talking about. Hope this helps:
Beginning with the code snippet you shared:
export const Radar = /* #__PURE__ */ createTypedChart('radar', RadarController);
If you follow the RadarController via the import statement, you see that it is fetched from chart.js
Now we move to the Chart.js code and look for this controller RadarController. It is found in a file called src/controllers/controller.radar.js.
Within that file, you see an update function
This function then calls updateElements with the points information
This function gets the new point position which is then set in properties and passed into the updateElement function
This updateElement function directly takes us to the core.datasetController
Here you see a condition to check if the chart is in directUpdateMode. If not, it calls a function to _resolveAnimations
Within this function, you will see the new Animations(args) object
This eventually brings us to the core.animations file which consists of all the animation related information and processing.
One interesting bit I found here was: this is what seems to be making the beautiful movement of points to the changed location.
You can explore this Animations class further for more detailed understanding
So yeah essentially, it is the js part under the hood that facilitates the smooth transitions and this is how it does it. React code is essentially just like a wrapper of Chart.js calling this update method with the new values.
You can see here: https://github.com/reactchartjs/react-chartjs-2/blob/master/src/chart.tsx
The react-chartjs-2 library creates a component that adds a canvas and when the props update the component creates/updates an internal Chart object that uses the rendered canvas.
From what I saw the animation starts when the props are changed.
The path is props->react-chartjs-2 component->chart object->animation

Is it okay to use the expect inside of a waitFor as my "Assert" in Act-Arrange-Assert?

I've recently begun utilizing React / DOM Testing Library in order to test all of my front-end React code. One of the first issues I've encountered is that with the way our application is laid out, I have no way of telling when loading of data (from Mock Service Worker in my case) is complete, as everything is handled via Redux/Redux Saga, and our Loading spinner component lives outside of the components which we are testing.
Due to this, in order to wait for the data to be loaded, we actually have to waitFor the raw data itself (as we can't simply await the loading spinner or text to disappear, since it lives outside of the component):
// Expect our first row's Name column to match our filter by text
await waitFor(() => expect(getTableRowAndCell(table, 1, 1).textContent).toBe('OUTSTANDING')
In this case, our assert in our Act-Arrange-Assert pattern is inside a waitFor, though there's other cases where I'm testing several rows in the table, so only the first assertion would be wrapped in the waitFor, while the others could simply be asserted directly, like so:
// Expect our first row's Value column to match our filterByText
await waitFor(() => expect(getTableRowAndCell(table, 1, 3).textContent).toBe('VALUE')
// Expect our second row's Value column to match our filterByText
expect(getTableRowAndCell(table, 2, 3).textContent).toBe('VALUE')
This leads to the topic of the question: is this a valid way to lay out assertions in my tests?
It's a bit of a late answer but I believe that is a perfectly fine way of writing your assertions.
Me and other developers at my workplace have been following this blog: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
He is the author of React Testing Library and he says it's fine to do so. Just keep in mind you should only have one assertion per waitFor
Are you trying to execute a pure function (getTableRowAndCell) to check if results are in DOM?
Natural steps would be:
1 - Render your component (which executes somewhere getTableRowAndCell, when component mounts?)
render(<TableComponent />);
2 - Wait for data to be in the document:
expect(await screen.findByText('VALUE')).toBeInTheDocument();

How to load script in specific page in gatsby

Hello.
I recently got stuck with pretty important thing on my gatsby site.
I have to import script from other site cause it is providing map widget. This is the widget from polish delivery company and it is only available under link https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js.
It is activated by a function window.PPWidgetApp.toggleMap(). Problem is when i try to activate, html and css markup from widget are showing but map coming from js it is not.
Here is how I'm loading the script:
{
resolve: "gatsby-plugin-load-script",
options: {
src:
"https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js",
},
},
When I'm on specific route where I'm using this widget and i refresh the page everything is working properly. So I'm guessing problem is that when this script is loaded in index it gets cached somehow by gatsby and most of the important features are not working. So can I load the script only when I'm on let's say route /delivery ? Or is there another, better way to load this script that may work fine ?
Link to github repo with this problem: https://github.com/Exanderal/gatsby-problem
The easiest, native and built-in way to achieve is using <Helmet> component. Basically, this component embeds everything that is inside in your <head> tag.
The problem using it is that if you need to activate or to wait for its loading to make some actions (like window.PPWidgetApp.toggleMap() in your case), it could be kind of buggy since sometimes it may load properly but sometimes not. I will show you different approaches to check which one fits you better.
<Helmet> approach:
<Helmet>
<script src="https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js"/>
</Helmet>
As I said, this workaround may work for standalone scripts, but if you need to perform actions or wait for its loading it may not work. The next approach should fit you.
Custom script loading approach:
const addExternalScript = (url, callback) => {
const script = document.createElement('script');
script.src = url;
script.async=true;
script.onload = callback;
document.body.appendChild(script);
};
useEffect(()=>{
addExternalScript("https://mapa.ecommerce.poczta-polska.pl/widget/scripts/ppwidget.js",window.PPWidgetApp.toggleMap())
},[])
Basically, you are setting a custom function (addExternalScript) that creates the same script tag as the first approach and embeds the passed URL as a first function parameter. The second parameter is the callback function to trigger once it's loaded in the onload function.
Everything it's triggered in the useEffect function with empty deps ([]). The useEffect is a hook (available in React version ^16) that is triggered once the DOM tree is loaded, in this case, it's a nice way to ensure that the window object is properly loaded and set to avoid some common issues in Gatsby using global objects.

Should I use useEffect()?

I understand useEffect runs every time when my component is rendered. I am currently integrating a VideoConference application which creates after initialization an object called publisher. That publisher can be updated. For example, I can deactivate video etc.
I created two buttons that change the state of audio and video to either 0 or 1. I then pass these values to the component OTPublisher. The part I am confused about is if I actually should/need to use useEffect? It works both ways, also if I just insert it in the component directly without useEffect.
<OTPublisher
video={video}
audio={audio}
completionHandler={completionHandler}
>
OTPublisher.js
useEffect(() => {
if (publisher) {
audio ? publisher.publishAudio(true) : publisher.publishAudio(false);
video ? publisher.publishVideo(true) : publisher.publishVideo(false);
}
});
///
if (publisher) {
audio ? publisher.publishAudio(true) : publisher.publishAudio(false);
video ? publisher.publishVideo(true) : publisher.publishVideo(false);
}
The reason to useEffect is because it has extra features that directly having side effects in your component don't have like cleanup and only performing the effect when it's dependencies change.
The other big reason to keep the main part of the component render pure is the upcoming concurrent mode: https://reactjs.org/docs/concurrent-mode-intro.html
Concurrent mode drastically changes how components render enabling potentially multiple re-renders at once. React.StrictMode is a component that sets your components up and essentially renders them twice for every render to simulate Concurrent mode to ensure that you don't have issues.

How to control a non-React component (BokehJS) in React?

Backstory
I want to include a BokehJS plot in my React component. The process for this is to render <div id="my_plot_id" className="bk-root"/> and call window.Bokeh.embed.embed_item(plotData, 'my_plot_id') which injects needed HTML into the DOM.
Because I want to control the BokehJS plot using the React component's state (i.e replace the plot with new generated plot data), I don't want to just call embed_item() in componentDidMount(). I've instead placed embed_item() in render() and added some code to remove child nodes of the container div prior to this call.
Problem
My React component renders 3 times on page load and although by the final render I have only one plot displayed, there is a brief moment (I think between the 2nd and 3rd/final render) where I see two plots.
Code
render()
{
let plotNode = document.getElementById('my_plot_id');
console.log(plotNode && plotNode.childElementCount);
while (plotNode && plotNode.firstChild) {
//remove any children
plotNode.removeChild(plotNode.firstChild);
}
const { plotData } = this.state;
window.Bokeh.embed.embed_item(plotData, 'my_plot_id');
return(
<div id="my_plot_id" className="bk-root"/>
)
}
In console I see:
null
0
2
Question
So it seems embed_item executes twice before the my_plot_id children are correctly detected.
Why is this happening and how can I resolve it? While the triple render may not be performance optimized I believe my component should be able to re-render as often as it needs to (within reason) without visual glitching like this, so I haven't focused my thought on ways to prevent re-rendering.
Interaction with DOM elements should never happen inside the render method. You should initiate the library on the element using the lifecycle method componentDidMount, update it based on props using componentDidUpdate and destroy it using componentWillUnmount.
The official React documentation has an example using jQuery which shows you the gist of how to handle other dom libraries.
At start plotNode is unable to reach 'my_plot_id'.
You can render null at start, when plotData is unavailable.
You can use componentDidUpdate().
In this case I would try shouldComponentUpdate() - update DOM node and return false to avoid rerendering.

Resources