React-table: how to use getCellProps, getHeaderProps etc.? - reactjs

The documentation of React Table is really sparse and I can't make sense of the examples.
For example here: https://react-table.tanstack.com/docs/examples/data-driven-classes-and-styles, we see this code:
{row.cells.map(cell => {
return (
<td
// Return an array of prop objects and react-table will merge them appropriately
{...cell.getCellProps([
{
className: cell.column.className,
style: cell.column.style,
},
getColumnProps(cell.column),
getCellProps(cell),
])}
>
{cell.render('Cell')}
</td>
It's not clear to me what's happening.
GetCellProps is called, and provided an array as argument. This array contains 1. an object with two properties, and 2. a call to getColumnProps (what does this do?), and then 3. another call to getCellProps but now with the cell as an argument.
The result of this call is then operated on with the spread-operator (...).
If anyone can help me understand all of this, much appreciated.

I will try to explain this to you -
cell.getCellProps -> is the method exposed on each cell by react-table, this is pretty useful in getting all props which the given cell will require based on the plugins used.
Few eg. of same are -
https://github.com/TanStack/react-table/blob/76a4a861ee56b782404ef91987c3b5691ecf2ebc/src/hooks/useTable.js#L415
https://github.com/TanStack/react-table/blob/76a4a861ee56b782404ef91987c3b5691ecf2ebc/src/plugin-hooks/useFlexLayout.js#L47
https://github.com/TanStack/react-table/blob/76a4a861ee56b782404ef91987c3b5691ecf2ebc/src/plugin-hooks/useAbsoluteLayout.js#L23
Now this method can expect an object or list of object to be provided as argument, which mainly acts as merge/override on prior values, loosely similar to object.assign with some exceptions based on key name, for more details on merge logic refer - https://github.com/TanStack/react-table/blob/76a4a861ee56b782404ef91987c3b5691ecf2ebc/src/publicUtils.js#L19
Below code is helpful in adding className in cell and merge/override styles with previously returned values.
{
className: cell.column.className,
style: cell.column.style,
}
Below two methods are props provided by component
getColumnProps(cell.column),
getCellProps(cell)
getColumnProps={column => ({
onClick: () => alert('Column!'),
})}
getCellProps={cellInfo => ({
style: {
backgroundColor: `hsl(${120 * ((120 - cellInfo.value) / 120) * -1 +
120}, 100%, 67%)`,
},
}
So whatever is returned by these props that will actually get merged in cell props, and will be applied to td.
P.S. - Refer https://github.com/tannerlinsley/react-table/blob/master/src/publicUtils.js#L48 for what all types of argumants can be expected in cell.getCellProps.

Related

Cannot assign to read only property of Object in TypeScript

can anyone explain to me please why and how this might happen:
I have a typescript app with Zustand state management.
Somewhere during the app I am updating certain elements by extracting them from the state and cloning via simple Object.Assign :
let elemToUpdate = Object.assign({},story?.content?.elementsData[nodeId]);
console.log(elemToUpdate);
if(elemToUpdate) {
if(elemToUpdate.title) elemToUpdate.title[editorLang] = newName;
else elemToUpdate.title = {[editorLang]:newName} as TextDictionary;
updateElement(nodeId,elemToUpdate);
}
Now the interesting part is on my first try the update goes through without fail, but the next object I am trying to update fails with the following message:
Tree.tsx:39 Uncaught TypeError: Cannot assign to read only property 'en' of object '#<Object>'
I can't understand WHY the first one comes through, but the second gets blocked.
(I know HOW to fix it, need to do deep clone, I just want to understand WHY)
Thanks
First, let's start from why some objects in your code are readonly. Based on what you described in the question, you use a Zustand state manager. Such managers traditionally wraps you stored data to readonly objects to prevent it's manual mutation (expecting, you will change the state only via built-in mechanisms), to guarantee data stability. So, if the story?.content?.elementsData[nodeId] is the Zustand state object, it self and all it's nested objects are converted to readonly.
Second, let's define, which objects will be blocked. I see at least two objects here: elemToUpdate: { ..., title: { [lang]: string }} (elemToUpdate and it's title). Both will be converted to readonly.
Third, you use Object.assign({}, ...) which creates a new object (new reference) and clones all properties of the source object. It happens only for first level of properties, no deep clone. So, as the title is a reference to another object, it will be cloned as is and in the new object it still leads to the existing { [lang]: string } object. There are several way to solve that: 1) deep clone as you mentioned; 2) manually clone title property, for instance {..., title: { ... elemToUpdate.title }} or via Object.assign
But I would suggest don't mutate you object this way. Probably, your entire algorithm has some architectural issues in general.
That is expected because in the first case you are not assigning value to the title you are only changing the value of the title property. In the second case, you are reassigning the value of the title property,
it's the read-only value you cant change it. Let's understand with a simple example
Javascript: Only for example not related to problem
const user = {
name: 'John',
}
user.name = "Pete"; // This works
const user = {
name: 'John',
}
user = { name: 'Pete'} // This doesn't work
Typescript:
const user: Readonly<{
a: {
name: string
}
}> = {
a:{ name: 'John',}
}
user.a.name = "Pete"; // This works
user.a = { name: 'John',} // not work
The same is happening there, Typescript does not check deep Readonly prop. check here

Can you define a type that has fixed keys at object creation, with values of a given type?

I'm using MaterialUI's sx property to style some react components. I find having the full style definition inline with the components leads to messy, screen filling component bodies, so I have moved all the style definitions into a constant object outside the function component. Here's a quick example:
const styles = {
a: {/*...*/}
b: {/*...*/}
}
const component = () => <Box sx={styles.a}>
<Button sx={styles.b}>Button Text</Button>
</Box>
The problem I'm having is ensuring that styles.a and styles.b are valid objects, which is difficult without the IDE knowing what types they should be. If I declare styles: Record<string, SystemStyleObject> I gain type checking when defining a and b, but I lose knowledge over the actual keys of styles and accessing styles.c no longer errors.
I've come up with two clunky work-arounds:
#1 Declare the individual variables and then collect them into an object
const a: SystemStyleObject = {}
const b: SystemStyleObject = {}
const styles = {a, b}
This gets me to the end product I want, but it requires declaring every single style in a separate variable, as well as explicity typing them.
#2 Pass a union of the objects keys as the first argument to record
const styles: Record<'a' | 'b', SystemStyleObject> = {
a: {/*...*/}
b: {/*...*/}
}
This also gets me to the same end product, but makes adding or changing the keys of the object awkward as you have to add them to the union as well as the body.
#2 is the one I've gone with for now, but I'm wondering if there's a better way to achieve this.
Solution for code completion in vscode:
You can import SxProps property directly from MUI, use it to describe your style objects and enjoy working IDE code completion. There also should be installed vscode-styled-components extension
import { SxProps } from '#mui/material/styles';
type StylesObjectType = {
[key:string]:SxProps
}
const styles: StylesObjectType = {
a: {
border: "1px solid red",
}
};
For the second part of the question - here are
suggestions on how to create a typed Record without explicitly defining the keys.

How to use styled-system responsive props

Can I use the styled-system to achieve something like this?
<MyComponent
backgroundImage={{
default: "https://placekitten.com/380/80",
sm: "https://placekitten.com/340/80"
}}
/>
or this (because I know it can be done this way too with other "style props" such as width, but I prefer to use an object with keys):
<MyComponent
backgroundImage={[
"https://placekitten.com/300/80",
"https://placekitten.com/500/80"
]}
/>
I think the code examples above are self-descriptive and they follow the library's pattern but just to be clear, I'm mapping the values (image sources) to the breakpoints (default and next one up).
For example, this works out of the box:
<Box
width={[
default: 1,
sm: 1/3,
]}
/>
The output is something like this:
.QWojd {
width: 100%;
}
#media screen and (min-width: 24em) {
.QWojd {
width: 33.33333333333333%;
}
}
I've been looking into the source code and this part here makes me think it should work with backgroundImage too:
Sadly, it doesn't work, and the result is a stringified object (or concatenated array values) in the CSS output.
I can't think of how the variant function would be useful here as people have suggested. I've tried to use the system function but I just can't understand the documentation. The ResponsiveValue type gives me a hint but I feel like crawling in the dark when I try to understand the internals.
Ultimately, I'd like to use the "breakpoints object" (or array) with whatever custom prop I feel like, like this:
<Box
myProp={[
default: 'foo',
sm: 'bar',
]}
/>
Note: I've learned (from experience) that you can just use the "breakpoints array" version (without setting breakpoints in a theme and passing it to a provider) and that will map the value to the first 2 default breakpoints (not sure where they come from) but if you want to use an object with keys you need to use a ThemeProvider with a theme object with your own breakpoints.
Note 2: I can understand the styled-system documentation up to this point: https://styled-system.com/custom-props. When I arrive here I feel like this is what I'm looking for but I can't understand the example, the explanation confuses me even more and I can't find any examples on the web.
Note 3: Spectrum Chat has a styled-system channel and the library author is in there but sadly I haven't been able to send any messages there (constant network error)
Examples
Ok, so according to the docs (https://styled-system.com/custom-props/), in order to create a custom prop (or in this case, replace the existing one) you should use the system utility. Since I'm not a user of this library (styled-system), I'm not 100% sure that this is correct approach, but I tested on top of your example code and it seems to work as you wanted.
The component declaration (it also works with objects like you wanted) with an array:
<ResponsiveImageBox
color="white"
backgroundImage={[
"https://placekitten.com/300/80",
"https://placekitten.com/500/80"
]}
>
Box 8
</ResponsiveImageBox>
with objects:
<ResponsiveImageBox
color="white"
backgroundImage={{
default: "https://placekitten.com/300/80",
sm: "https://placekitten.com/500/80"
}}
>
Box 8
</ResponsiveImageBox>
And this is the component code:
export const ResponsiveImageBox = styled(Box)(
({ myCustomProp }) => {
return css`
${system({
backgroundImage: {
property: "backgroundImage",
transform: value => `url(${value})`
}
})}
`
});
As you can see on examples 4, 5 and 8 (https://stackblitz.com/edit/styled-system-mccqje?file=Examples.tsx), I also did it for the border-radius attribute with a simple prop renaming and just specifying what css attribute I wanted to change (property), so no need to add transform as the value will remain the same.
export const ExtendedBox2 = styled(Box)<ExtendedBoxProps>`
background-position: center;
${system({
myCustomProp: {
property: "border-radius"
}
})}
`;
Have a look and see if this is what you were looking for! :)
I know you already marked it as solved, and Eduardo's approach definitely works. However another way you can do it "out of the box" is to use aliases so that you can use objects instead of arrays (source: https://styled-system.com/responsive-styles/):
// theme.js
const breakpoints = ['40em', '52em', '64em', '80em']
// aliases
breakpoints.sm = breakpoints[0]
breakpoints.md = breakpoints[1]
breakpoints.lg = breakpoints[2]
breakpoints.xl = breakpoints[3]
export default {
breakpoints,
}
// ResponsiveImageBox.js
<ResponsiveImageBox
color="white"
backgroundImage={{
md: "https://placekitten.com/300/80",
sm: "https://placekitten.com/500/80"
}}
>
Box 8
</ResponsiveImageBox>

How to chain useState() method from React hook

Is it possible to chain a React hook? If so, how?
A typical application of a hook would look like this:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
b = a.filter(isDairy)
updateInventory(b)
We can also do this, but it's not chained:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
updateInventory(a.filter(isDairy))
What I want is a chained hook in a functional style:
const [inv, updateInventory] = useState([])
a = ["cheese", "bread", "apples"]
a.filter(isDairy).updateInventory()
Can a hook can be modified to take state from this?
Proper usage would be:
updateInventory([...a, "cheddar"].quicksort().filter("cheese"))
But if you really want that chaining, look into how to edit the array prototype.
This is really not recommended, as that method will then be available on all arrays.
I think the underlying problem is you're not clear on what's actually happening with method chaining and possibly with hooks. The specific question:
Can a hook can be modified to take state from this?
doesn't really make sense. So let's break down why then come back at the end to how you could approach this.
For method chaining, let's try a simple example using two methods, .filter and .map, that have two important properties:
They actually return arrays (unlike .push, which returns the new length of the array); and
They actually exist on arrays (unlike .quicksort, which exists on neither an array nor the integer you were calling it on).
function isDairy(item) {
return ["cheese", "milk"].includes(item);
}
function getPrice(item) {
return { bread: 0.58, cheese: 0.80, apples: 0.47, milk: 1.01 }[item];
}
const inStock = ["bread", "cheese", "apples"];
inStock
.filter(isDairy)
.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
There's nothing particularly special happening here, each method you're calling returns a new array on which you can also call any method an array has. You could assign the intermediate steps and get the same result:
const filteredStock = stock.filter(isDairy);
// => ["cheese"]
const pricedFilteredStock = filteredStock.map((item) => ({ item, price: getPrice(item) }));
// => [{ item: "cheese", price: 0.8 }]
It is not the case that:
these are standalone functions (like in e.g. Python where you map(callable, iterable)); or
that the item.name syntax is doing anything beyond just accessing a property named name on the item.
If I tried to use the filter method as a standalone function:
filter(isDairy, inStock);
that would be a ReferenceError, or if I defined another function and tried to access it as if it was a prop on an array:
function allUppercase() {
return this.map((item) => item.toUpperCase());
}
inStock.allUppercase();
it would be a TypeError (because isStock.allUppercase is undefined and undefined isn't callable).
Note you could do allUppercase.bind(inStock)() (or the neater allUppercase.call(inStock)), though; JavaScript does have a means of setting this for a function.
When you use the useState hook, you're calling a function that returns an array containing two objects, and destructuring that array to two local variables:
const [thing, setThing] = useState(initialValue);
is equivalent to:
const result = useState(initialValue);
const thing = result[0];
const setThing = result[1];
The thing, setThing naming is just a convention; really, we're accessing those two objects (current value and setter function) by position. They don't have names of their own, you can do const [foo, bar] = useState("baz") (but... don't).
As the setter is a function you might be wondering whether you can use setThing.bind here, but if setThing is written to use this (I didn't look into the implementation, as it's not directly relevant), it's not going to be happy if you change what this is!
So this comes together when you try to do:
const [basket, setBasket] = useState([]);
// ^^^^^^^^^
inStock.filter(...).map(...).setBasket();
// ^^^^^^^^^
As with the example above, this is a TypeError because setBasket doesn't exist on the array returned by .map. The fact that the same "word" setBasket appears twice is totally irrelevant as far as JavaScript is concerned; one is a local variable and the other is a prop on an array, there's no connection between them.
.map(...) returns a new array, one that we didn't already have a reference to, so the only way to make this work is to ensure all arrays have a setBasket method, which means patching the prototype (as covered in adding custom functions into Array.prototype):
Object.defineProperty(Array.prototype, "setBasket", {
value () {
setBasket(this);
},
});
One problem here is that the function setBasket is accessed via a closure, so it needs to happen inside the component where the hook is defined, so it's going to get defined every time the component is rendered (or you're going to useEffect), which is a problem because you can't redefine that method as written...
But let's ignore that because the bigger problem is that every array in your app now has that method, even in contexts where it's not relevant. If you have multiple state hooks, as seems likely in any non-trivial app, your arrays are gaining lots of methods globally that are only for use in small local scopes.
A more feasible approach is to add a generic method that can be used to apply any hook (in fact any function) to an array:
Object.defineProperty(Array.prototype, "andCall", {
value (func) {
return func(this);
},
});
This can be added once, globally, and used to apply whatever hook is relevant:
inStock.filter(...).map(...).andCall(setBasket);
Note that if you're using TypeScript, you'd also have to add the definition to the global array type, e.g.:
declare global {
interface Array<T> {
andCall<S>(func: (arr: Array<T>) => S): S;
}
}

React JS .map and assignment causing esLint error 'Assignment to property of function parameter. eslint(no param-reassign)'

I am trying to figure out how to fix a esLint error of the following
'Assignment to property of function parameter. eslint(no param-reassign)'
Here is my code so far
myArray.records.map(record =>
record.subRecords.map(subRecord => (
subRecord.transId = this.getIdCorrection(subRecord.transId)
))
))
I tried using cloneDeep and making another copy for map and reassign, the error is there there.
I read a few post but I am confused with how they are using filter.
I am pass an integer value and it passes an integer based on some business rules.
I am not sure how to get pass this one.
Thanks
ESLint probably wants you to create and return a new object. Remember that map isn't for mutating the elements of an array, it's for creating a new array of items.
const newRecords = myArray.records.map(record => ({
...record,
subRecords: record.subRecords.map(subRecord => ({
...subRecord,
transId: this.getIdCorrection(subRecord.transId)
})
}))

Resources