Adding a functional component to useeffect method in react js - reactjs

I'm trying to adding a sample ModalPage functional component in the useEffect method, but it is showing an error:
const [data, setData] = React.useState('');
useEffect(() => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
};
fetch('http://localhost:4000/api/users/getUserDetails', requestOptions)
.then(response => response.json())
.then(res => {
const data = {
columns: [{
label: 'First Name',
field: 'FirstName',
sort: 'asc',
width: 150
},
{
label: 'Last Name',
field: 'LastName',
sort: 'asc',
width: 270
},
{
label: 'Email',
field: 'Email',
sort: 'asc',
width: 200
},
{
label: 'Phone Number',
field: 'PhoneNumber',
sort: 'asc',
width: 100
},
{
label: 'Edit',
field: 'Edit',
width: 100
},
],
rows: res.isUser.map((data) => ({
FirstName: data.firstName,
LastName: data.LastName,
Email: data.email,
PhoneNumber: data.phoneNumber,
edit : <ModalPage/>
}))
}
setData(data);
})
}, [])
this is the modal page
export default function ModalPage() {
const [show, setShow] = React.useState(false);
function toggle () {
setShow({
show: !show
});
}
return (
<MDBContainer>
<MDBBtn onClick={toggle}>Modal</MDBBtn>
<MDBModal isOpen={show} toggle={toggle}>
<MDBModalHeader toggle={toggle}>MDBModal title</MDBModalHeader>
<MDBModalBody>
(...)
</MDBModalBody>
<MDBModalFooter>
<MDBBtn color="secondary" onClick={toggle}>Close</MDBBtn>
<MDBBtn color="primary">Save changes</MDBBtn>
</MDBModalFooter>
</MDBModal>
</MDBContainer>
);
}

As #Nisharg Shah said, you can't call <ModalPage/> inside of the useEffect hook. Try changing it to this:
rows: res.isUser.map((data) => ({
FirstName: data.firstName,
LastName: data.LastName,
Email: data.email,
PhoneNumber: data.phoneNumber,
Edit: ModalPage // Note the removal of the tags "</>"
}))
Then to render you can add this to your return statement:
<MDBModalBody>
{data.rows.map(row => <Edit/>)}
</MDBModalBody>

Related

AgGrid cells don't re-render on refresh

