React component doesnt update when updating from different component

66 Views Asked by At

So I am creating a webapp where I am using Material-UI DataGrid. Expense.js below is the main page I am trying to render. Inside it, I have used three different components. The Custom Data Grid component, the Loading component, and a Custom Upload Button which I am rendering inside each DataGrid row in a column.

Now the Upload Button uploads a file to Drive and tries to add its URL to another column. I fetch the data initially to fill the Datagrid from the server. If I click on the Upload Button for the prefetched data, it works well. It sets the URL to another column.

If I add a row dynamically on the UI and then try to upload a file, it doesn't set the URL in the other column. I can see API request succeeding and rows being updated. But it doesn't show up on the UI.

Strange part is, if I don't split these components into three separate components and add everything inside the main Expense component as nested components, it all works well. The code below is a bit lengthy, I have tried to keep it readable.

Expense.js

    const [snackbar, setSnackbar] = useState(null);
    const [isLoading, setLoading] = useState(true);
    const [rowModesModel, setRowModesModel] = useState({});
    const [rows, setRows] = useState([]);

    const columns = [
        { field: 'id', headerName: 'ID', width: 50 },
        { field: 'projectId', type: 'number', headerName: 'Project ID', width: 150, editable: true },
        { field: 'dueId', headerName: 'Due ID', width: 150 },
        { field: 'description', headerName: 'Description', width: 150, editable: true },
        {
            field: 'expenseDate', type: 'date', headerName: 'Expense Date', width: 150, editable: true, valueGetter: (params) => {
                return new Date(params.value)
            }
        },
        { field: 'amount', type: 'number', headerName: 'Amount', width: 150, editable: true },
        {
            field: 'document', headerName: 'Document Link', width: 150,
            // renderCell: renderLink, 
            editable: true,
        },
        {
            headerName: 'Upload Document', width: 150,
            renderCell: (params) => <UploadButton params={params} ------> UPLOAD BUTTON COMPONENT
                setRows={setRows} rows={rows} setLoading={setLoading} setRowModesModel={setRowModesModel} />,
        },
        { field: 'approved', type: 'boolean', headerName: 'Approved', width: 150, editable: true },
        { field: 'reimburesed', type: 'boolean', headerName: 'Reimbursed', width: 150, editable: true },
    ]

    return (
        <div>
            {isLoading && (<Loading />)}
            <DataGridCustom
                customColumns={columns}
                getUrl={EXPENSE_LIST_URI}
                createUrl={CREATE_EXPENSE_URI}
                updateUrl={UPDATE_EXPENSE_URI}
                rowModesModel={rowModesModel}
                setRowModesModel={setRowModesModel}
                setLoading={setLoading}
                setSnackbar={setSnackbar}
                rows={rows}
                setRows={setRows} />
            {!!snackbar && (
                <SnackBarCustom snackbar={snackbar} setSnackbar={setSnackbar} />
            )}
        </div>
    );

}


Upload_Button.js

const VisuallyHiddenInput = styled('input')({
    clip: 'rect(0 0 0 0)',
    clipPath: 'inset(50%)',
    height: 1,
    overflow: 'hidden',
    position: 'absolute',
    bottom: 0,
    left: 0,
    whiteSpace: 'nowrap',
    width: 1,
});

