mui / mui-x Goto Github PK
View Code? Open in Web Editor NEWMUI X: Build data-rich applications using a growing list of advanced React components.
Home Page: https://mui.com/x/
MUI X: Build data-rich applications using a growing list of advanced React components.
Home Page: https://mui.com/x/
In its current form, the released packages bundle code instead of relying on a nested dependency tree.
For instance:
@material-ui/x
package bundles @material-ui/x-license & @material-ui/x-grid & @material-ui/x-tree-view
@material-ui/x-grid
package bundles @material-ui/x-grid & @material-ui/x-license
@material-ui/x-tree-view
package bundles @material-ui/x-tree-view & @material-ui/x-license
I can't help myself asking, wouldn't it be better to leverage transitive dependencies? In this mode, we would get this tree structure:
@material-ui/x
├── @material-ui/x-grid
│ └── @material-ui/x-license
└── @material-ui/x-tree-view
└── @material-ui/x-license
Which can avoid duplicates in developer's bundle when they start to do fancy stuff, like importing from @material-ui/x and @material-ui/x-tree-view at the same time.
A list of checks we will need to run in the CI in order to save us time during development and help guarantee a high code quality:
A lot should be copied from https://github.com/mui/material-ui, the CI has been gone through numerous iterations there already. It will be much better to compound on it. Then, once we double the size of the team, I think that it could starts to make sense to diverge, exploring different directions.
This issue was originally opened in https://github.com/dtassone/fin-ui-demo/issues/3, 10/04/2020, work was transferred to this repository, for now.
Notes I have taken while looking at the source. Some are more relevant than others. I have been as exhaustive as I could.
import * as React from 'react';
It's the approach we use in the main repository. It avoids back and forth with the top of the file when refactoring.
module.exports = {
printWidth: 100,
singleQuote: true,
trailingComma: 'all',
overrides: [
{
files: '*.d.ts',
options: {
// This is needed for TypeScript 3.2 support
trailingComma: 'es5',
},
},
],
};
I was reading the code and noticed that most of the lines were going off the screen. 180 chars per line is a lot 😬.
px
is the default unit:-<DataContainer ref={gridRef} style={{ minHeight: renderCtx?.totalHeight + 'px', minWidth: internalColumns.meta.totalWidth + 'px' }}>
+<DataContainer ref={gridRef} style={{ minHeight: renderCtx?.totalHeight, minWidth: internalColumns.meta.totalWidth }}>
components
in v5 to enable developers to inject custom versions. I think that it will be interesting to look at the API of https://github.com/DevExpress/devextreme-reactive, it's a prior-work to solve the theming problem.I like the idea behind https://basecamp.com/shapeup/.
onScroll
listener. Would it be worth looking at pruning the logic for the x or y-axis if it wasn't scrolled?import { debounce } from '@material-ui/core/utils';
It's a 10 LOCs method. Speaking of debounce, we should clear it when you unmount the component, otherwise, we risk crashing the app.Great start 👍
It appears we already have a tsconfig.json file in the main repository that differs from the one here, let's make sure we share the same rules.
I opened up a new react project to test x-grid and it works for the most part, but resizing doesn't seem to work at the moment.
I've set up my project like this:
import React from 'react';
import { XGrid, RowsProp, ColDef, LicenseInfo } from '@material-ui/x-grid';
LicenseInfo.setLicenseKey(test);
const rows: RowsProp = [
{ id: 1, col1: 'Hello', col2: 'World' },
{ id: 2, col1: 'XGrid', col2: 'Awesome' },
{ id: 3, col1: 'Material-UI', col2: 'Amazing' },
];
const columns: ColDef[] = [
{ field: 'id', hide: true},
{ field: 'col1', headerName: 'Column 1', resizable: true },
{ field: 'col2', headerName: 'Column 2', resizable: true },
];
function App() {
return (
<div className='App' style={{ height: 400, width: '100%', display: 'flex', flexGrow: 1 }}>
<XGrid columns={columns} rows={rows} />
</div>
);
}
export default App;
The drag button does seem to work but nothing happens. I also get this error inside the console when i try to drag the columns:
index-esm.js:15 Uncaught TypeError: Cannot read property 'field' of undefined
at fr (index-esm.js:15)
at index-esm.js:264
at HTMLDivElement.<anonymous> (index-esm.js:264)
Rending the grid on the server fails on:
ReferenceError: localStorage is not defined
This makes me also wonder. Do we want to bundle the useLogger
package in production? Is there a way we could make is a noop when process.env.NODE_ENV === 'production'
?
In order to build on top of the same foundation and to save time, I think that we should reuse the existing build steps of the main repository by leveraging Babel. It appears that reproducing the same building pipeline will provide the maximum consistency. For instance
I pulled the latest changes and after running the project locally it fails to compile. Running the build also doesn't work.
The issue I'm getting is related to missing next
folder inside the docs
folder.
The commit from which this problem started is this one -> #144
PS: If this is not the right way to create issues please point it out and I'll update it.
I think that we should make sure that all the exported types and modules are unique within the Material-UI ecosystem, this includes all the packages in @material-ui/*
. We had a first discussion around the issue on https://github.com/mui-org/material-ui-pickers/issues/1778.
This is important for two reasons:
@material-ui/x
.We can benchmark with competitors:
Grid
I don't think that Kendo is the leader by chance, they have put a lot more care into the design of their components. I think that Grid
would make a great prefix. In https://github.com/mui-org/material-ui, we already do our best to avoid conflicts
Noticed it with oliviertassinari/material-ui@9a3c143.
Looking at the current shape of the API, it seems that we have an opportunity to explore a more intuitive and flexible API around how the different states can be handled. We have started to uncover this aspect in #133, where we talk about renaming sortModel
to sortBy
. We have also uncovered some of the problems in #165 (comment) with the update columns method.
Changes I would propose:
setSortModel
should warn when used without the uncontrollabe API: defaultSortModel
.defaultSortModel
prop to allow the uncontrollable API to function with the imperative API setSortModel
.defaultSortModel
prop could be supported, and equivalent to calling setSortModel
.This gives us:
sortModel
: the controllable prop, will be used no matter what as a source of truth.defaultSortModel
: the uncontrollable prop, the state is hosted by the componentsetSortModel
: imperative API to update the uncontrollable mode, update the state hosted by the component. Throw or warn if the state is already controlled.onSortModelChange
: callback method used for the controllable mode.getSortModel
: imperative API to pull the state hosted by the component.Another corollary is that we should remove duplicated APIs, for instance: https://github.com/mui-org/material-ui-x/blob/91b05ee8b6cc9fb1beab96187fd4308d4b7b294b/packages/grid/x-grid-modules/src/models/colDef/colDef.ts#L56-L63 should be removed for simplicity and clarity.
In the documentation, I think that we should:
While in this demonstration, I take the "sortModel" state as an example, I think that we can apply the very same model to all the other states. In the core package, there is a useControlled() hook to normalize the behavior. Example of other states we can expose:
This should solve my concerns from #165 (comment). Exposing a resetState
publicly doesn't feel right, not at all. From what I understand the boolean influences: caching and internal states. Caching should stay private and internal states should be exposed independently (columnsWidth
).
This change aims to make the objective of the prop easier to grab and memorize. It's pretty similar to what most ORMs have, matching the ORDER BY
command of SQL. First discussed in #130 (comment).
The purpose of this issue is to identify misalignment between the main repository and this one, see what improvement we can bring in any direction.
While it's unclear how this package will evolve in the future, staying standalone or merging with the main repository as we plan for the pickers. So far the great reason not to merge is to keep the effort private and allow fast POC iterations. But what about once we make it public? I think that our default answer to the problem would be to go all-in in the mono-repository approach unless there is a great reason not to, something that would outweigh the cost (like we have right now).
The core idea around why this approach can help bring value:
Left is mui-org/material-ui-x, right is mui-org/material-ui. I will make the case for why the right approach might be better at the same time. I would love to hear counter-arguments in the other direction.
function Foo({ bar, baz })
vs. function Foo(props)
: Using props
as the first variable and destructuring in the body of the function. This allows making the context explicit when reading the signature of the function. We are inside a React component:
classnames
utils vs. clsx()
dependency. The last discussion we had on the topic can be found in mui/material-ui#14152. clsx comes with a babel plugin that rewrites the source to make the function run faster and take less bundle size: https://github.com/merceyz/babel-plugin-optimize-clsx
Explicit display name vs. implicit one. The React dev tool should be able to infer the name from anonymous arrow functions and named functions with the .name
property. It shouldn't be needed. I believe it's bundle size we can save from the bundle and one less thing to worry about.
src/renderer/link-renderer.tsx vs. src/renderer/LinkRenderer.tsx: Naming the filename of the component with the same case of the name of the component make is easier to identify.
#33 npm vs. yarn. It originates from an issue with Lerna. I think that we can have a closer look at the problem once we actually use Lerna to publish code on the main repository, so far, we only use Lerna to execute commands in parallel. If we can get rid of the yarn link requirement, it will be awesome.
#39 120 vs. 100 max code lenght. Prettier recommends against using more than 80. I think that we already did a stretch by going to 100.
#104 import React, { useEffect } from 'react';
vs. import * as React from 'react';
. The React bundle isn't tree-shakable, the latter offers a better DX, no need to update the imports when adding or removing the usage of an API.
#105 className={'country-flag'}
vs. className="country-flag"
. I believe the convention in the React ecosystem is to us the shorthand version (double quote) anytime it's possible. The React documentation seems to follow this approach, which I would guess is more frequently used in the community.
useCallback((e: any) =>
vs. useCallback((event: any) => {
I think that we should use explicit arguments exclusively. I get that it can be faster to write, but it hard readability, which should have more value overall.
import syntax. There are a couple of different approaches. Should we accept blank lines in between the imports? Should we sort the imports in a specific order?
...rest vs. ...other. A pure convention, we always name the other props, other
.
I will add more items as I identify "gaps" and potential improvements in either direction.
The line I'm going after:
While there are a couple of leverages we can use to make a hard dependency on @material-ui/icons more viable, I don't think that it's a wise direction to take. I would propose we apply the same tradeoff than with all the other packages. We inline the few SVG icons we need. It's a small bundle size duplication overhead. To the best of my knowledge, we wouldn't have a lot of them. Here are examples:
We are moving away from enzyme to testing-library in the main repository. 💯 for starting writing tests with the best tools available: testing-library 👌. Meaning replacing:
https://muix-storybook.netlify.app/?path=/story/real-data-demo--commodity
I see two issues in the above case:
I think that we can solve the above problems with:
We have started to work on the feature a year ago, the person that takes on this task can refer to mui/material-ui#18872 and this code: saveAs.
UIs inspiration I could find:
The "More options" button opens a modal where you can select the format of the CSV file.
Grouping columns allows you to have multiple levels of columns in your header and the ability, if needed, to 'open and close' column groups to show and hide additional columns.
This is a follow-up on a conversation we have started in #44 (comment). The tslib
dependency was added in order to make codesandbox runs successfully. It's unclear if the requirement is coming from codesandbox itself or from a misconfiguration of the building pipeline.
It's also to be noted that we use babel-runtime on @material-ui/core
which might duplicate in the bundle for our users.
@dmtrKovalenko Considering we have a similar building stack on the pickers, do you have any idea of what could be wrong? :)
I have added the "Post MVP" milestone as we can/should delay the resolution to later.
3 improvements opportunities:
The list of public components I have noticed missing the forward ref logic
Grid
. A reproduction: https://codesandbox.io/s/trusting-vaughan-voq8o. The source:I'm taking notes on #134 (comment) in case the topic comes up again, in the future.
Copy and paste data between an Excel sheet and a Data Grid.
The reflection started at #43 (comment), opening an issue so we can keep track of it.
While lodash was inlined in the repository (I guess to hide, on the surface, the dependency on this library), I don't think that it removes the fact that we depend on it. I would be in favor of completely removing its modules from the codebase.
Possible migration plan:
import { denounce } from @material-ui/core
module. We could also move the module to @material-ui/utils
instead of the core if that makes more sense. This module also has the advantage of being deduplicated with the core components, sharing the simple source.For consistency with the core packages and compatibility with the ecosystem, I think that we can widen the scope of the support versions of React from ^16.13.1
to ^16.8.0
.
I'm not aware of any downsides to doing such, only upside with consistency against the other Material-UI's packages. The day we get a bug from somebody that uses an older version and we can fix by increasing the minimum version of React supported, sure.
priority: small, cost: small
We would need to:
The idea to host the X line product in the main repository is growing in me. While we are going in this direction for the pickers: mui/material-ui#19706, I would like to continue the reflection for X. It's not obvious.
Looking at larger projects, yes, it seems so
Note that if bug tracking is a concern, we can very well manage the concern independently to where the source is hosted: fullcalendar/fullcalendar-workspace#558.
cc @mui-org/core
@dtassone The issue was caught in the CI (Karma), something is calling setState after the component is unmounted. It leaks.
@eps1lon this makes me think that we might have an improvement opportunity in the CI, shouldn't we fail if a component call console.warn or console.error during the tests? I believe it works when we run the tests in JSDOM, but not with Karma. I don't know why yet. The data grid might be an exception as all JSDOM tests are skipped (require layout).
The current bundle size and bundling strategy is not flying. It's not living up to the quality level our audience expects from the core components. https://bundlephobia.com/result?p=@material-ui/data-grid
First, tree-shaking is not working. (tested with CRA). This means that, for instance, developers bundle all the locales. I think that the solution is to drop the current bundling strategy and copy what the core is doing. I think that this bundling problem is hard enough, we don't have the resources to solve the problems twice because we use two different solutions between different packages.
in
import * as React from 'react';
import { GridToolbar } from '@material-ui/data-grid';
export default function DataGridDemo() {
console.log('GridToolbar', GridToolbar)
return null;
}
out, in the production bundle
Second, here is a couple of bundle size-reduction opportunities:
packages/license/src/encoding/base64.ts
in the bundle of the users. The possible solution would work as follow:
window.atob
and window.btoa
, only run the license check if the window is defined (ignore server-side, and CI)Buffer.from('Hello World!').toString('base64');
and Buffer.from(b64Encoded, 'base64').toString()
utils.import { denounce } from @material-ui/core
module. We could also move the module to @material-ui/utils
instead of the core if that makes more sense. This module also has the advantage of being deduplicated with the core components, sharing the simple source.OutlinedInput
and FilledInput
from the bundle..A follow-up on #115 (comment).
I believe that extending the resizing session up to the mouse up event would improve the UX. I don't think that we should interrupt the resizing session when the mouse leaves the column header. This surface area is relatively small. It would be better for end-users not to have to concentrate on keeping the same vertical alignment with their pointing device. It also feels weird to have the mouse pressed but not doing anything (once escaping the zone).
I'm going to assume that we want to build a first theme for the preview of the DataGrid. In this context, we want to follow the Material Design specification: https://material.io/design/components/data-tables.html. I have seen the following divergences with the specification: the typography:
Check the implementation of TableCell for a version that is supposed to (unless proven otherwise 🙃) matches the specification.
The solution would be about:
theme.typography.body2
theme.palette.text.primary
Actually, I wonder. Would the performance be good enough if we used the component? (I suspect it won't be but it would be interesting to figure that out. Alternatively, would it make sense to only import the style of the component?
@dtassone What about we:
packages/license
folder -> packages/x-license-pro
to match package namepackages/grid
folder to make it easier to navigate the codebase?While the default strategy should be to not use React.memo()
unless there is a good reason to use it cc @eps1lon, looking at the codebase, there is a quick win on the problem. In 99% of the cases, anytime a React element is accepted as a prop, React.memo()
is a waste of CPU cycles (unless this babel plugin kicks in, which is not frequent).
Components we can make faster be removing memo:
This is a follow-up on a couple of past discussions we had, mostly speaking. I couldn't find any written conclusion on how to best name the package and components outside of a verbal discussion that no other member can introspect. In order to improve decision making and transparency, I'm making a written RFC, so we can settle.
We came to the conclusion at https://github.com/mui-org/material-ui-pickers/issues/1778 that:
@material-ui/core
vs @material-ui/x
.@material-ui/pickers
.@materal-ui/utils
.Looking at the current naming organization, I see a couple of things we could potentially improve:
Grid
component name is already reserved by https://material-ui.com/api/grid/. We can't use it here./grid
folder. A flat structure would be easier to navigate./grid
was introduced to fix a naming organization issue: if we sort ASC, related components won't be grouped. This might be an issue we would want to solve userland too (not only in the repository).A couple of options that we could consider to solve the above:
/data-grid
/x-grid-data-generator
/x-grid-modules
/x-grid
/pickers
/x-pickers
/x-tree-view
to
/data-grid
/grid-x-generator
/grid-x-modules
/grid-x
/pickers
/pickers-x
/tree-view-x
I've not convinced it's better, but it could have its advantages, to consider.
/grid
folder. I think that it would make it easier to navigate the codebase.import { DatePicker } from '@material-ui/pickers';
import { DateRangePicker } from '@material-ui/x-pickers';
// import { DataGrid } from '@material-ui/data-grid';
import { DataGrid } from '@material-ui/x-data-grid';
import { DatePicker } from '@material-ui/pickers';
import { DateRangePicker } from '@material-ui/x-pickers';
import { DataGrid } from '@material-ui/data-grid';
import { XGrid } from '@material-ui/x-grid';
This feature is also related to the editable row #204.
I have noticed the following import when reading the documentation that made me wonder
Shouldn't we assume that all public API lives in a global namespace? https://github.com/mui-org/material-ui-pickers/issues/1778. Let's assume we have two components that have an Columns
exported variable.
@material-ui/x
. I expect that most developers will use it for simplicity. ConflictI would recommend:
-import { Columns, RowsProp, XGrid } from '@material-ui/x-grid';
+import { GridColumns, GridRowsProp, XGrid } from '@material-ui/x';
Baseline rule: https://material-ui.com/guides/api/#property-naming
Issues:
Here are some of my foundings on the current material-ui-x
codebase.
Do not take it personally, just some of my observations
clsx
module to combine class namesSuch expressions are so unreadable (https://github.com/mui-org/material-ui-x/blob/master/packages/grid/src/components/cell.tsx#L6):
let cssClasses = `${CELL_CSS_CLASS} ${cssClass || ""} ${
showRightBorder ? "with-border" : ""
}`;
cssClasses += !align || align === "left" ? "" : ` ${align}`;
className={'country-flag'}
are a little bit weird.export const IsDone: React.FC<{ value: boolean }> = React.memo(({ value }) =>
value ? <DoneIcon fontSize={"small"} /> : <ClearIcon fontSize={"small"} />
);
export const IsDone: React.FC<{
value: boolean;
}> = React.memo(({ value }) => {
return value ? (
<DoneIcon fontSize={"small"} />
) : (
<ClearIcon fontSize={"small"} />
);
});
React.memo
? Memoization costs runtime performance. And probably we do not need to make premature optimizations:MOST OF THE TIME YOU SHOULD NOT BOTHER OPTIMIZING UNNECESSARY RERENDERS. React is VERY fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this. In fact, the need to optimize stuff with what I'm about to show you is so rare that I've literally never needed to do it in the 3 years I worked on PayPal products and the even longer time that I've been working with React.
cc https://kentcdodds.com/blog/usememo-and-usecallback
Avoid using inline styles style={{ display: 'flex', justifyContent: 'space-between' }}
(https://github.com/mui-org/material-ui-x/blob/master/packages/grid-data-generator/src/renderer/incoterm-renderer.tsx, https://github.com/mui-org/material-ui-x/blob/master/packages/grid-data-generator/src/renderer/progress-bar.tsx and other)
Grid cell field
prop must be well-typed string union which will autocomplete the other available props (https://github.com/mui-org/material-ui-x/blob/master/packages/grid/src/components/cell.tsx#L6)
Make sure that we are using strict
mode in tsconfig.json
. I am seeing some functions without argument types, that would be inferred as any
Expressions like icons: { [key: string]: React.ReactElement };
are so unsafe. key
must be well-typed for autocomplete and type system experience
(https://github.com/mui-org/material-ui-x/blob/master/packages/grid/src/components/column-headers.tsx#L13)
Probably we need some convention about the body of the functional component. IMHO it is much more readable when all the state/ref hooks are going first, then declaring effects, then callbacks and than return
. It is similar to what we have with class components (1. state in the constructor, 2. methods, and the last render
method) E.g. this component is really hard to read.
Avoid using 1-letter variable names, even for maps {columns.map((c, idx) => (
(https://github.com/mui-org/material-ui-x/blob/master/packages/grid/src/components/column-headers.tsx#L94)
This .map loop is to long and too nested. Really hard to read, probably need to split in some reusable functions to improve readability.
Do we really need constants for css classes? Looks like it is too different from material-ui
styling solution (https://github.com/mui-org/material-ui-x/blob/master/packages/grid/src/constants/cssClassesConstants.ts)
Events must be properly typed MouseEvent
is native dome event. React.MouseEvent
is react synthetic event and must be used as React.MouseEvent<HTMLElement>
to prevent explicit casts later on and improve typings. (https://github.com/mui-org/material-ui-x/blob/master/packages/grid/src/hooks/root/useApi.ts#L58
Overall typescript doesn't work well right from what I am seeing. For example probably we want to use import * as React from 'react'
in this project as well to get better access to the react typings.
Probably we don't need to use explicit type definitions everywhere like here:
const getColumnFromField: (field: string) => ColDef = field => stateRef.current.lookup[field];
const getAllColumns: () => Columns = () => stateRef.current.all;
const getColumnsMeta: () => ColumnsMeta = () => stateRef.current.meta;
const getVisibleColumns: () => Columns = () => stateRef.current.visible;
We could make the return value to be inferred automatically. The same for anonym objects.
It is used !
operator really rarely which means that we have an unsound type system and we are going to crash here 😱 just like here. Probably we want to use ?
operator?
import { useEffect } from 'react';
import { useLogger } from '../utils/useLogger';
import { KEYDOWN_EVENT, KEYUP_EVENT, MULTIPLE_KEY_PRESS_CHANGED } from '../../constants/eventsConstants';
import { GridApiRef } from '../../grid';
Do we really need to use forceUpdate
's in each hook? Probably we can avoid using it?
Do we need to duplicate ColumnsContainer.displayName = 'ColumnsContainer';
for each component? It will be inferred by react automatically
Is it intended that we are using global classnames? I suppose we need to remove usage of the global class names like here. It is potentially dangerous because of accidental overrides. But I suppose it will be redone.
Probably we need to use prettier
for code formatting, to increase readability for other contributors.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.