How do you optimize high total blocking time vue - javascript

I have encountered problem that I am not capable of solving. I have high total blocking time on my page (2+ sec). I have tried loading every vue component asynchronously, but it does not matter, still 2+ sec tbt. I don't really understand what can cause such high tbt and what can I do about it, as it is just a simple page without much underlying logic (https://i.stack.imgur.com/o7LSk.png) (Just 21 simple cards).
I have removed everything I can, compressed code, and left only the most nessesary stuff. Still it does not solve the issue. Is there any way to make it go down to 100-200ms? What can cause such a problem in your experience?
I have high amount of components though (cards, buttons, lazy-load picture, rating), so in the end there will be around 100-300 components on page. But I don't see there any possibilities of removing it, as this will break neat structure

The only way I found to fix high total blocking time in this case, is to use intersection observer and load it only when it enters the screen. I think same job can do some virtual scroller plugin.
<template>
<div class="grid">
<div class="grid__item" v-for="item in items" :data-id="item.id">
<Item v-if="itemsInScreen[item.id]" :item="item" />
</div>
</div>
</template>
<script>
export default {
name: 'Grid',
props: {
items: {required: true}
},
data() {
return {
itemsInScreen: {}
}
},
methods: {
initObserver() {
const callback = entries => {
entries.forEach(entry => {
if(entry.isIntersecting) this.$set(this.itemsInScreen, entry.target.dataset.id, true);
});
};
const options = {
rootMargin: "20px 20px 20px 20px"
};
const observer = new IntersectionObserver(callback, options);
const itemEls = this.$el.querySelectorAll('.grid__item');
itemEls.forEach(itemEl => observer.observe(itemEl));
}
},
mounted() {
this.initObserver();
}
}
</script>

Related

How to make component appear/disappear animation in React?

There is a block of content:
<div className="col-7">
{content}
</div>
A component is placed in "content" depending on the state:
switch (activeItem) {
case "home": {
content = <AppHome/>
break;
}
case "about-me": {
content = <AppAboutMe/>
break;
}
default:
content = null
}
How to add content change animation so that one disappears smoothly, the other appears?
I tried to add animation through CSS class. It worked, but the disappearance was interrupted by the appearance. I tried through CSS transition but it appeared only once and did not disappear. When the content was subsequently changed, the animations no longer worked.
Here's a little CodeSandbox project I made to refer as an example:
To handle animation among several components, I recommend having all of your available components in a single constant. For example:
const components = {
title: {
key: '1',
content: <div>New York</div>,
},
body: {
key: '2',
content: (
<div>
<ul>
<li>cheese</li>
<li>tomato</li>
<li>onions</li>
</ul>
</div>
),
},
};
And then you can initialise your state with the wanted ones.
const [fields, setFields] = useState([]); // [] || [components.title]
Then, depending on your logic you can either add components on top of existing ones or remove by setting your state.
const onEnter = () => {
setFields([...fields, components.title]);
};
const onExit = () => {
setFields([]);
};
Lastly, in order to animate state changes, you can wrap your components with <motion.div>{component}<motion.div/>, setting variants will take care of the details of your animation process.

How do you test the functionality of less CSS with Jest?

