Value of type '(params: any) => CSSProperties' has no properties in common with type 'Properties<string | number>'. Did you mean to call it? - css

Why does this property in react CSS not work if it is of type CSSProperties? How can I get it to work with Properties<string | number> ?
export const fields: GridFieldsConfiguration[] = [
{
...defaultColDefs,
field: 'amInitials',
displayNameRule: 'Asset Manager',
flex: 1.1,
minWidth: 75,
cellStyle: (params: any): any => {
getCellStyle(params, 'amInactive')
}
}
];
const isDisabledbStyle = {
color: '#FF0000'
};
const getCellStyle = ((params: any, inactiveCol: string): CSSProperties => {
console.log(params);
if (params?.api?.getValue(inactiveCol, params.node) === true) {
return isDisabledbStyle;
} else {
return isDisabledbStyle;
}
}
);
Here are the types. cellStyle comes from CSSProperties which is an extension of CSS.Properties<string | number>.
export interface GridFieldConfiguration extends FieldConfiguration {
cellStyle?: CSSProperties;
}
export interface CSSProperties extends CSS.Properties<string | number> {
/**
* The index signature was removed to enable closed typing for style
* using CSSType. You're able to use type assertion or module augmentation
* to add properties or an index signature of your own.
*
* For examples and more information, visit:
* https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
*/
}
Here is Properties
export interface Properties<TLength = string | 0> extends StandardProperties<TLength>, VendorProperties<TLength>, ObsoleteProperties<TLength>, SvgProperties<TLength> {}

Sounds like you are using AG Grid, and trying to configure a cellStyleFunc for the cellStyle option of column definitions?
As shown in the linked documentation, it is indeed possible to provide a function that takes a params argument.
But it looks like in your case, you have an intermediate GridFieldsConfiguration custom type, that expects cellStyle to be of type Properties<string | number> (which is very probably actually React.CSSProperties), which does not accepts the function form, hence the error message.
If the rest of your code that handles GridFieldsConfiguration really expects CSSProperties and not the function form, then you would have to refactor it first, so that it can handle that form.
If all it does is to pass the cellStyle option to AG Grid, then you just need to improve the definition of GridFieldsConfiguration type. You can re-use the actual types grom AG Grid, e.g.:
import { AgGridColumnProps as ColDef } from "ag-grid-react";
export interface GridFieldsConfiguration {
cellStyle?: ColDef["cellStyle"];
}
But note that CSSProperties is actually not type-compatible with cellStyle. To fix it, simply remove the return type assertion on your getCellStyle function. If you want to ensure that the returned objects still resembles a CSS object, you can use the new satisfies operator:
const isDisabledbStyle = {
color: '#FF0000'
} satisfies CSSProperties;
const getCellStyle = (params: any, inactiveCol: string) => {
return isDisabledbStyle;
};
Playground Link

Related

How to get CSS types for React / styled-components?

I am getting this error in my styled component:
Type 'string | undefined' is not assignable to type 'WordBreak | undefined'.
It is happening here:
type PropsType = {
breakEverywhere?: boolean
breakWord?: boolean
}
const Text = styled.p<PropsType>(props => ({
wordBreak: getWordBreak(props),
}))
function getWordBreak(props: PropsType): string | undefined {
if (props.breakWord) {
return 'break-word'
}
if (props.breakEverywhere) {
return 'break-all'
}
}
It can be easily fixed by leaving off the type annotation string | undefined on the getWordBreak function. But how can I add a type annotation? It says WordBreak, but google searching for WordBreak type definitions doesn't yield any results, and no VSCode help. Any ideas?
The same sort of problem happens if I abstract away textAlign to in a similar way, it talks about TextAlign type. Looks like the csstype won't help either.
If I use this in the styled component:
textAlign: props.align ? TEXT_ALIGN[props.align] : undefined,
And I have this:
type AlignType = 'center' | 'end' | 'start'
const TEXT_ALIGN: Record<AlignType, string> = {
center: 'center',
end: 'right',
start: 'left',
}
Then i get this:
Types of property 'textAlign' are incompatible.
Type 'string | undefined' is not assignable to type 'TextAlign | undefined'.
Type 'string' is not assignable to type 'TextAlign | undefined'.ts(2345)
I can fix it with an untyped function instead:
function getTextAlign(align: AlignType) {
switch (align) {
case 'center':
return 'center'
case 'end':
return 'right'
default:
return 'left'
}
}
But that is ugly, how can I do it the Record way or a cleaner way? How can I get access to these types?
Looks like the csstype won't help either.
Styled-components types are based on csstype, so you should be able to get what you need from there.
Type WordBreak is in namespace Property of csstype:
export namespace Property {
// ...
export type WordBreak = Globals | "break-all" | "break-word" | "keep-all" | "normal";
// ...
}
Using it with your code sample:
import styled from "styled-components"
import { Property } from "csstype"
const Text2 = styled.p<PropsType>(props => ({
wordBreak: getWordBreak2(props), // Okay
}))
function getWordBreak2(props: PropsType): Property.WordBreak | undefined { // Okay
if (props.breakWord) {
return 'break-word'
}
if (props.breakEverywhere) {
return 'break-all'
}
return
}
Playground Link

