I'm learning Remix.run and trying to figure out how to face some requirements
in advance. According to the documentation there is something called Resource Routes. But seems that a Resource Route need to be linked from a Link component:
<Link to="pdf" reloadDocument>
View as PDF
</Link>
I can't found any example showing how to create a simple route that can return data for a grid component, for example ag-grid.
There is any way to do this inside Remix or I will need to implement an external endpoint?
AG Grid wrote a blog post about this not too long ago. Here is the article: https://blog.ag-grid.com/using-ag-grid-react-ui-with-remix-run/.
First, set up a resource route using Remix's conventions outlined here: https://remix.run/docs/en/v1/guides/resource-routes#creating-resource-routes
The resource route should export only a loader function that retrieves the data you want to load into the table.
Note: This example also uses logic for infinite scrolling
app/routes/posts/$id/postsGridData.ts
import type { LoaderFunction } from 'remix';
import { db } from '~/utils/db.server'; // Prisma ORM being used
export const loader: LoaderFunction = ({ request }) => {
const from = Number(new URL(request.url).searchParams.get("from"));
const to = Number(new URL(request.url).searchParams.get("to"));
if (from >= 0 && to > 0) {
const posts = await db.post.findMany({
skip: from,
take: to - from,
select: {
id: true,
title: true,
updatedAt: true,
author: {
select: {
email: true,
name: true,
},
},
},
});
return posts;
}
return [];
}
Next, in the route with your AGGridReact component, you'll add the following:
A Remix Fetcher to get the data from your resource route without a route change
An onGridReady function that loads the next batch of data
Some local state to manage the fetching logic
A datasource to plug into AG Grid
A useEffect function to trigger when the fetcher has loaded
AgGridReact component with added parameters rowModelType and onGridReady
app/routes/posts.tsx
import { useFetcher } from 'remix';
import { useCallback, useEffect, useState } from 'react';
import { AgGridReact } from "ag-grid-react";
import AgGridStyles from "ag-grid-community/dist/styles/ag-grid.css";
import AgThemeAlpineStyles from "ag-grid-community/dist/styles/ag-theme-alpine.css";
export default function PostsRoute() {
const [isFetching, setIsFetching] = useState(false);
const [getRowParams, setGetRowParams] = useState(null);
const posts = useFetcher();
const onGridReady = useCallback((params) => {
const datasource = {
getRows(params) {
if (!isFetching) {
posts.load(`/posts?from=${params.startRow}&to=${params.endRow}`);
setGetRowParams(params);
setIsFetching(true);
}
},
};
params.api.setDatasource(datasource);
}, []);
useEffect(() => {
// The useEffect hook in this code will trigger when the fetcher has
// loaded new data. If a successCallback is available, it’ll call it,
// passing the loaded data and the last row to load
if (getRowParams) {
const data = posts.data || [];
getRowParams.successCallback(
data,
data.length < getRowParams.endRow - getRowParams.startRow
? getRowParams.startRow
: -1
);
}
setIsFetching(false);
setGetRowParams(null);
}, [posts.data, getRowParams]);
const columnDefs = [/* Your columnDefs */];
return (
<div className="ag-theme-alpine" style={{ width: "100%", height: "100%" }}>
<AgGridReact
columnDefs={columnDefs}
rowModelType="infinite"
onGridReady={onGridReady}
/>
</div>
);
}
Related
I have since modified my code to focus on the problem so its more easier to understand by whoever attempts to help.
Scenario
I have a redux store created with Redux toolkit with a slice named 'asts' and initialized with 'astsTestData' array.
import { createSlice } from "#reduxjs/toolkit";
import { astsData, astsTestData } from "../data/astsData/astsData";
const astsSlice = createSlice({
name: "asts",
initialState: { astsData, astsTestData },
reducers: {
astCreated: (state, action) => {
state.astsData.push(action.payload);
},
astUpdated: (state, action) => {},
astDeleted: (state, action) => {},
astTestDataCreated: (state, action) => {
console.log(`astTestDataCreated running`);
console.log(`state.astsTestData`, state.astsTestData);
console.log(`action`, action);
return {
...state,
astsTestData: [...astsTestData, action.payload],
};
// state.astsTestData.push(action.payload)
},
},
});
// console.log(`astsSlice`, astsSlice)
export const { astCreated, astUpdated, astDeleted, astTestDataCreated } =
astsSlice.actions;
export default astsSlice.reducer;
I have another slice named 'sch'. This slice stores rgv data.
import { createSlice } from "#reduxjs/toolkit";
import { poData, splData, grvData } from "../data/schData/schData";
const schSlice = createSlice({
name: "sch",
initialState: { poData, splData, grvData },
reducers: {
// Purchase Order reducers
// Goods receiving reducers
grvCreated: (state, action) => {
console.log(`grvCreated running`);
console.log(`state.grvData`, state.grvData);
console.log(`action`, action);
state.grvData.push(action.payload);
},
grvUpdated: (state, action) => {},
grvDeleted: (state, action) => {},
},
});
// console.log(`schSlice`, schSlice);
export const {
poCreated,
poUpdated,
poDeleted,
popCreated,
popUpdated,
popDeleted,
grvCreated,
grvUpdated,
grvDeleted,
} = schSlice.actions;
export default schSlice.reducer;
I have a react component called 'Sch' that displays 'sch.grvData' on a ag-grid table. 'Sch uses 'ag-grid' on a component called 'GrvTestTable' to display 'sch.rgvData' acquired using useSelctor. This part works well all the time.
import React from "react";
import GrvTestAddAstBtn from "../../components/forms/grvForm/grvTest/GrvTestAddAstBtn";
import GrvTestTable from "../../components/forms/grvForm/grvTest/GrvTestTable";
const Sch = () => {
return (
<div className="sch">
<GrvTestTable />
<GrvTestAddAstBtn />
</div>
);
};
export default Sch;
The table displays 'sch.grvData' records that are created via dispatch from "handlSubmit" on grv form .
const handleSubmit = e => {
e.preventDefault();;
// dispatch data to 'sch.grvData'
dispatch(grvCreated(grvFormData));
// dispatch data to 'asts.astsTestData'
dispatch(
astTestDataCreated({
astId: nanoid(),
grvId: grvFormData.grvId,
astCartegory: grvFormData.grvAstCartegory,
astNo: grvFormData.grvAstNo,
})
);
setModalOpened(false);
setGrvFormData([]);
setComponentToOpen("");
};
From 'Sch', besides the ag grid table, there is a button used to open the 'grv form' where on submission the grv form data is written into the 'sch.rgvData' that's on in redux store after which, same data is used to create a new asts record on 'asts.astsTestData'.
I have another component called 'TestAstTable' that displays the 'asts.astsTestData' using ag data grid table. This is where the problem is.
Both my ag-grid tables ( and ) receive data from the grid store via useSelector.
TestAstTable
import React, { useRef, useMemo, useState, useEffect } from "react";
import { AgGridReact } from "ag-grid-react"; // the AG Grid React Component
import "ag-grid-community/styles/ag-grid.css"; // Core grid CSS, always needed
import "ag-grid-community/styles/ag-theme-alpine.css"; // Optional theme CSS
import "react-tippy/dist/tippy.css";
import { useSelector, useStore } from "react-redux";
const TestAstsTable = () => {
const { astsTestData } = useSelector(state => state.asts)
console.log(`astsTestData`, astsTestData);
const [rowData, setRowData] = useState(astsTestData)
const [columnDefs] = useState([
{ field: "astId" },
{ field: "grvId" },
{ field: "astCartegory" },
{ field: "astNo"},
])
useEffect(() => {
setRowData(astsTestData);
}, [astsTestData]);
const gridRef = useRef();
const defaultColDef = useMemo(
() => ({
sortable: true,
filter: true,
resizable: true,
}),
[]
);
// console.log(`rowData`, rowData);
return (
<div className={`ag-theme-alpine `}>
<AgGridReact
ref={gridRef} // Ref for accessing Grid's API
rowData={rowData} // Row Data for Rows
columnDefs={columnDefs} // Column Defs for Columns
defaultColDef={defaultColDef} // Default Column Properties
animateRows={true} // Optional - set to 'true' to have rows animate when sorted
rowSelection="single" // Options - allows click selection of rows
domLayout={"autoHeight"}
/>
</div>
);
};
export default TestAstsTable;
// TODO: mouse over tips on the TestAstsTable skipHeader
GrvTestTable
import React, { useRef, useMemo, useState, useEffect } from "react";
import { AgGridReact } from "ag-grid-react"; // the AG Grid React Component
import "ag-grid-community/styles/ag-grid.css"; // Core grid CSS, always needed
import "ag-grid-community/styles/ag-theme-alpine.css"; // Optional theme CSS
import "react-tippy/dist/tippy.css";
import { useSelector } from "react-redux";
const GrvTestTable = () => {
const { grvData } = useSelector(state => state.sch);
console.log(`grvData`, grvData);
const [rowData, setRowData] = useState(grvData);
useEffect(() => {
setRowData(grvData)
}, [grvData]);
const [columnDefs] = useState([
{ field: "grvId" },
{ field: "grvAstCartegory" },
{ field: "grvAstNo" },
]);
const gridRef = useRef();
const defaultColDef = useMemo(
() => ({
sortable: true,
filter: true,
resizable: true,
}),
[]
);
// console.log(`rowData`, rowData);
return (
<div className={`ag-theme-alpine `}>
<AgGridReact
ref={gridRef} // Ref for accessing Grid's API
rowData={rowData} // Row Data for Rows
columnDefs={columnDefs} // Column Defs for Columns
defaultColDef={defaultColDef} // Default Column Properties
animateRows={true} // Optional - set to 'true' to have rows animate when sorted
rowSelection="single" // Options - allows click selection of rows
domLayout={"autoHeight"}
/>
</div>
);
};
export default GrvTestTable;
Every time a new 'grv record' is created on 'sch.grvData', there is an ast record created on the 'asts.astsTestData'. This is done on form submit handler (handleSubmit) from 'grv form'.
Through redux devtools, I can can confirm that the store gets updated after I dispatch the action payload from the form submit. Via redux dev tools I can see that both 'sch.rgvData' and 'asts.astsTestData' are updated.
With redux toolkit immer, I'm able to use array push for both 'sch.rgvData' (state.grvData.push(action.payload)) and 'asts.astsTestData' (state.astsTestData.push(action.payload)) in my reducers to update the immutable sate.
Problem
'TestAstTable' table component which uses useSelctor to acquire redux state.astsTestData DOES NOT update but the 'GrvTestTable' does update on every grv form submission.
Efforts I've tried to solve the problem
I've looked all over the web in vain
I've tried useState and useEffect to trigger rerender when astsTestData updates but this does not work.
I've looked to see if I'm not mutating the store, and I am NOT
I thought the problem mmay be immutability so I tried on reducers to use old way with immer, no luck. With immer I to push the acton payload on the array. With the old way I used object spread operator. no luck.
I verified that I'm using only one store
Some weird observations
When I look at 'asts' redux page using redux dev tools I don't see updated asts state in redux store, but when I look at 'Sch' redux page using redux dev tools I do see both 'sch.grvData' and 'asts.astsTestData' updated state in redux store. This left me very confused.
It's hard to help you out without a reproducible example in stackblitz etc.
I have the following code,
const Layout: React.FC<LayoutProps> = ({ children }) => {
const darkMode = useRecoilValue(darkModeAtom)
console.log('darkMode: ', darkMode)
return (
<div className={`max-w-6xl mx-auto my-2 ${darkMode ? 'dark' : ''}`}>
<Nav />
{children}
<style jsx global>{`
body {
background-color: ${darkMode ? '#12232e' : '#eefbfb'};
}
`}</style>
</div>
)
}
I am using recoil with recoil-persist.
So, when the darkMode value is true, the className should include a dark class, right? but it doesn't. I don't know what's wrong here. But it just doesn't work when I refresh for the first time, after that it works fine. I also tried with darkMode === true condition and it still doesn't work. You see the styled jsx, that works fine. That changes with the darkMode value and when I refresh it persists the data. But when I inspect I don't see the dark class in the first div. Also, when I console.log the darkMode value, I see true, but the dark class is not included.
Here's the sandbox link
Maybe it's a silly mistake, But I wasted a lot of time on this. So what am I doing wrong here?
The problem is that during SSR (server side rendering) there is no localStorage/Storage object available. So the resulted html coming from the server always has darkMode set to false. That's why you can see in cosole mismatched markup errors on hydration step.
I'd assume using some state that will always be false on the initial render (during hydration step) to match SSR'ed html but later will use actual darkMode value. Something like:
// themeStates.ts
import * as React from "react";
import { atom, useRecoilState } from "recoil";
import { recoilPersist } from "recoil-persist";
const { persistAtom } = recoilPersist();
export const darkModeAtom = atom<boolean>({
key: "darkMode",
default: false,
effects_UNSTABLE: [persistAtom]
});
export function useDarkMode() {
const [isInitial, setIsInitial] = React.useState(true);
const [darkModeStored, setDarkModeStored] = useRecoilState(darkModeAtom);
React.useEffect(() => {
setIsInitial(false);
}, []);
return [
isInitial === true ? false : darkModeStored,
setDarkModeStored
] as const;
}
And inside components use it like that:
// Layout.tsx
const [darkMode] = useDarkMode();
// Nav.tsx
const [darkMode, setDarkMode] = useDarkMode();
codesandbox link
Extending on #aleksxor solution, you can perform the useEffect once as follows.
First create an atom to handle the SSR completed state and a convenience function to set it.
import { atom, useSetRecoilState } from "recoil"
const ssrCompletedState = atom({
key: "SsrCompleted",
default: false,
})
export const useSsrComplectedState = () => {
const setSsrCompleted = useSetRecoilState(ssrCompletedState)
return () => setSsrCompleted(true)
}
Then in your code add the hook. Make sure it's an inner component to the Recoil provider.
const setSsrCompleted = useSsrComplectedState()
useEffect(setSsrCompleted, [setSsrCompleted])
Now create an atom effect to replace the recoil-persist persistAtom.
import { AtomEffect } from "recoil"
import { recoilPersist } from "recoil-persist"
const { persistAtom } = recoilPersist()
export const persistAtomEffect = <T>(param: Parameters<AtomEffect<T>>[0]) => {
param.getPromise(ssrCompletedState).then(() => persistAtom(param))
}
Now use this new function in your atom.
export const darkModeAtom = atom({
key: "darkMode",
default: false,
effects_UNSTABLE: [persistAtomEffect]
})
I have created AgGrid using AgGridReact component, set server side datasource using examples from documentation but somehow infinite scroll is not working. getRows function is called only on the initial render and no scrollbar is shown on the grid.
import { AgGridReact } from '#ag-grid-community/react';
import {
AllModules,
ColumnApi,
GridReadyEvent,
IServerSideGetRowsParams,
ModelUpdatedEvent,
GridApi,
ColDef,
ToolPanelVisibleChangedEvent,
} from '#ag-grid-enterprise/all-modules';
export const DataGrid = <T extends unknown>({ rows, columns, onRowsFetch }: React.PropsWithChildren<Props<T>>) => {
const gridApi = useRef<GridApi>();
const gridColumnApi = useRef<ColumnApi>();
return (
<Box height="800px" width="100%" boxSizing="border-box" className="ag-theme-material">
<AgGridReact
modules={AllModules}
popupParent={document.body}
defaultColDef={defaultColDef}
headerHeight={90}
rowData={rows}
rowModelType={onRowsFetch ? 'serverSide' : 'clientSide'}
serverSideDatasource={{ getRows }}
cacheBlockSize={100}
blockLoadDebounceMillis={500}
maxBlocksInCache={100}
suppressContextMenu
colResizeDefault="shift"
columnDefs={columns as ColDef[]}
columnTypes={columnTypes.reduce((ac, a) => ({ ...ac, [a]: {} }), {})}
onGridReady={onGridReady}
onModelUpdated={resizeColumns}
onGridSizeChanged={resizeColumns}
onToolPanelVisibleChanged={resizeColumns}
suppressMovableColumns
suppressColumnMoveAnimation
sideBar={sideBarDef}
suppressMenuHide
alwaysShowVerticalScroll
/>
</Box>
);
function onGridReady({ api, columnApi }: GridReadyEvent) {
gridApi.current = api;
gridColumnApi.current = columnApi;
api.sizeColumnsToFit();
api.setFilterModel({});
}
function resizeColumns({ api }: ModelUpdatedEvent | ToolPanelVisibleChangedEvent) {
api.sizeColumnsToFit();
}
async function getRows({ request, successCallback, failCallback }: IServerSideGetRowsParams) {
if (!onRowsFetch) {
return;
}
try {
const result = await onRowsFetch({
startRow: request.startRow,
endRow: request.endRow,
sort: parseSortModel(request.sortModel),
filter: parseFilterModel(request.filterModel),
});
successCallback(result.content, result.endRow ?? -1);
} catch (error) {
failCallback();
throw error;
}
}
};
Request to the api does not include startRow and endRow parameters (getRows function's params argument's props request.startRow and request.endRow somehow are undefined).
However, api request still returns with this response:
{
content: [...rowData],
endRow: 10,
lastRow: 474,
startRow: 0,
}
What am I missing here?
Apparently, there were no issues with the code. The problem was in the version of the ag-grid packages. I've downgraded it from v25.3 to v24.1 and now everything works as expected.
I am using the Context API to load categories from an API. This data is needed in many components, so it's suitable to use context for this task.
The categories can be expanded in one of the child components, by using a form. I would like to be able to tell useCategoryLoader to reload once a new category gets submitted by one of the child components. What is the best practice in this scenario? I couldn't really find anything on google with the weird setup that I have.
I tried to use a state in CategoryStore, that holds a boolean refresh State which gets passed as Prop to the callback and can be modified by the child components. But this resulted in a ton of requests.
This is my custom hook useCategoryLoader.ts to load the data:
import { useCallback } from 'react'
import useAsyncLoader from '../useAsyncLoader'
import { Category } from '../types'
interface Props {
date: string
}
interface Response {
error?: Error
loading?: boolean
categories?: Array<Category>
}
const useCategoryLoader = (date : Props): Response => {
const { data: categories, error, loading } = useAsyncLoader(
// #ts-ignore
useCallback(() => {
return *APICALL with modified date*.then(data => data)
}, [date])
)
return {
error,
loading,
categories
}
}
export default useCategoryLoader
As you can see I am using useCallback to modify the API call when input changes. useAsyncloaderis basically a useEffect API call.
Now this is categoryContext.tsx:
import React, { createContext, FC } from 'react'
import { useCategoryLoader } from '../api'
import { Category } from '../types'
// ================================================================================================
const defaultCategories: Array<Category> = []
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories
})
// ================================================================================================
const CategoryStore: FC = ({ children }) => {
const { loading, categories } = useCategoryLoader({date})
return (
<CategoryContext.Provider
value={{
loading,
topics
}}
>
{children}
</CategoryContext.Provider>
)
}
export default CategoryStore
I'm not sure where the variable date comes from in CategoryStore. I'm assuming that this is an incomplete attempt to force refreshes based on a timestamp? So let's complete it.
We'll add a reload property to the context.
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories,
reload: () => {},
})
We'll add a state which stores a date timestamp to the CategoryStore and create a reload function which sets the date to the current timestamp, which should cause the loader to refresh its data.
const CategoryStore: FC = ({ children }) => {
const [date, setDate] = useState(Date.now().toString());
const { loading = true, categories = [] } = useCategoryLoader({ date });
const reload = useCallback(() => setDate(Date.now.toString()), []);
return (
<CategoryContext.Provider
value={{
loading,
categories,
reload
}}
>
{children}
</CategoryContext.Provider>
)
}
I think that should work. The part that I am most iffy about is how to properly memoize a function that depends on Date.now().
I'm still new to React, and functional programming, and Javascript, and JSX, so go easy if this is a stupid question.
I'm modifying one of the example material-ui tables from react-table v7. The original code can be found here. The example is completely functional and is using React Hooks as opposed to classes, as do all of the components of the template I'm using (shout out to creative-tim.com!)
My parent function (representative of a page in my dashboard application), for instance Users.js or Stations.js fetches data from a backend api inside a useEffect hook. That data is then passed as a prop to my subcomponent ReactTables.js
For some reason ReactTables.js does not receive changes to the "data" prop after the parent page's useEffect finishes. However, once I modify the data from a subcomponent of ReactTables (in this case AddAlarmDialog.js) then the table re-renders and all of my data suddenly appears.
How can I trigger the re-render of my subcomponent when data is returned from the parent component's useEffect? I noticed that in older versions of React there was a lifecycle function called componentWillReceiveProps(). Is this the behavior I need to emulate here?
Example Parent Component (Alarms.js):
import React, { useEffect, useState } from "react";
// #material-ui/core components
// components and whatnot
import GridContainer from "components/Grid/GridContainer.js";
import GridItem from "components/Grid/GridItem.js";
import ReactTables from "../../components/Table/ReactTables";
import { server } from "../../variables/sitevars.js";
export default function Alarms() {
const [columns] = useState([
{
Header: "Alarm Name",
accessor: "aName"
},
{
Header: "Location",
accessor: "aLocation"
},
{
Header: "Time",
accessor: "aTime"
},
{
Header: "Acknowledged",
accessor: "aAcked"
},
{
Header: "Active",
accessor: "aActive"
}
]);
const [data, setData] = useState([]);
const [tableType] = useState("");
const [tableLabel] = useState("Alarms");
useEffect(() => {
async function fetchData() {
const url = `${server}/admin/alarms/data`;
const response = await fetch(url);
var parsedJSON = JSON.parse(await response.json());
var tableElement = [];
parsedJSON.events.forEach(function(alarm) {
tableElement = [];
parsedJSON.tags.forEach(function(tag) {
if (alarm.TagID === tag.IDX) {
tableElement.aName = tag.Name;
}
});
tableElement.aTime = alarm.AlarmRcvdTime;
parsedJSON.sites.forEach(function(site) {
if (site.IDX === alarm.SiteID) {
tableElement.aLocation = site.Name;
}
});
if (alarm.Active) {
tableElement.aActive = true;
} else {
tableElement.aActive = false;
}
if (!alarm.AckedBy && !alarm.AckedTime) {
tableElement.aAcked = false;
} else {
tableElement.aAcked = true;
}
//const newData = data.concat([tableElement]);
//setData(newData);
data.push(tableElement);
});
}
fetchData().then(function() {
setData(data);
});
}, [data]);
return (
<div>
<GridContainer>
<GridItem xs={12} sm={12} md={12} lg={12}>
<ReactTables
data={data}
columns={columns}
tableType={tableType}
tableLabel={tableLabel}
></ReactTables>
</GridItem>
</GridContainer>
</div>
);
}
Universal Table Subcomponent (ReactTables.js):
import React, { useState } from "react";
// #material-ui/core components
import { makeStyles } from "#material-ui/core/styles";
// #material-ui/icons
import Assignment from "#material-ui/icons/Assignment";
// core components
import GridContainer from "components/Grid/GridContainer.js";
import GridItem from "components/Grid/GridItem.js";
import Card from "components/Card/Card.js";
import CardBody from "components/Card/CardBody.js";
import CardIcon from "components/Card/CardIcon.js";
import CardHeader from "components/Card/CardHeader.js";
import { cardTitle } from "assets/jss/material-dashboard-pro-react.js";
import PropTypes from "prop-types";
import EnhancedTable from "./subcomponents/EnhancedTable";
const styles = {
cardIconTitle: {
...cardTitle,
marginTop: "15px",
marginBottom: "0px"
}
};
const useStyles = makeStyles(styles);
export default function ReactTables(props) {
const [data, setData] = useState(props.data);
const [columns] = useState(props.columns);
const [tableType] = useState(props.tableType);
const [skipPageReset, setSkipPageReset] = useState(false)
const updateMyData = (rowIndex, columnId, value) => {
// We also turn on the flag to not reset the page
setData(old =>
old.map((row, index) => {
if (index === rowIndex) {
return {
...old[rowIndex],
[columnId]: value
};
}
return row;
})
);
};
const classes = useStyles();
return (
<GridContainer>
<GridItem xs={12}>
<Card>
<CardHeader color="primary" icon>
<CardIcon color="primary">
<Assignment />
</CardIcon>
<h4 className={classes.cardIconTitle}>{props.tableLabel}</h4>
</CardHeader>
<CardBody>
<EnhancedTable
data={data}
columns={columns}
tableType={tableType}
setData={setData}
updateMyData={updateMyData}
skipPageReset={skipPageReset}
filterable
defaultPageSize={10}
showPaginationTop
useGlobalFilter
showPaginationBottom={false}
className="-striped -highlight"
/>
</CardBody>
</Card>
</GridItem>
</GridContainer>
);
}
ReactTables.propTypes = {
columns: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
tableType: PropTypes.string.isRequired,
tableLabel: PropTypes.string.isRequired,
updateMyData: PropTypes.func,
setData: PropTypes.func,
skipPageReset: PropTypes.bool
};
**For the record: if you notice superfluous code in the useEffect it's because I was messing around and trying to see if I could trigger a re-render.
I dont know exactly how the reactTable is handling its rendering, but if its a pure functional component, then the props you pass to it need to change before it will re-evaluate them. When checking if props have changed, react will just do a simple === comparison, which means that if your props are objects whos properties are being modified, then it will still evaluate as the same object. To solve this, you need to treat all props as immutable
In your example, you are pushing to the data array, and then calling setData(data) which means that you are passing the same instance of the array. When react compares the previous version of data, to the new version that you are setting in the call to setDate, it will think data hasnt changed because it is the same reference.
To solve this, you can just make a new array from the old array by spreading the existing array into a new one. So, instead of doing
data.push(tableElement);
You should do
const newInstance = [...data, tableElement];
Your code will need some tweaking because it looks like you are adding in lots of tableElements. But the short version of the lesson here is that you should never try and mutate your props. Always make a new instance
EDIT: So, after looking again, I think the problem is the way you are using the default param in the useState hook. It looks like you are expecting that to set the state from any prop changes, but in reality, that param is simply the default value that you will put in the component when it is first created. Changing the incoming data prop doesn't alter your state in any way.
If you want to update state in response to changes in props, you will need to use the useEffect hook, and set the prop in question as a dependancy.
But personally, I would try and not have what is essentially the same data duplicated in state in two places. I think the best bet would be to store your data in your alarm component, and add a dataChanged callback or something which will take your new data prop, and pass it back up to alarm via a parameter in the callback