I've recently joined a new organisation and they use a lot of CSS to hide/show elements.
First of all, is this good practice? I have always (in most cases) shown and hidden components using a boolean to add or remove it from the dom (this is also easy to test)
Whilst trying to add tests using #testing-library/react I've found that the classes are visible by using the identity-obj-proxy module.
However, when trying to test the functionality of an element being visible or not, it becomes difficult because I don't think the less code is being compiled.
Is it possible to compile less code so it will be reflected in the tests?
Could it be something to do with the classnames module being used?
failing test
it('should open and close when clicked', async () => {
render(
<Collapse
label="Collapse"
testId="collapse-test"
isHidden
>
<div>
<h1>just some demo text</h1>
</div>
</Collapse>
)
const content = screen.getByTestId('collapse-test-content')
expect(content).not.toBeVisible()
userEvent.click(screen.getByTestId('collapse-test-button'))
await waitFor(() => expect(content).toBeVisible())
})
====================result====================
expect(element).not.toBeVisible()
Received element is visible:
<div aria-expanded="false" class="accordionContent contentHidden" data-testid="collapse-test-content" />
38 | )
39 | const content = screen.getByTestId('collapse-test-content')
> 40 | expect(content).not.toBeVisible()
| ^
41 | userEvent.click(screen.getByTestId('collapse-test-button'))
42 | await waitFor(() => expect(content).toBeVisible())
43 | })
Component
import React, { useState } from 'react'
import cn from 'classnames'
import styles from './styles.less'
const AccordionContent = ({ children, hidden, testId }) => {
const displayClass = hidden ? styles.contentHidden : styles.contentBlock
const accordionContentClass = cn(styles.accordionContent, displayClass)
return (
<div
className={ accordionContentClass }
aria-expanded={ !hidden }
data-testid={ `${testId}-content` }
>
{children}
</div>
)
}
const CollapseComponent= ({
isHidden,
onClick,
label,
children,
testId
}) => {
const [hidden, toggleHidden] = useState(isHidden)
const handleOnpress = () => {
toggleHidden((curr) => !curr)
if (onClick) { onClick }
}
return (
<div
className={ styles.accordionWrapper }
data-testid={ testId }
>
<AccordionButton
onPress={ handleOnpress }
buttonLabel={ label }
testId={ testId }
/>
<AccordionContent
hidden={ !!hidden }
testId={ testId }
>
{children}
</AccordionContent>
</div>
)
}
styles.less
.accordion-content {
background-color: #preservica-gray-1;
display: flex;
}
.content-hidden {
display: none;
}
.content-block {
display: flex;
}
jest.config
const config = {
testEnvironment: 'jsdom',
coverageThreshold: {
global: {
statements: 80,
branches: 75,
functions: 75,
lines: 80
}
},
testPathIgnorePatterns: [
"./src/components/atoms/Icons",
"./src/models"
],
coveragePathIgnorePatterns: [
"./src/components/atoms/Icons",
"./src/models"
],
setupFilesAfterEnv: [
"<rootDir>/src/setupTests.ts"
],
moduleNameMapper: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|less)$": "identity-obj-proxy",
"^#root(.*)$": "<rootDir>/src$1",
"^common(.*)$": "<rootDir>/src/common$1",
"^translation(.*)$": "<rootDir>/src/translation$1",
"^view(.*)$": "<rootDir>/src/view$1",
"^actions(.*)$": "<rootDir>/src/actions$1",
"^usecases(.*)$": "<rootDir>/src/usecases$1",
"^repository(.*)$": "<rootDir>/src/repository$1",
"^models(.*)$": "<rootDir>/src/models$1",
"^router(.*)$": "<rootDir>/src/router$1",
},
transform: {
"^.+\\.(ts|tsx|js|jsx)$": "ts-jest",
},
snapshotSerializers: [
"enzyme-to-json/serializer"
]
}
You can read more on how Jest handles mocking CSS modules in the Jest docs. You could perhaps write your own module name mapper or custom transform to load and process the Less files. However, you'd have to figure out how to actually inject the CSS into the code under test (that's something that Webpack normally handles). Something like jest-transform-css might do this.
Personally, I'd just test whether the CSS class is present, like #jonrsharpe suggests. Think of it from the perspective of the test pyramid: your Jest tests should likely be focused at the unit test level, with an emphasis on speed and simplicity. Unit tests are ideally fast enough that you can run them nearly instantly, whenever you save a file; adding the complexity to parse and insert Less CSS may work against that.
It's okay if the unit tests don't test the entire stack; you have other tests, higher up in the pyramid, to do this. For example, you could have a handful of Cypress tests that run your app in the actual browser and verify that a couple of controls are actually hidden, then it should be safe to assume that (1) Jest validating all controls set the correct class plus (2) Cypress validating that a few controls with the correct class are correctly hidden means that (3) all controls are correctly hidden.
To help make your tests more self-documenting, and to make them easier to maintain if you ever change how controls are shown and hidden, you can use Jest's expect.extend to make your own matcher. Perhaps something like this (untested):
expect.extend({
toBeVisibleViaCss(received) {
const pass = !received.classList.contains('content-hidden');
const what = pass ? 'not visible' : 'visible';
return {
message: () => `expected ${received} to be ${what}`,
pass,
};
},
});
First of all, is this good practice? I have always (in most cases) shown and hidden components using a boolean to add or remove it from the dom (this is also easy to test).
Hiding components via CSS is certainly not what I'm used to. Without knowing your codebase, I'd wonder if the developers were used to previous jQuery-style approaches of hiding via manipulating the class lists. The main advantage I'm aware of keeping components always rendered is that you can animate their transitions if you want to. I'm not sure how performance compares; the browser might find it faster to toggle a CSS class than to add or remove an element, but removing the element means that React has less to render, which could help performance.