flow issue I can't overcome. Passing props buy not confirming to shape

I have a parent component, this a very scaled down version.
I define type State (as example shows)..... and then I pass down a portion of (their values) them into a child component. That child component (in a seperate file) again, defines its props.... but I am getting errors for every single prop in the parent. infuriating.
for each prop being passed to the "MyChildComponent", I am getting this error:
props of React element MyChildComponent. This type is incompatible with object type
type State = {
name: string,
age: number,
shoe: number,
hair: string
}
class Dude extends component<void, Props, State> {
props: Props;
state: State;
// these values get over-written by some ajax call
constructor() {
this.state = {
name: 'george',
age: 999,
hair: 'brown',
shoe: 11
}
displaySomeComponent = (): React.Element<*> => {
const { name, age, hair } = this.state;
return (
// EACH ONE OF THESE GIVES AN ERROR
//Flow: props of React element `MyChildComponent`. This type is incompatible with object type
<MyChildComponent
name={name},
age={age},
hair={hair}
/>
)
}
}
// In a separate file for MyChildComponent
// No errors in this file
type Props = {
name: string,
age: number,
hair: string
}
class MyChildComponent extends component<void, Props, void> {
props: Props;
render() {
// all renders fine
}
}
I am at my wits end. I do not understand what is going on, as I've passed things into many child components, but this is mucking up. I am thinking it might have something to do with the destructuring?
Hmm, it's hard to say. If I fix a few syntax errors, this seems to work fine in Flow 0.54: my version
To track down what is wrong, you might try adding type declarations to see where things go wrong. Like, inside the parent render method, under the destructuring, try adding name: string; and see if Flow complains about that.

How to flowtype cover this code in a function with dereferenced object fields

I'm new to flow, any trying to cover some of my functions, however often I have these snippets where I extract fields form an object based on some condition. But I'm struggling to cover them with flow.
const _join = function ( that: Array<Object>, by: string, index: number) {
that.forEach((thatOBJ: {[string]: any}, i: number)=>{
let obj: {[string]: any} = {};
for (let field: string in thatOBJ) {
if (field !== by) {
obj[`${index.toString()}_${field}`] = thatOBJ[field]; // NOT COVERED
} else {
obj[field] = thatOBJ[field]; // NOT COVERED
}
that[i] = obj;
}
});
}
The array that in this code is a data array so can really be in any format of mongodb data.
Any ideas on what to add to make the two lines which are not covered by flow covered?
Thanks.
A few notes...
This function has a "side effect" since you're mutating that rather than using a transformation and returning a new object.
Array<Object> is an Array of any, bounded by {}. There are no other guarantees.
If you care about modeling this functionality and statically typing them, you need to use unions (or |) to enumerate all the value possibilities.
It's not currently possible to model computed map keys in flow.
This is how I'd re-write your join function:
// #flow
function createIndexObject<T>(obj: { [string]: T }, by: string, index: number): { [string]: T } {
return Object.keys(obj).reduce((newObj, key) => {
if (key !== by) {
newObj[`${index}_${key}`] = newObj[key]
} else {
newObj[key] = obj[key]
}
return newObj
}, {})
}
// NO ERROR
const test1: { [string]: string | number } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)
// ERROR
const test2: { [string]: string | boolean } = createIndexObject({ foo: '', bar: 3 }, 'foo', 1)

