Mobx React with Material Grid - reactjs

I think this is a simple enough question. I am trying to create a material ui grid in react and be able to add rows to it. I've tried several variations of patterns and nothing is working for me. I'm pasting the sample below, and you can find the code here.
import React from "react";
import "./styles.css";
import { observer, useObservable } from "mobx-react-lite";
import MaterialTable from "material-table";
import Button from "#material-ui/core/Button";
const columns = [
{ title: "First Name", field: "name" },
{ title: "Last Name", field: "surname" },
{ title: "Year of Birth", field: "birthYear", type: "numeric" },
{
title: "City of Birth",
field: "birthCity",
lookup: {
34: "Haughton",
63: "San Diego",
88: "Henryetta"
}
}
];
const initialData = [
{ name: "Dak", surname: "Prescott", birthYear: 1993, birthCity: 34 }
];
const addData = [
{ name: "Troy", surname: "Aikman", birthYear: 1966, birthCity: 88 },
{ name: "Tony", surname: "Romo", birthYear: 1980, birthCity: 63 }
];
const App = observer(() => {
const store = useObservable({
data: initialData,
index: 0,
addRow() {
if (store.index < store.data.length) {
store.data.push(addData[store.index++]);
}
}
});
return (
<div className="App">
<MaterialTable
columns={columns}
data={store.data}
title="Sample Material Table"
/>
<Button onClick={() => store.addRow}>Add Row</Button>
</div>
);
});
export default App;
This is just one attempt. My real Mobx stores I create in separate files and each attribute that is observable I annotate with #observable, but at this point I'll accept absolutely anything that works. I've gotten Mobx stores working with native values (strings and numbers) but not with an array of objects. I hope I'm just missing some small nuance.

You're right about the convention for modifying stores - generally, it's better to create a new array or object and spread your old data into the new one while you merge your new data in.
I think in your case, you just have a typo in your onClick handler. You're not invoking it in the Button element - you're running an arrow function, but inside you're not calling the addRow method! I've rewritten it for you and also switched to the useLocalStore hook as that's the recommended one to use these days.
This is also a good opportunity to review the syntax for calling functions from onClick attributes in React.
onClick={handleClick} // Passing a reference to a function, React will automatically call this function when the element is clicked. You don't need to call it manually. In other words, putting parenths here is wrong.
onClick={() => handleClick} // Creating an inline arrow function, React will automatically run this function like above. It will execute each line just like a normal function. It will reach the identifier handleClick and do nothing more because handleClick is not being called within the new arrow function you've created.
onClick={() => handleClick()} // Same as above in that you're creating an inline arrow function. React will run this arrow function like above. It will execute each line and then call your function because you've invoked it with parenths.
Hope this helps!
By the way, you'll want to verify that the data source you're getting new rows from is able to accept the index you're passing in. Right now if you click the button more than twice you'll get an index out of bounds error. But I'm assuming you're taking care of that part already so I didn't change that part of your code.
import React from "react";
import "./styles.css";
import { useObserver } from "mobx-react-lite";
import MaterialTable from "material-table";
import Button from "#material-ui/core/Button";
import { useLocalStore } from "mobx-react";
const columns = [
{ title: "First Name", field: "name" },
{ title: "Last Name", field: "surname" },
{ title: "Year of Birth", field: "birthYear", type: "numeric" },
{
title: "City of Birth",
field: "birthCity",
lookup: {
34: "Haughton",
63: "San Diego",
88: "Henryetta"
}
}
];
const initialData = [
{ name: "Dak", surname: "Prescott", birthYear: 1993, birthCity: 34 }
];
const addData = [
{ name: "Troy", surname: "Aikman", birthYear: 1966, birthCity: 88 },
{ name: "Tony", surname: "Romo", birthYear: 1980, birthCity: 63 }
];
const App = () => {
const store = useLocalStore(() => ({
data: initialData,
index: 0,
addRow() {
if (this.index < this.data.length) {
this.data = [...this.data, addData[this.index++]];
}
}
}));
return useObserver(() => (
<div className="App">
<MaterialTable
columns={columns}
data={store.data}
title="Sample Material Table"
/>
<Button onClick={store.addRow}>Add Row</Button>
</div>
));
};
export default App;

Ok, I took a look at other posts with the material-table tag and found the key to my problem. Instead of pushing an element to my data array I set the array to an entirely new array with the new element appended.
store.data = [...store.data, addData[store.index++]];
As it turns out, my example above doesn't work even with that change, but changing the array modifier to return a new array is the key to the solution.

Related

AntD Tree: need help! can't pass react element as icon OR title for antd tree