Plotly boxplot not relayouting properly when route changes

I'm using Plotly in Vue, and the plots are not properly rendering when I visit a new route. Only after hovering will the plot change to its proper size. When I change to another route with a different grid (for the same plot UUID), the plot seems to have some kind of wrong size, and it's overflowing the container. But if I hover on it, then it seems fine.
Here is my code that I use for the plotting and relayout:
mounted() {
Plotly.plot(
this.$refs[this.chartCopy.uuid],
this.chartCopy.traces,
this.chartCopy.layout
);
this.$refs[this.chartCopy.uuid].on("plotly_hover", this.hover);
this.$refs[this.chartCopy.uuid].on("plotly_unhover", this.unhover);
},
watch: {
chart: {
handler: function () {
Plotly.react(
this.$refs[this.chartCopy.uuid],
this.chartCopy.traces,
this.chartCopy.layout
);
},
deep: true,
},
},
In the options and the layout, I set autosize and responsive to true
Is there a way to make the plot already the proper size (both height and width) without the need to hover over it?
Here is the reproduced example where this behavior can be clearly seen:
https://codesandbox.io/s/vuetify-with-plotly-20nev
Any kind of relayout sort of destroys the set layout of the plot, and only after hovering, it becomes correct.
One more thing is that I can't use Vue wrapper for Plotly (it has other issues), so I have to stick with using plotly.js library only.
Responsive charts
To make the charts responsive to the window size, set the responsive property of the config option, which is the fourth argument to Plotly.plot():
// Boxplot.vue
export default {
mounted() {
Plotly.plot(
this.$refs[this.chartCopy.uuid],
this.chartCopy.traces,
this.chartCopy.layout,
{ responsive: true } 👈
)
}
}
Relayout
To perform relayout of the chart, use an IntersectionObsesrver that calls Plotly.relayout(). This should be setup in the mounted hook and torn down in beforeDestroy hook:
// Boxplot.vue
export default {
mounted() {
const chart = this.$refs[this.chartCopy.uuid]
const intersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
requestIdleCallback(() => Plotly.relayout(entry.target, {}))
}
})
})
intersectionObserver.observe(chart)
this._unobserveChart = () => intersectionObserver.unobserve(chart)
},
beforeDestroy() {
this._unobserveChart()
},
}
Is there a reason you use tabs to render the routes? The layout is actually not valid. What actually happens in User.vue is that you render views multiple times. This causes the artifacts:
// you render same contents in all tabs many times
<v-tab-item v-for="tab of tabs" :key="tab.id" :value="tab.route">
<router-view></router-view>
</v-tab-item>
Just use the <router-link> instead of tabs:
// User.vue
<template>
<div>
<router-link v-for="tab of tabs" :to="tab.route" :key="tab.id">
{{ tab.name }}
</router-link>
<div>
<router-view></router-view>
</div>
</div>
</template>
And like #tony19 mentioned, make charts responsive:
Plotly.plot(
this.$refs[this.chartCopy.uuid],
this.chartCopy.traces,
this.chartCopy.layout,
{ responsive: true }
);
That's it! Check the sandbox

Dynamic / Async Component Render