This is the cell renderer for the 2 action buttons I'm passing to AgGrid. When I refresh the page, it loses its state and doesn't re-render them, but hits the function nevertheless.
const columnDefs: ColDef[] = useMemo(() => [
{
field: 'actions',
headerName: t('actions', { ns: 'common' }),
cellRendererSelector: (params: ICellRendererParams<IShop>) => ({
component: ActionsCell,
params: {
...params,
options: {
edit: {
id: 'editShop',
hasPermission: user.permissions.canEditShop,
href: (data: IShop) => appRoutes.editShop(router.query.companyId as string, data.id as string)
},
delete: {
id: 'deleteShop',
hasPermission: user.permissions.canDeleteShop,
onDeleteClick: (data: IShop) => handleClickOpen(data),
},
}
}
}),
cellStyle: agGridStyles.actionsCell
},
]
My AgGrid invocation:
<AgGridReact<IShop>
sideBar={sidebarConfig}
ref={gridRef}
containerStyle={agGridStyles.container}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
paginationPageSize={gridPageSize}
onGridReady={onGridReady}
/>
I tried spreading the options, in order to create a new reference to no avail:
options: {
...params.options,
edit: {
//...
}
}
Should I define the options object outside of the cell renderer:
const [options, setOptions] = useState<IOptions>({
edit: {
id: 'editShop',
hasPermission: user.permissions.canEditShop,
href: (data: IShop) => appRoutes.editShop(router.query.companyId as string, data.id as string)
},
delete: {
id: 'deleteShop',
hasPermission: user.permissions.canDeleteShop,
onDeleteClick: (data: IShop) => handleClickOpen(data),
},
});

Why is my input field losing focus when typing a character?

I have a form that have several input fields and for some reason my component y re-rendering everytime y change the value of my input field which produces to the input to lose focus.
ContactForm.js:
const ContactForm = () => {
const [values, setValues ] = useState({
name: '',
lastname: '',
email: '',
confirmEmail: '',
message: ''
});
const inputs = [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name'
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name'
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email'
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email'
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message'
}
]
const handleSubmit = (e) => {
e.preventDefault();
}
MY child component, FormInput.js:
import React from 'react'
import './FormInput.css';
/* import { Input } from '../atoms/Input'; */
const FormInput = (props) => {
const { id, onChange, ...inputProps } = props;
return (
<div className='formInput'>
{/* <label htmlFor="">Username</label> */}
{/* <Input {...inputProps} onChange={onChange}/> */}
<input {...inputProps} onChange={onChange} />
</div>
)
}
export default FormInput
const onChange = (e) => {
setValues({...values, [e.target.name]: e.target.value});
}
console.log(values);
return (
<form className='contactForm' onSubmit={handleSubmit}>
{inputs.map((input) => (
<FormInput
key={input.id}
{...input}
value={values[input.name]}
onChange={onChange}
/>
))}
<SubmitBtn/>
</form>
)
}
So is there a solution for this, so that my input field doesn´t lose focus after re-rendering? Or should i prevent re-rendering?
you have 3 options here.
move the input array outside of the component so that it is always the same on every iteration. But if you are fetching this from the server, that is not possible.
you can use a useMemo hook on the input array and make sure to pass an empty array as a dependency array.
remove the Math.random function and maybe use a unique id from the server or for the time being you can use the array index (even though it is not advisable).
I have created a small POC. if you remove the useMemo, the input(s) will lose their focus on every re-render.
Following is the code:
import * as React from 'react';
import './style.css';
export default function App() {
const inputs = React.useMemo(
() => [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name',
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name',
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email',
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email',
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message',
},
],
[]
);
const [state, setState] = React.useState({
name: '',
email: '',
message: '',
confirmEmail: '',
lastname: '',
});
const handleChange = (e: any) => {
const value = (e.target as HTMLInputElement).value;
const name = (e.target as HTMLInputElement).name;
setState({
...state,
[name]: value,
});
};
const handleSubmit = () => {
console.log('state', state);
};
return (
<div>
{inputs.map((item) => (
<div key={item.id}>
<label>{item.name}: </label>
<input
name={item.name}
onChange={handleChange}
placeholder={item.placeholder}
/>
</div>
))}
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
It's probably because you are calling Math.random in the body of the ContactForm component. You should never call Math.random() during rendering.
In your case, you can probably move the const inputs to outside the component.

My fetch command doesn't work, what did i do wrong?

im trying to fetch customers and trainings by using a fetch request but for some reason it doesn´t print anything in the page. However it is printing those informations in the network console.
import React, { useState, useEffect } from "react";
import Snackbar from '#material-ui/core/Snackbar';
import Addcustomer from "./Addcustomer";
import Addtraining from "./Addtraining";
import Editcustomer from "./Editcustomer";
import { AgGridReact } from'ag-grid-react'
import'ag-grid-community/dist/styles/ag-grid.css'
import'ag-grid-community/dist/styles/ag-theme-material.css';
export default function Customerlist() {
const [customers, setCustomers] = useState([]);
useEffect(() => fetchData(), []);
const fetchData = () => {
fetch('https://customerrest.herokuapp.com/api/customers')
.then(response => response.json())
.then(data => setCustomers(data.content));
};
const deleteCustomer = link => {
if (window.confirm("Are you sure to delete customer?")) {
console.log(link);
fetch(link, { method: "DELETE" })
.then(res => {
fetchData();
if (res.status >= 200 && res.status < 300) {
Snackbar({ message: "Customer deleted successfully" });
} else {
Snackbar({ message: "Error. Try again." });
}
})
.catch(err => console.error(err));
}
};
const saveCustomer = customer => {
fetch('https://customerrest.herokuapp.com/api/customers', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(customer)
})
.then(res => {
fetchData();
if (res.status >= 200 && res.status < 300) {
Snackbar({ message: "Customer added successfully" });
} else {
Snackbar({ message: "Error. Try again." });
}
})
.catch(err => console.error(err));
};
const saveTraining = training => {
fetch('https://customerrest.herokuapp.com/api/trainings', {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(training)
})
.then(res => {
fetchData();
if (res.status >= 200 && res.status < 300) {
Snackbar({ message: "Training added successfully" });
} else {
Snackbar({ message: "Error. Try again." });
}
})
.catch(err => console.error(err));
};
const updateCustomer = (customer, link) => {
fetch(link, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(customer)
})
.then(res => fetchData())
.then(Snackbar({ message: "Customer updated successfully" }))
.catch(err => console.error(err));
};
const columns = [
{
title: "Edit",
field: "links[0].href",
render: customerData => (
<Editcustomer updateCustomer={updateCustomer} customer={customerData} />
),
sorting: false
},
{
Header: "First name",
accessor: "firstname"
},
{
Header: "Last name",
accessor: "lastname"
},
{
Header: "Email",
accessor: "email"
},
{
Header: "Phone",
accessor: "phone"
},
{
Header: "Address",
accessor: "streetaddress"
},
{
Header: "Postcode",
accessor: "postcode"
},
{
Header: "City",
accessor: "city"
},
{
title: "Delete",
field: "links[0].href",
render: customerData => (
<button
style={{ cursor: "pointer" }}
onClick={() => deleteCustomer(customerData.links[0].href)}
>Delete</button>
),
sorting: false
},
{
title: "Add training",
render: trainingRow => (
<Addtraining
saveTraining={saveTraining}
customerId={trainingRow.links[0].href}
/>
),
sorting: false
}
];
return (
<div>
<Addcustomer saveCustomer={saveCustomer} />
<AgGridReact
title="Customers"
rowData={customers}
columns={columns}
options={{ sorting: true }}
></AgGridReact>
</div>
);
}
i have multiple fetch requests for training and customers but its not working
it shows the information in the console but it doesn't show them in the page. I would like to see all the information in my page, so what i have to do or what did i do wrong here?
The props passed to AgGridReact component are not the right type.
columnDefs prop is used to declare the column headers and should look like:
const columnDefs = [
{ headerName: 'Customers',
children: [
{
headerName: 'Edit',
valueGetter: (params) => params.data.links[0].href,
cellRenderer: (params) => <Editcustomer updateCustomer={updateCustomer} customer={params.data} />,
sortable: false,
},
{
headerName: 'First name',
field: 'firstname',
},
{
headerName: 'Last name',
field: 'lastname',
},
{
headerName: 'Email',
field: 'email',
},
{
headerName: 'Phone',
field: 'phone',
},
{
headerName: 'Address',
field: 'streetaddress',
},
{
headerName: 'Postcode',
field: 'postcode',
},
{
headerName: 'City',
field: 'city',
},
{
headerName: 'Delete',
valueGetter: (params) => params.data.links[0].href,
cellRenderer: (params) => (
<button style={{ cursor: 'pointer' }} onClick={() => deleteCustomer(params.data.links[0].href)}>
Delete
</button>
),
sortable: false,
},
{
headerName: 'Add training',
valueGetter: (params) => params.data.links[0].href,
cellRenderer: (params) => (
<Addtraining
saveTraining={saveTraining}
customerId={params.data.links[0].href}
/>
),
sortable: false,
},
]
}];
defaultColDef prop is used to declare defaults for the column headers.
const defaultColDef={ sortable: true }
Your implementation of the AgGridReact element should be:
<AgGridReact
rowData={customers}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
></AgGridReact>
Finally, you need to either set the domLayout prop so that the grid size is autocomputed (ideally for very small datasets).
<AgGridReact
rowData={customers}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
domLayout='autoHeight'
></AgGridReact>
Otherwise, you can set the size of the container element so that the grid size is computed from it.
<div
style={{
height: '500px',
width: '600px',
}}
>
<AgGridReact
rowData={customers}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
></AgGridReact>
</div>
you need to conditionaly render the information when the data is ready and you need to map on every object
const [customers, setCustomers] = useState([]);
useEffect(() => fetchData(), []);
return (
<>
{customers && customers.map((obj, i) => (
<div key={i}>
<p>{obj.firstname}</p>
<p>{obj.lastname}</p>
</div>
)}
</>
)
your displaying data is more complicated because you use other components "Addcustomer", "AgGridReact" but im just showing how to basically get the data into html

React: Issue with fetching and updating the state in useCallback

I am currently working on a component that makes an API call, retrieves the data, and then displays the data in the Fluent UI Datalist.
The issue is as follows:
The component loads for the first time, then it re-renders after the API call, and the component shows the correct entries within the table with the state.items being set to correct value. However, when I click on column to run the onColumnClick the items inside the function are empty, which result in an error. The columns are fine, but the state.items is just an empty collection.
How can this possibly be fixed to so that I see the items within the onColumnClick?
Here is a piece of code:
export const ListComponent = (props: ListComponentProps) => {
const fetchPeople = async () => {
const entry: ITableEntry[] = [];
//items ... sdk call
for await (const item of items) {
entry.push({
key: item.id,
name: item.name,
lastName: item.lastname
});
}
}
useEffect(() => {
fetchPeople();
.then(elementList => {
setState(
state => ({ ...state, items: elementList }),
);
});
}, [])
const onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
// PLACE WHERE THE ERROR HAPPENS
console.log(items);
}, []);
const columns: IColumn[] = [
{
key: 'column1',
name: 'First Name',
fieldName: 'name',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
{
key: 'column2',
name: 'Last Name',
fieldName: 'lastname',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
];
const [state, setState] = React.useState({
items: [] as ITableEntry[],
columns: columns,
});
return (
<>
<DetailsList
items={state.items}
columns={state.columns}
/>
</>
);
});
const onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
// PLACE WHERE THE ERROR HAPPENS
console.log(items);
}, [state]);
add dependency to the use callback to be recalculated when state changes
This is a total rewrite with some notes
import React, {useCallback, useEffect, useState} from "react";
/** Option One if the function does not requires variables from the component
* itself you can put it outside like in "api" folder */
const fetchPeople = async () => {
//items ... sdk call
// if items are already calculated and they are not async
return items.map((item)=>({
key: item.id,
name: item.name,
lastName: item.lastname
}))
// else
// return (await Promise.all(items)).map((item)=>({
// key: item.id,
// name: item.name,
// lastName: item.lastname
// }))
}
export const ListComponent = (props: ListComponentProps) => {
const [items, setItems] = useState<ITableEntry[]>([])
// Option Two: use callback this function is "saved" inside a variable with a memoization based on the
// elements inside the array at the end
// const fetchPeople = useCallback(async () => {
// ...
// }, [])
useEffect(() => {
// option three you can also leave it there so it can be used in other part of the application
// const fetchPeople = async () => {
// ...
// }
// if you like async await toy can run this
(async () => {
setItems(await fetchPeople())
})()
/** if this is not modifiable you don't need to put it there
* and this function will run after the component is "mount"
* in my case fetch people will not change and that is why you should use useCallback
*/
}, [fetchPeople]);
const onColumnClick = useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
console.log(items);
}, [items]);
const columns = [
{
key: 'column1',
name: 'First Name',
fieldName: 'name',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
{
key: 'column2',
name: 'Last Name',
fieldName: 'lastname',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
]
return (
<>
<DetailsList
items={items}
columns={columns}
/>
</>
);
});
keep variables as simple as possible and unless something strange is required just save "datas" in State
Here is a fix that actually makes this work!
So I actually found a similar post to my issue (although I have searched for it for ages before):
React - function does not print current states
However, the solution had to be modified to this to reflect the changes in the columns.
The solution always also refreshes columns upon changes to items (see useEffects, where I set the columns), so the columns are being updated.
export const ListComponent = (props: ListComponentProps) => {
const [state, setState] = React.useState({
items: [] as IDocument[],
columns: [] as IColumn[],
});
const fetchPeople = React.useCallback(async () => {
const entry: ITableEntry[] = [];
//items ... sdk call
for await (const item of items) {
entry.push({
key: item.id,
name: item.name,
lastName: item.lastname
});
}
setState((state) => ({ ...state, items: elementsList }));
}, []);
useEffect(() => {
setState((state) => ({ ...state, columns: columns }));
}, [state.items]);
useEffect(() => {
fetchPeople();
}, []);
const _onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
console.log(items);
}, [state.items, state.columns]);
const columns: IColumn[] = [
{
key: 'column1',
name: 'First Name',
fieldName: 'name',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: _onColumnClick,
data: 'string',
isPadded: true,
},
{
key: 'column2',
name: 'Last Name',
fieldName: 'lastname',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: _onColumnClick,
data: 'string',
isPadded: true,
},
];
return (
<>
<DetailsList
items={state.items}
columns={state.columns}
/>
</>
);
});