export default function UploadButton({params, setLoading, setRowModesModel, setRows, rows}) {
    const { userName, userEmail, userRole } = useContext(UserContext)

    function handleChange(event) {
        setLoading(true)
        const request = new XMLHttpRequest();
        const url = process.env.REACT_APP_SERVER_HOST + USER_GOOGLE_TOKEN_URI
        request.open("GET", url, false); // `false` makes the request synchronous
        request.withCredentials = true;
        request.send();
        const res = JSON.parse(request.responseText)
        const file = event.target.files[0]
        const formData = new FormData();
        const metadata = {
            name: userEmail + "_" + Date.now() + "_" + file.name,
            mimeType: file.type,
            parents: [EXPENSES_FOLDER_ID],
            response: 'file.id'
        }
        formData.append('metadata', new Blob([JSON.stringify(metadata)], {
            type: "application/json"
        }))
        formData.append("file", file)

        fetch(GOOGLE_DRIVE_UPLOAD_FILE_URI, {
            method: 'POST',
            headers: new Headers({ 'Authorization': 'Bearer ' + res.accessToken }),
            body: formData
        }).then((res) => {
            return res.json();
        }).then(function (val) {
            const fileUrl = DRIVE_FILE_URI + val.id
            console.log("Params")
            console.log(params)
            setRowModesModel((oldModel) => ({
                ...oldModel,
                [params.id]: { mode: GridRowModes.Edit, fieldToFocus: 'document' },
            }));
            setRows(rows.map((row) => (row.id === params.id ? {...row, document: fileUrl} : row))); ----------> Setting Row here
            setLoading(false);
        });
    }


    return (
        <Button size="small" component="label" variant="contained" startIcon={<CloudUploadIcon />}>
            Upload file
            <VisuallyHiddenInput type="file" onChange={handleChange} />
        </Button>
    );

Custom_Data_Grid.js

export default function DataGridCustom({ customColumns, customPermissions, getUrl,
    createUrl, updateUrl, rowModesModel, setRowModesModel, setLoading, setSnackbar, rows, setRows }) {


    console.log("rendering data grid")
    console.log("rows")
    console.log(rows)
    function guidGenerator() {
        var S4 = function () {
            return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
        };
        return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
    }

    const { userName, userEmail, userRole } = useContext(UserContext)

    function checkPermission(action) {
        return customPermissions[action] === "admin" ? true : false
    }

    console.log({ customColumns })
    const columns = [
        ...customColumns,
        {
            field: 'actions',
            type: 'actions',
            headerName: 'Actions',
            width: 100,
            cellClassName: 'actions',
            getActions: ({ id }) => {
                const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

                if (isInEditMode) {
                    return [
                        <GridActionsCellItem
                            icon={<SaveIcon />}
                            label="Save"
                            sx={{
                                color: 'primary.main',
                            }}
                            onClick={handleSaveClick(id)}
                            disabled={userRole !== "admin"}
                        />,
                        <GridActionsCellItem
                            icon={<CancelIcon />}
                            label="Cancel"
                            className="textPrimary"
                            onClick={handleCancelClick(id)}
                            color="inherit"
                            disabled={userRole !== "admin"}
                        />,
                    ];
                }

                return [
                    <GridActionsCellItem
                        icon={<EditIcon />}
                        label="Edit"
                        className="textPrimary"
                        onClick={handleEditClick(id)}
                        color="inherit"
                        disabled={userRole !== "admin"}
                    />,
                    <GridActionsCellItem
                        icon={<DeleteIcon />}
                        label="Delete"
                        onClick={handleDeleteClick(id)}
                        color="inherit"
                        disabled
                    />,
                ];

            },
        },
    ];
    useEffect(() => {
        fetch(process.env.REACT_APP_SERVER_HOST + getUrl, {
            credentials: "include"
        })
            .then((response) => response.json())
            .then((data) => {
                console.log(data);
                setRows(data);
                setLoading(false);
            }).catch((error) => {
                console.log(error.message);
                handleProcessRowUpdateError(error)
            });
    }, []);

    const makeRequest = (data) => {
        const request = new XMLHttpRequest();
        if (data.isNew) {
            const url = process.env.REACT_APP_SERVER_HOST + createUrl
            request.open("POST", url, false); // `false` makes the request synchronous
            delete data.id;
        }
        else {
            const url = process.env.REACT_APP_SERVER_HOST + updateUrl
            request.open("PUT", url, false); // `false` makes the request synchronous
        }
        request.setRequestHeader("Content-Type", "application/json");
        delete data.isNew;
        request.withCredentials = true;
        request.send(JSON.stringify(data));
        return JSON.parse(request.responseText)
    }

    function AddToolbar(props) {
        const { setRows, setRowModesModel } = props;

        const handleClick = () => {
            const id = guidGenerator()
            setRows((oldRows) => [...oldRows, { id, isNew: true }]);
            setRowModesModel((oldModel) => ({
                ...oldModel,
                [id]: { mode: GridRowModes.Edit, fieldToFocus: 'name' },
            }));
        };

        return (
            <GridToolbarContainer>
                <Button color="primary" startIcon={<AddIcon />} onClick={handleClick} disabled={userRole === "user"}>
                    Add Record
                </Button>
            </GridToolbarContainer>
        );
    }


    const handleRowEditStop = (params, event) => {
        if (params.reason === GridRowEditStopReasons.rowFocusOut) {
            event.defaultMuiPrevented = true;
        }
    };

    const handleEditClick = (id) => () => {
        setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
    };

    const handleSaveClick = (id) => () => {
        setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
    };

    const handleDeleteClick = (id) => () => {
        setRows(rows.filter((row) => row.id !== id));
    };

    const handleCancelClick = (id) => () => {
        setRowModesModel({
            ...rowModesModel,
            [id]: { mode: GridRowModes.View, ignoreModifications: true },
        });

        const editedRow = rows.find((row) => row.id === id);
        if (editedRow.isNew) {
            setRows(rows.filter((row) => row.id !== id));
        }
    };

    const processRowUpdate = (newRow) => {
        setLoading(true)
        console.log("New Row")
        console.log(newRow)
        const oldId = newRow.id
        const savedRow = makeRequest(newRow);
        setRows(rows.map((row) => (row.id === oldId ? savedRow : row)));
        setLoading(false)
        return savedRow;
    };

    const handleRowModesModelChange = (newRowModesModel) => {
        setRowModesModel(newRowModesModel);
    };
    const handleProcessRowUpdateError = useCallback((error) => {
        setLoading(false)
        setSnackbar({ children: error.message, severity: 'error' });
    }, []);

    return (

        <div>
            <Box
                sx={{
                    height: 500,
                    width: '100%',
                    '& .actions': {
                        color: 'text.secondary',
                    },
                    '& .textPrimary': {
                        color: 'text.primary',
                    },
                }}
            >
                <DataGrid
                    rows={rows}
                    columns={columns}
                    editMode="row"
                    rowModesModel={rowModesModel}
                    onRowModesModelChange={handleRowModesModelChange}
                    onRowEditStop={handleRowEditStop}
                    processRowUpdate={processRowUpdate}
                    onProcessRowUpdateError={handleProcessRowUpdateError}
                    slots={{
                        toolbar: AddToolbar,
                    }}
                    slotProps={{
                        toolbar: { setRows, setRowModesModel },
                    }}
                />
            </Box>
        </div>
    );

}
0

There are 0 best solutions below