I am quite new to VueJS and have been playing around with the framework for a couple of days.
I am building a sort of dashboard with a widget based look and feel and the problem I have is that when the user adds a lot of widgets to the dashboard, problems arise on the loading of the page since the widgets make simultaneous calls to the API's to retrieve subsets of data.
To give you a better understanding of what I am doing, the concept is the below. (This is a brief idea to keep the code clean and simple).
Home.vue
<template>
<div class="Home">
<h1>Homepage</h1>
<div v-for="w in widgets">
<component :is="widget"></component>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
mounted() {
for (var i = 0; i < availableWidgets; i++) {
widgets.push(availableWidgets);
}
},
};
</script>
Widget 1
<template>
<div class="Widget1">
<span>Widget 1</span>
</div>
</template>
<script>
export default {
name: 'Widget1',
mounted() {
//Get data from API and render
},
};
</script>
Widget 2
<template>
<div class="Widget2">
<span>Widget 2</span>
</div>
</template>
<script>
export default {
name: 'Widget2',
mounted() {
//Get data from API and render
},
};
</script>
As you can see, I am sort of loading the widgets and adding them dynamically depending on what the user has in his dashboard.
The problem I have is that Widget 1 and Widget 2 (in my case there are like 20-30 widgets), will be making API calls and this works fine when 1 or 2 widgets are loaded. But once the page grows a lot and there will be like 10 widgets on the page, everything starts lagging.
What would you suggest to do to make this more performant? Is it possible to allow once component to load at a time before loading the second component and so on? I was thinking of adding async calls, but that would not stop the components from being loaded at the same time?
Looking forward to your feedback and help that you could provide.
A common pattern would be to have the first render be without data, then re-render whenever your data comes in. The browser will make sure that not too many network requests run at the same time, so you should not have lag perse from that. You just perceive lag, because your component does not render until the data loads.
I would suggest using something like Axios, which uses promises and makes it easy to create asynchronous http requests while still keeping your code readable.
<template>
<div class="widget graph">
<div v-if="loading">
<span>Loading...</span>
<img src="assets/loader.svg">
</div>
<div v-else>
<!-- Do whatever you need to do whenever data loads in -->
</div>
</div>
</template>
<script>
export default {
name: 'WidgetGraph',
data () {
return {
loading: true,
error: null,
graphData: {}
}
},
created () {
this.loadData();
},
methods: {
loadData () {
return axios.get(...).then((data) => {
this.loading = false;
}).catch(() => {
this.error = 'Something went wrong.... Panic!';
this.loading = false;
});
}
}
}
</script>
<style>
</style>

Efficiently rendering a large number of Redux Form Fields?