I'm using the AntD tree and I have a react element that I want to pass as either an icon or a title because it has custom styling. Due to it being IP I can't share too much code, but my question is:
how can I pass a react element (see below i.e. generic name) as either a title or icon and have antD tree render it?
i.e. this is what I want to pass as a prop to the icon or title
import React from 'react';
const genericName = (props) => {
// code uses props to get some infor for Color
// cant share code due to proprietary reasons
// but it is not needed for this question
const colorHTML = getColor(Color);
return (
<div>
<div className={`colors from`}>${colorHTML}</div>
{pin}
</div>
);
};
export default genericName;
in my console you can see node.icon is a typeof react.element. I want to target that and just pass the prop into antD tree as either title or icon
i.e.
return (
<Tree
icon={node.icon}
/>
)
I've searched and similar answers were given before antD forbid the use of children and strictly allows treeData. All examples I see only use strings in titles/icons, but since antD documentation is very limited, I need to know if my use case is possible. Right now, for the life of me I can't understand why it doesn't populate.
Thank you in advance.
It should definitely work to put a JSX component as title within treeData. Take a look at this snippet, I added a Icon here in one of the titles:
import React from 'react'
import { RightCircleOutlined } from '#ant-design/icons'
type Props = {}
import { Tree } from 'antd';
import type { DataNode, TreeProps } from 'antd/es/tree';
const treeData: DataNode[] = [
{
title: <span>{<RightCircleOutlined />} parent</span>, //icon added here
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
disabled: true,
children: [
{
title: 'leaf',
key: '0-0-0-0',
disableCheckbox: true,
},
{
title: 'leaf',
key: '0-0-0-1',
},
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [{ title: <span style={{ color: '#1890ff' }}>sss</span>, key: '0-0-1-0' }],
},
],
},
];
const Demo: React.FC = () => {
const onSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
console.log('selected', selectedKeys, info);
};
const onCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
console.log('onCheck', checkedKeys, info);
};
return (
<Tree
checkable
defaultExpandedKeys={['0-0-0', '0-0-1']}
defaultSelectedKeys={['0-0-0', '0-0-1']}
defaultCheckedKeys={['0-0-0', '0-0-1']}
onSelect={onSelect}
onCheck={onCheck}
treeData={treeData}
/>
);
};
export default Demo;

how to add unique key using uuid in react js?

I read a comment from someone here in StockOverflow who talks about React keys and said that
'React expects STABLE keys, meaning you should assign the keys once and every item on your list should receive the same key every time, that way React can optimize around your data changes when it is reconciling the virtual DOM and decides which components need to re-render. So, if you are using UUID you need to do it at the data level, not at the UI level',
and I want to ask if anyone know how to apply this in a real code where we have for example a context component that have an array of objects and another component that maps through this array, how can we apply this using uuid() or any other package.
Although it is not a common requirement for you to generate id on FE, it happens some times, so using uuid is a really good way of doing that. It is easy and implementation is quick.
I made an example for you here how to do it:
import { useEffect, useState } from "react";
import { v1 } from "uuid";
import "./styles.css";
const items: { name: string; id?: string }[] = [
{
name: "Name1"
},
{
name: "Name2"
},
{
name: "Name3"
},
{
name: "Name4"
},
{
name: "Name5"
},
{
name: "Name6"
}
];
export default function App() {
const [itemList, setItemList] = useState<{ name: string; id?: string }[]>([]);
useEffect(() => {
setItemList(items.map((item) => ({ ...item, id: v1() })));
}, []);
return (
<div className="App">
{itemList.map((item) => (
<div key={item.id}>
{item.name} - {item.id}
</div>
))}
</div>
);
}
In this example your array is empty at the beginning and after first useEffect gets populated by items with uuid generated ids:
And code sandbox code
You can install react-uuid
import uuid from 'react-uuid'
const array = ['one', 'two', 'three']
export const LineItem = item => <li key={uuid()}>{item}</li>
export const List = () => array.map(item => <LineItem item={item} />)
or you can use crypto.randomUUID() directly without pckgs

React Table 7 - How to change the state of an individual button created dynamically by the React-Table API

I am using React Table 7 to create a table with the first cell of each row being a ChecOut/CheckIn Button. My goal is to create a tool where people can CheckOut and CheckIn equipment. My React Table CodeSandbox
The idea is that when a user clicks the button, it will change from "CheckOut" to "CheckIn" and vise versa. It will also change the color of the row when Checked out. I was able to accomplish this with JQuery (code is in the sandbox under the App() functional component) but want to avoid that and am sure it's easily doable in react.
My issue is changing the state of an individual button and which functional component to define it in. The <Button> elements are created dynamically by the React-Table, defined in the columns object under the App functional component.
{
Header: "CheckIn/Out",
Cell: ({ row }) => (
<Button
key={row.id}
id={row.id}
onClick={e => checkClick(e)}
value={checkButton.value}
>
{checkButton.value}
</Button>
)
}
I tried passing a function to onChnage attribute on the <Button> element. In that function I updated the state of the <Button> to change the string value from "CheckOut" to "CheckIn" but that does not work. At first, that changed the name (state) of All the <Button> elements. Not sure if the state will live in the App() component or the TableRe() functional component. Or do I need to use state at all?
I basically built this on top of the editable table code available on the React-Table website React-Table Editable Data CodeSandbox , examples section React-Table Examples .
Any help is much appreciated!
data.js
import React from "react";
// export default random => {
// let arr = [];
// for (let i = 0; i < random; i++) {
// arr.push({
// first: chance.name(),
// last: chance.last(),
// birthday: chance.birthday({ string: true }),
// zip: chance.zip()
// });
// }
// return arr;
// };
const temp_data = [
{
firstName: "johny",
lastName: "Bravo",
age: "23",
visits: "45",
progress: "complete",
status: "all done"
},
{
firstName: "johny",
lastName: "Bravo",
age: "23",
visits: "45",
progress: "complete",
status: "all done"
}
];
const ArrayData = () => {
const data = temp_data.map(item => {
return {
firstName: item.firstName,
lastName: item.lastName,
age: item.age,
visits: item.visits,
progress: item.progress,
status: item.status
};
});
return data;
};
// Do not think there is any need to memoize this data since the headers
// function TempData() {
// const data = React.useMemo(ArrayData, []);
// return data;
// }
export default ArrayData;