Selecting a Row in React Griddle, and changing tr background color

I'm just wondering if someone has already been able to change the color of a row, in React Griddle, by clicking on it (just once).
I'm experimenting things with JQuery, and even with Griddle Metadata, but it may be done in a cleaner way ?
Edit : I'm using React 15, Griddle inside MantraJS/Meteor, getting the data in my react Component using a Mantra container.
I can get the data by using onClick event, but not able to switch the background color in the onClick event, or playing with Metadatas.
Thanks !
EDIT : I use another view to display the content of the table, so for now I don't need to change the background of my tables cells, but if I found a solution I'll complete this post
You can use react-griddle props rowMetadata and onRowClick to do this:
class ComponentWithGriddle extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowId: 0,
};
}
onRowClick(row) {
this.setState({ selectedRowId: row.props.data.id });
}
render() {
const rowMetadata = {
bodyCssClassName: rowData => (rowData.id === this.state.selectedRowId ? 'selected' : ''),
};
return (
<Griddle
...
rowMetadata={rowMetadata}
onRowClick={this.onRowClick.bind(this)}
/>
);
}
}
Now this adds a selected class to the selected <tr> elements, so you can use custom styles to add colors or whatever styles you want to apply to the selected row.
Note that a more convenient API for selecting rows has been called for in the Griddle Github issues.
For whatever reason, I couldn't get Waiski's answer to work for me at all. I'm assuming that something must have changed in Griddle over the past two years. It looks like the current prevailing advice on the Web is to "implement row selection as a plugin", but I couldn't find any examples of that either. After a long hard look at the code for the Position plugin’s TableEnhancer on GitHub and a bunch of trial and error I eventually managed to cobble together the following row selection plugin for Griddle in TypeScript:
import * as React from "react";
import * as Redux from "redux";
import Griddle, { connect, GriddlePlugin, components } from "griddle-react";
export type RowId = string | number;
export type RowClickHandler = (event: React.MouseEvent<Element>, rowId: RowId) => void;
export type RowIdGetter<TData> = (rowData: TData) => RowId;
export interface IRowEnhancerProps {
rowClickHandler: RowClickHandler;
rowId: RowId;
isSelected: boolean;
}
export class RowSelector<TData> {
private _rowClickHandler: RowClickHandler = null;
private _rowIdGetter: RowIdGetter<TData>;
constructor(rowClickHandler: RowClickHandler, rowIdGetter: (rowData: TData) => RowId) {
this._rowClickHandler = rowClickHandler;
this._rowIdGetter = rowIdGetter;
}
public rowIdToSelect: RowId;
public plugin: GriddlePlugin = {
components: {
RowEnhancer: (OriginalComponent: React.ComponentClass<components.RowProps>) =>
this.rowSelectionEnhancer(OriginalComponent)
}
}
private rowSelectionEnhancer(
OriginalComponent: React.ComponentClass<components.RowProps>
): React.ComponentClass<components.RowProps> {
const rowDataSelector = (state, { griddleKey }) => {
return state
.get('data')
.find(rowMap => rowMap.get('griddleKey') === griddleKey)
.toJSON();
};
return Redux.compose(
connect((state, props) => {
const rowData: TData = rowDataSelector(state, props as { griddleKey });
const rowId: RowId = this._rowIdGetter(rowData);
return {
...props,
rowClickHandler: this._rowClickHandler,
rowId: rowId,
isSelected: rowId.toString() === this.rowIdToSelect.toString()
};
})
)(class extends React.Component<IRowEnhancerProps, any>{
public render() {
return (
<OriginalComponent
{...this.props}
onClick={(event) => this.props.rowClickHandler(event, this.props.rowId)}
className={this.props.isSelected ? "selected" : ""}
/>
);
}
});
}
}
Here's a rough outline of how it's used by a component. (Note that I had to selectively extract this example from a much larger and more complicated component, so there might be some errors/inconsistencies; sorry about that. It should still give a good overall idea of the approach.)
import * as React from "react";
import Griddle, { RowDefinition, plugins, GriddlePlugin} from "griddle-react";
import * as MyGriddlePlugins from "../GriddlePlugins";
export interface IPartInfo {
serialNumber: number,
name: string,
location: string
}
export interface IPartListProps{
parts: IPartInfo[],
selectedSerialNumber: number
}
export class PartList extends React.Component<IPartListProps, void > {
private rowSelector: MyGriddlePlugins.RowSelector<IPartInfo>;
private rowIdGetter: MyGriddlePlugins.RowIdGetter<IPartInfo>;
constructor(props?: IPartListProps, context?: any) {
super(props, context);
this._rowClickHandler = this._rowClickHandler.bind(this);
this.rowSelector = new MyGriddlePlugins.RowSelector(
this._rowClickHandler,
this._rowIdGetter);
}
private _rowClickHandler: MyGriddlePlugins.RowClickHandler =
(event: React.MouseEvent<Element>, selectedSerialNumber: MyGriddlePlugins.RowId) => {
if (selectedSerialNumber !== this.props.selectedSerialNumber) {
/*
Set state, dispatch an action, do whatever. The main point is that you
now have the actual event from the click on the row and the id value from
your data in a function on your component. If you can trigger another
render pass from here and set a fresh value for this.rowSelector.rowIdToSelect
then the "selected" CSS class will be applied to whatever row this click
event just came form so you can style it however you like.
*/
}
}
private _rowIdGetter: (rowData: IPartInfo) => MyGriddlePlugins.RowId =
(rowData: IPartInfo) => rowData.serialNumber;
public render(): JSX.Element {
this.rowSelector.rowIdToSelect = this.props.selectedSerialNumber;
return (
<div>
<Griddle
data={this.props.parts}
plugins={[plugins.LocalPlugin, this.rowSelector.plugin]}
>
<RowDefinition>
<ColumnDefinition id="name" title="Part Name" />
<ColumnDefinition id="location" title="Installed Location" />
<ColumnDefinition id="serailNumber" title="Serial Number" />
</RowDefinition>
</Griddle>
</div>
);
}
}
So, what's actually going on here? The component creates an instance of the plugin class at instantiation time, passing in an event handler to capture the click on the row and an accessor function to retrieve your ID value (not an inscrutable internal ID) from a row of your data. Just before the component returns its rendering, a value is set on the component's instance of the plugin, that way, when Griddle renders the plugin has the data to figure out when it's on a selected row and then adjust the CSS accordingly. The handler function from your component is then assigned to the row's onClick handler so your component can get the data from the click and do whatever it needs to do.
This passes the "It works for me" test (on React 15.6) which in my case is a straightforward master/detail view driven by a traditional table implemented through Griddle. I have no idea how well it would work with some of Griddle's more advanced features.