I'm having a table of employees and their monthly working hours day by day. Here we can update all the employees hours values in bulk.
A simple math for the current month: 30 days x 50 employees will result in 1500 mounted Redux Form Fields.
Each time a Field is being mounted, Redux Form will dispatch an action for registering the Field in the Redux store. Therefore 1500 events are dispatched.
Debugging with Chrome->Performance tool, I found out that the whole process from mounting through dispatching to rendering the Fields takes about ~4 seconds:
The above performance score is based on the following simple working example I've created with React, Redux, Redux Form, Reselect:
/* ----------------- SAMPLE DATA GENERATION - START ----------------- */
const generateEmployees = length =>
Array.from({ length }, (v, k) => ({
id: k + 1,
name: `Emp ${k + 1}`,
hours: [8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8]
}))
const employees = generateEmployees(50)
/* ----------------- SAMPLE DATA GENERATION - END ------------------- */
/* --------------------- INITIALIZATION - START --------------------- */
const { reduxForm, Field, reducer: formReducer } = ReduxForm
const { createStore, combineReducers, applyMiddleware } = Redux
const { Provider, connect } = ReactRedux
const { createSelector } = Reselect
// Creating the Reducers
const employeesReducer = (state = employees) => state
const reducers = {
form: formReducer,
employees: employeesReducer
}
// Custom logger.
// The idea here is log all the dispatched action,
// in order to illustrate the problem with many registered fields better.
const logger = ({ getState }) => {
return next => action => {
console.log("Action: ", action)
return next(action)
}
}
const reducer = combineReducers(reducers)
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(logger)
)
/* --------------------- INITIALIZATION - END ----------------------- */
const renderEmployees = employees =>
employees.map(employee => {
return (
<tr key={employee.id}>
<td>{employee.id}</td>
<td>{employee.name}</td>
{employee.hours.map((hour, day) => (
<td key={day}>
<Field component="input" name={`${employee.id}_${day}`} />
</td>
))}
</tr>
)
})
const FormComponent = ({ employees, handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<h2>Employees working hours for November (11.2018)</h2>
<p>
<button type="submit">Update all</button>
</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
{Array.from({ length: 30 }, (v, k) => (
<th key={k + 1}>{`${k + 1}.11`}</th>
))}
</tr>
</thead>
{renderEmployees(employees)}
</table>
</form>
)
}
const Form = reduxForm({
form: "workingHours",
onSubmit: submittedValues => {
console.log({ submittedValues })
}
})(FormComponent)
const getInitialValues = createSelector(
state => state.employees,
users =>
users.reduce((accumulator, employee) => {
employee.hours.forEach(
(hour, day) => (accumulator[`${employee.id}_${day}`] = hour)
)
return accumulator
}, {})
)
const mapStateToProps = state => ({
employees: state.employees,
initialValues: getInitialValues(state)
})
const App = connect(mapStateToProps)(Form)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
table {
border-collapse: collapse;
text-align: center;
}
table, th, td {
border: 1px solid black;
}
th {
height: 50px;
padding: 0 15px;
}
input {
width: 20px;
text-align: center;
}
<script src="https://unpkg.com/react#15.5.4/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15.5.4/dist/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.4/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-form/6.7.0/redux-form.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/3.0.1/reselect.js"></script>
<div id="root">
<!-- This element's contents will be replaced with your component. -->
</div>
So am I doing something inefficient and wrong with Redux Form or rendering a large number of Fields at once is bottleneck and I should take a different approach (pagination, lazy loading, etc.)?
Existing open-source form libraries seem to not work well when getting into this kind of complexity. We implemented a number of optimisations by building our own form library, but it's possible that one or more of these ideas you may be able to use with redux form.
We mount a multitude of form & other redux attached components (in the order of thousands), which all do validation when they get mounted. Our architecture required all components to be mounted at once. Each component may need to update the state multiple times when they get mounted. This is not quick, but these are the optimisations we used to make this seem quick for 1000+ controls:
Each component mounts in a way which checks if it really needs to dispatch something right now, or if it can wait until later. If the value in the state is wrong, or the current value is invalid - it will still cause a dispatch, otherwise it will defer other updates for later (like when the user focuses it).
Implemented a batch reducer which can process multiple actions in a single dispatch. This means if a component does need to perform several actions on mount, at most it will only send 1 dispatch message instead of one dispatch per action.
Debouncing react re-renders using a redux batch middleware. This means that react will be triggered to render less often when a big redux update is happening. We trigger listeners on the leading and the falling edge of redux updates, which means that react will update on the first dispatch, and every 500-1000ms after that until there are no more updates happening. This improved performance for us a lot, but depending on how your controls work, this can also make it look a bit laggy (see next item for solution)
Local control state. Each form control has a local state and responds to user interaction immediately, and will lazily update the redux state when the user tabs out of the control. This makes things seem fast and results in less redux updates. (redux updates are expensive!!)
I'm not sure any of this will help you, and I'm not even sure that the above was smart or recommended - but it worked really well for us and I hope it can give you some ideas.
Also, if you can get away with mounting less components (pagination etc), you definitely should do that instead. We were limited in our choices by some early architecture choices and were forced to press on and this is what we came up with.
Did you try https://www.npmjs.com/package/react-virtualized I used it in a project where I was capturing dozen of events. The list is growing and growing and this component helped me render all of them. I'm not sure how Redux Form works but if it is based on mounting then I guess this is a good option.

Categories

Resources