How to format value of the React Select

I use "react final forms" and "react select".
I've organized an interface and functionality works, but I have one thing which I don't like.
React select requires options in next format:
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
]
and the value also should be set as { value: 'chocolate', label: 'Chocolate' }.
But for me is strange to have in my model of data (and send to the server also) the value like this - { value: 'chocolate', label: 'Chocolate' }, because I need only 'chocolate'.
Easy way is format the object into a single value after the form will be saved and format back from single value to the object before the rendering of the element. I know how to do it outside of form, but in this case I should solve this problem again and again for each select element separately.
What I would like to do:
Find a way how to set value of the react select as single value, like 'chocolate' instead of object.
OR
Create a wrapper for react select component and format value there when it sets (it's easy) and when the form get the value from this field (this I don't know how to do).
I will appreciate any help.
Method 1
With strings:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
const data = ["1", "2"];
function SingleStringReactSelect() {
const [option, setOption] = useState();
return (
<section>
<Select
onChange={option => setOption(option)}
value={[option]}
getOptionLabel={label => label}
getOptionValue={value => value}
closeMenuOSelect={false}
options={data}
/>
</section>
);
}
Method 2:
Created example:
https://codesandbox.io/s/react-codesandboxer-example-z57ke
You can use map in options and with Wrapper like SingleValueReactSelect
import React, { Fragment, useState } from "react";
import Select from "react-select";
const data = ["chocolate", "strawberry", "vanilla"];
export function SingleValueReactSelect(props) {
const [selectedItem, setSelectedItem] = useState();
return (
<Select
{...props}
value={selectedItem}
onChange={item => {
setSelectedItem(item);
props.onChange(item.value);
}}
options={props.options.map(item => ({ label: item, value: item }))}
/>
);
}
export default function AppDemo() {
return (
<Fragment>
<p>SingleValueReactSelect Demo</p>
<SingleValueReactSelect
isClearable
isSearchable
options={data}
onChange={item => {
alert(item);
}}
/>
</Fragment>
);
}
You can transform/filter the object data once you are sending the form data to server or another component.
Setting the value of the react-select as a single value will not show it as selected in the React-Select.

React push data into object of arrays

How do I get imported data from importFile.js into dataTable.js?
https://github.com/Romson/CSV-file-uploader/blob/master/src/components/importFile.js
https://github.com/Romson/CSV-file-uploader/blob/master/src/components/dataTable.js
Tried this function to change nested arrays in imported data from importFile.js into a object of arrays.
const newArray = [data].map(
([firstName, lastName, issueCount, dateOfBirth]) => ({
firstName,
lastName,
issueCount,
dateOfBirth
})
);
Then a push into dataTable.js with:
data.rows.push(newArray);
What is the correct way to do this in React?
Expected result is to get the imported data to show in this table:
https://csv-file-uploader.herokuapp.com/
What you want to do is make DataTable component a child of Reader component. You need the array of object from Reader component for the rows property of datatable in DataTable component. As you said you are a beginner so better start from react hooks as it is easy.
Reader component
import React, {useState} from "react";
import CSVReader from "react-csv-reader";
import DatatablePage from "./dataTable";
import "../index.css";
const Reader = () => {
const [data, setData] = useState([]);
return (
<div className="container">
<CSVReader
cssClass="react-csv-input"
label="Upload a new CSV file"
onFileLoaded={(data) => setData(data)}
/>
<DatatablePage uploadedData={data} />
</div>
);
}
export default Reader;
DatatablePage component
import React from "react";
import { MDBDataTable } from "mdbreact";
const DatatablePage = ({uploadedData}) => {
const data = {
columns: [
{
label: "First Name",
field: "name",
sort: "asc",
width: 150
},
{
label: "Last Name",
field: "surname",
sort: "asc",
width: 270
},
{
label: "Issue count",
field: "issuecount",
sort: "asc",
width: 200
},
{
label: "Date of birth",
field: "dateofbirth",
sort: "asc",
width: 100
}
],
rows: []
};
// we append the passed props in the rows. read about spread operator if unaware of it.
const datatableProps = {...data, rows: uploadedData};
return <MDBDataTable striped bordered hover uploadedData={uploadedData} data={datatableProps} />;
};
export default DatatablePage;
We are using react hooks to create a state variable named data and a setter for it. Then we pass this state variable to the child component which can render it.

Resources