How to add button to material ui table at the bottom?

This is the code for the material ui in react typescript. I am trying to add a button at the bottom which when clicked leads to a form, but after trying many things, I still dont know how to do that?
I just need a simple button at the bottom, any help is appreciated
This is the code from the website
import React from 'react';
import MaterialTable, { Column } from 'material-table';
interface Row {
name: string;
surname: string;
birthYear: number;
birthCity: number;
}
interface TableState {
columns: Array<Column<Row>>;
data: Row[];
}
export default function MaterialTableDemo() {
const [state, setState] = React.useState<TableState>({
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Surname', field: 'surname' },
{ title: 'Birth Year', field: 'birthYear', type: 'numeric' },
{
title: 'Birth Place',
field: 'birthCity',
lookup: { 34: 'İstanbul', 63: 'Şanlıurfa' },
},
],
data: [
{ name: 'Mehmet', surname: 'Baran', birthYear: 1987, birthCity: 63 },
{
name: 'Zerya Betül',
surname: 'Baran',
birthYear: 2017,
birthCity: 34,
},
],
});
return (
<MaterialTable
title="Editable Example"
columns={state.columns}
data={state.data}
editable={{
onRowAdd: (newData) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
setState((prevState) => {
const data = [...prevState.data];
data.push(newData);
return { ...prevState, data };
});
}, 600);
}),
onRowUpdate: (newData, oldData) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
if (oldData) {
setState((prevState) => {
const data = [...prevState.data];
data[data.indexOf(oldData)] = newData;
return { ...prevState, data };
});
}
}, 600);
}),
onRowDelete: (oldData) =>
new Promise((resolve) => {
setTimeout(() => {
resolve();
setState((prevState) => {
const data = [...prevState.data];
data.splice(data.indexOf(oldData), 1);
return { ...prevState, data };
});
}, 600);
}),
}}
/>
);
}
This is what i want the button to look like:
You can have a component that returns both your MaterialTableDemo and the button.
You can wrap both of them in a div, or use the React.Fragment to inline them.
function TableWithButton() {
return (
<>
<MaterialTableDemo />
<div style={{ width: '100%', textAlign: 'center'}}>
<Button onClick={navigateToForm}>Button</Button>
</div>
</>
);
}
Here is an example

Resources