Properties of exported const object map are not defined

I am trying to simulate map-like behaviour in TypeScript and also get code completion of possible values. I am limited to TypeScript 1.8.
catalog.ts
export declare type CATALOG = 'CATALOG1' | 'CATALOG2' | 'CATALOG3';
export const CATALOGS: { [catalog: string]: CATALOG } = {
CATALOG1: 'CATALOG1',
CATALOG2: 'CATALOG2',
CATALOG3: 'CATALOG3'
};
example.ts
import { CATALOGS } from './catalog';
class MyClass {
catalogs = CATALOGS;
constructor() {
CATALOGS.CATALOG1; // error
this.catalogs.CATALOG1; // error
}
}
This results in following error:
Property 'CATALOG1' does not exist on type { [catalog: string]:
"CATALOG1" | "CATALOG2" | "CATALOG3" }
Can someone elaborate?
What you're describing isn't a "map-like behaviour", with a map you'll do something like:
CATALOGS.get("CATALOG1");
And that's basically what you defined with: { [catalog: string]: CATALOG }.
You can access it like this:
let a = CATALOGS["CATALOG1"];
If you want to access it as you did, then it should be:
interface Catalogs {
CATALOG1: CATALOG;
CATALOG2: CATALOG;
CATALOG3: CATALOG;
}
export const CATALOGS: Catalogs = {
CATALOG1: 'CATALOG1',
CATALOG2: 'CATALOG2',
CATALOG3: 'CATALOG3'
};
let a = CATALOGS.CATALOG1;
(code in playground)

Resources