Add loader into the React page till data is loading - reactjs

I have a onclick function to open a accounts information page
Below is My code
Index.tsx in this page i have clicked on the name and then all employees names are open in the another page
<p onClick={() => accountSelectorClick(rowData)}>{rowData.name}</p>
After click on the name the all data is sent to the EmployeesData page via props
<EmployeesData showPopup={showPopup} setPopup={setPopup} employee={employee}/>
account popup is used to show and hide the table from page
Below is the code to show employees data
const EmployeesData = (props) => {
let displayForm = props.showPopup ? "block" : "none";
const [loader, showLoader, hideLoader] = useFullPageLoader();
useEffect(() => {
showLoader();
},[]);
useEffect(() => {
if(props.employee.length !== 0) {
hideLoader();
}
},[props.employee]);
return (
<Container fluid="md" style={{ display: displayForm }}>
<div className="datatable-crud-demo datatable-filter-demo">
<Toast ref={toast} />
<div className="card" >
<div className='account-header'>
<h1>Accounts</h1>
</div>
{loader}
<DataTable ref={dt} value={props.employee} dataKey="id" paginator rows={15} filterDisplay="menu"
responsiveLayout="scroll" size="small">
<Column field="id" align='center' header="Id" sortable style={{ minWidth: '10rem', height:'1rem' }}></Column>
<Column field="name" align='center' header="Name" sortable style={{ minWidth: '10rem', height:'1rem' }}></Column>
</DataTable>
</div>
</div>
</Container>
)
};
EmployeesData.propTypes = {
showPopup: PropTypes.bool,
setPopup: PropTypes.func,
employee : PropTypes.any,
}
const mapStateToProps = (state: AppState) => {
return {}
}
const mapDispatchToProps = (dispatch: any) => {
return {};
};
export default connect(mapStateToProps, mapDispatchToProps)(memo(EmployeesData));
In this page i want to add a loader which can show the loader till all data is render,
I have tried with the useFullPageLoader hooks but it not working as expected,
Need a loader which can start the loading the data immideate after click on the Name button from employee table

Related

useEffect doesn't re-render on state change or infinite looping issue

I have a component which contains a form and a list. When user adds an item to the list though the form, the item should display immediately in the list. I try to use useEffect to fetch data, useEffect without dependency causes an infinite request loop. I added empty array as dependency to prevent looping but in this case new item which is added doesn't display in the list until refreshing the page. How can I solve this issue? (I use antd and antd-form-builder to create the component)
here is my code:
function FieldSetting() {
const [form] = Form.useForm()
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [])
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta} />
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting
Just add a state that will refretch (trigger useEffect) after you have submitted the form. Be aware that it will refetch all the data from the API. This might bring scalability issues when the data grows.
function FieldSetting() {
const [form] = Form.useForm()
const [refetch, setRefetch] = useState(false) // <----- add this state
const [typeValue, setTypeValue] = useState()
const meta = {
fields: [{ key: "pathname", onChange: (e) => setTypeValue(e.target.value) }],
}
const [data, setData] = useState([])
async function onFinish() {
try {
await axios.post("api", { typeValue, typeId })
form.resetFields()
setRefetch(!refetch) // <----- set the refetch to change the state
} catch (e) {
console.log(e)
}
}
useEffect(() => {
const getData = async () => {
const response = await fetch(`api?id=${typeId}`)
const newData = await response.json()
setData(newData)
}
getData()
}, [refetch]) // <----- add the refetch here to trigger the effect
return (
<Container>
<Form form={form} layout="inline" className="form-field" onFinish={onFinish}>
<FormBuilder form={form} meta={meta}
/>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
<div
id="scrollableDiv"
style={{
height: 665,
overflow: "auto",
padding: "0 16px",
border: "1px solid rgba(140, 140, 140, 0.35)",
}}
>
<List
itemLayout="horizontal"
dataSource={data}
renderItem={(item) => (
<List.Item
actions={[
<a key="list-edit">edit</a>,
<a onClick={() => axios.delete(`http://gage.axaneh.com/api/Gages/SettingProduct/RemoveProductSetting/${item.id}`, item)} key="list-delete">
delete
</a>,
]}
>
<List.Item.Meta title={item.typeValue} />
</List.Item>
)}
/>
</div>
</Container>
)
}
export default FieldSetting```
Whenever you manipulate your array just add a dummy state and change it
add this state
const [extra, setExtra] = useState(0)
when you change the state of your array like add or remove just add this line below
setExtra(extra+1)
what happens is that adding or removing data in an array don't count as a state change in react as per my understanding it need to be something different like true to false or in this case 0 to 1

Parsing in props value from parent to component and display

Still new to ReactJS.
I have 3 JS pages: Home, Create, Edit.
From Home, you can navigate to the Create and Edit pages.
You can access the Create page anytime, but you need to call an API to populate some data before you can access the Edit page.
All 3 pages are using the same component, FormEntry. As its name, it generates basically a form input. Within this component, there are 2 functions: Search and AddEdit. Home is using the former, Create and Edit are using the later.
The flow is as such where when you click on the Create button, this will direct you to the Create page. The Create page will then display the form.
However, if you click on the Search button, this will call an API and generate data in a table. Each table row is clickable and clicking on them will direct you to the Edit page. For reusability, I parse in some values using props that, in theory, should populate the form fields based on which row I clicked on.
The issue I'm having is that though the value gets parsed in, the form field is not displaying the correct data. To be specific, the data from the responseData I parsed into the component is not displaying. And even if it does display, it's returning as 'undefined'.
What am I doing wrong?
Home.js
function HomePage() {
const [responseData, setData] = useState([]);
const navigateData = useNavigate();
function navigateToEdit(event){
navigateData({insert URL here}+event.id);
}
function getSearchData2(allData){
( allData.propRefNum !== "" ||
allData.client !== "" ||
allData.appSys !== "" ||
allData.status !== "" ? AxiosCall.searchProposal(JSON.stringify(allData)): AxiosCall.getProposals()
)
.then(
(result) => {
setData(result.data);
}
);
}
return (
<>
<div style={{ height: 400, width: '100%' }}>
<div style={{ display: 'flex', height: '100%' }}>
<div style={{ flexGrow: 1 }}>
<DataGrid onRowClick={navigateToEdit} rows={dataRowObjs} columns={dataColObjs} headerAlign="center" disableColumnFilter />
</div>
</div>
</div>
</>
);
}
export default HomePage;
Edit.js
function EditPage() {
const [responseData, setData] = useState([]);
const { id } = useParams();
useEffect(() => {
const apiData = {
id: id
}
AxiosCall.getProposal(JSON.stringify(apiData))
.then(
(result) => {
setData(result.data);
}
);
},[]);
function getEditData(allData){
fetch({insert URL here}).then(
(result) => {
setData(result);
});
}
return <FormEntry.AddEditFormEntry title="Edit Proposal" defaultDataValue={responseData} responseInputData={getEditData} />
}
export default EditPage;
FormEntry component; AddEditForm
function AddEditFormEntry(props){
const propRefNumRef = useRef();
const descRef = useRef();
const clientRef = useRef();
const appSysRef = useRef();
const statusRef = useRef();
const remarkRef = useRef();
const vendorRef = useRef();
const { register, formState: { errors }, handleSubmit } = useForm();
function submitData(data){
//event.preventDefault();
const propRefNum = propRefNumRef.current.value;
const desc = descRef.current.value;
const client = clientRef.current.value;
const appSys = appSysRef.current.value;
const status = statusRef.current.value;
const remark = remarkRef.current.value;
const vendor = vendorRef.current.value;
const allData = {
propRefNum: propRefNum,
desc: desc,
client: client,
appSys: appSys,
status: status,
remark: remark,
vendor: vendor,
}
props.responseInputData(allData);
}
let defaultRefNum = props.defaultDataValue?.refNum; - **Note A: this is the line in question. When I console.log this variable, it displays the data correctly**
return(
<>
<form className="formEntry" onSubmit={handleSubmit(submitData)}>
<div style={{ display: 'flex'}} >
<div align="left" >
<RouterLink to={insert URL here} >
<IconButton aria-label="search" color="primary" >
<SkipPreviousIcon />
</IconButton>
</RouterLink >
</div>
<h1 style={{ flexGrow: 1, marginTop: -4 }} >{props.title}</h1>
<div align="right">
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
<IconButton aria-label="search" color="primary" type="submit" >
<SaveIcon />
</IconButton>
</div>
</div>
<br/>
<Stack justifyContent="center" direction="row" spacing={2} >
<Stack justifyContent="center" direction="column" spacing={2} >
<FieldEntry.TextEntry required="true" label="Proposal Reference Number" type="text" id="input_propRefNum" name="propRefNum" propsRef={propRefNumRef} value={defaultRefNum} />
**Referring to Note A above, I want to populate this field above. I am getting 'undefined', if not blank. If I am using the Create function, blank/undefined is expected. The Edit function is supposed to populate something here **
<FieldEntry.TextEntry label="Description" type="text" id="input_desc" name="desc" propsRef={descRef} value={props.defaultDataValue?.description} />
<FieldEntry.TextEntry required="true" label="Client" type="text" id="input_client" name="client" propsRef={clientRef} />
<FieldEntry.TextEntry required="true" label="Application System" type="text" id="input_appSys" name="appSys" propsRef={appSysRef} value={props.defaultDataValue?.appSystem} />
</Stack>
</Stack>
<br/>
</form>
</>
)
}
Note: I've removed a number of codes that does not pertain to the matter, to keep the sample code small. Rest assured that aside from my issue, everything is working as expected
I think a simpler example(which I just found out will have the same issue) would be
const [defaultRefNum, setRefNum] = useState("");
const [counter, setCounter] = useState(0);
let testValue = props.defaultDataValue?.refNum
useEffect(() => {
console.log("testValue2:",testValue)
if (props.defaultDataValue != null){
console.log("is not Null")
setCounter(c => c+1);
}
else{
console.log("is Null")
}
},[testValue]);
console.log("counter:",counter)
and
<FieldEntry.TextEntry value={counter} />
console.log output - counter: 2
Value in TextEntry: 0
I would assume the value in TextEntry should've outputted to be 2.

Popover with id data in list (React, Material UI)

I have a table list with first name, last name and email. The email section is a clickable popover SimplePopper:
<TableCell>{list.first_name}</TableCell>
<TableCell>{list.last_name}</TableCell>
<TableCell><SimplePopper /></TableCell>
I'd like the popover to only display the email related to the id at the row level, but at the moment it displays all emails. Is there a simple way to pass something on <SimplePopper /> so I see the related emails to each first and last name?
SimplePopper calls this function:
function PostPopover() {
return (
<div>
{PostData.map((list, index)=>{
return <div>
{list.email}
</div>
})}
</div>
)
}
Edit: added SimplePopper code
function PostPopover() {
return (
<div>
{PostData.map((list, index)=>{
return <div>
{list.email}
</div>
})}
</div>
)
}
const useStyles = makeStyles((theme) => ({
paper: {
border: '1px solid',
padding: theme.spacing(1),
backgroundColor: theme.palette.background.paper,
},
}));
export default function SimplePopper() {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(anchorEl ? null : event.currentTarget);
};
const open = Boolean(anchorEl);
const id = open ? 'simple-popper' : undefined;
return (
<div>
<button aria-describedby={id} type="button" onClick={handleClick} color="primary">
Show email clicking here
</button>
<Popper id={id} open={open} anchorEl={anchorEl}>
<div className={classes.paper}><PostPopover />.</div>
</Popper>
</div>
);
}
Edit: PostData structure (1,000 fake records)
[{"id":1,
"first_name":"Geraldine",
"last_name":"Graal",
"email":"ggraal0#indiegogo.com"},
{"id":2,
"first_name":"Farris",
"last_name":"Sowten",
"email":"fsowten1#blogger.com"}]

How to manage react state for a list of JSX.Elements correctly

I am using react-hooks to manage a list of JSX.Elements. However, once the element changed, trying to delete it will cause unexpected behavior.
I had tried using useReducer, remove by index etc, still unexpected updated result occurred.
FolderPage.tsx
import React, { useState, useEffect } from 'react';
import { Button, Box, Grid } from 'grommet';
import { Add, Close } from 'grommet-icons';
import { Files } from '../Component/Files/Files';
import { ePub } from '../extension/ePub/ePub';
interface Props {}
export const FolderPage: React.FC<Props> = () => {
const [state, setState] = useState([<Files openFileHandlers={[ePub]} />]);
const newFolderPanel = () => setState(prev => prev.concat(<Files openFileHandlers={[ePub]} />));
const removePanel = (panel: JSX.Element) => setState(prevState => prevState.filter(s => s !== panel));
return (
<div>
<Box align="start" pad="xsmall">
<Button icon={<Add />} label="New Folder Panel" onClick={newFolderPanel} primary />
</Box>
{state.map((s, index) => (
<Grid key={index}>
<Box align="end">
<Button
icon={<Close color="white" style={{ backgroundColor: 'red', borderRadius: '50%', padding: '.25rem' }} />}
type="button"
onClick={() => removePanel(s)}
/>
</Box>
{s}
</Grid>
))}
</div>
);
};
For example, in usage:
What should I change my code so my delete click will delete the matched element?
There is a way to work around it. For each item inside the array. Instead of storing item directly, stored it as {id: yourAssignedNumber, content: item}.
In this way, you could have control of the id, and remove by comparing the id only. This way, it will work correctly.
import React, { useState, useRef } from 'react';
import { Button, Row, Col } from 'antd';
import { Files } from '../Components/Files/Files';
import { fileHandler } from '../model/fileHandler';
interface Props {
fileHandlers?: fileHandler[];
}
export const FolderPage: React.FC<Props> = ({ fileHandlers }) => {
const [state, setState] = useState([{ key: -1, content: <Files fileHandlers={fileHandlers} /> }]);
const key = useRef(0);
const newFolderPanel = () =>
setState(prev =>
prev.concat({
key: key.current++,
content: <Files fileHandlers={fileHandlers} />
})
);
const removePanel = (key: number) => setState(prevState => prevState.filter(s => s.key !== key));
return (
<Row>
<Button type="primary" icon="plus" onClick={newFolderPanel} style={{ margin: '.75rem' }}>
New Foldr Panel
</Button>
{state.map(({ key, content }) => (
<Col key={key}>
<div
style={{
background: '#75ff8133',
display: 'grid',
justifyItems: 'end',
padding: '.5rem 1rem'
}}>
<Button onClick={() => removePanel(key)} icon="close" type="danger" shape="circle" />
</div>
{content}
</Col>
))}
</Row>
);
};

React - Set loading state only on specific clicked button

Here is my current code
class cart extends Component {
state = { loading: [] };
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading[e] = true;
this.setState({
loading,
})
};
render() {
const { data } = this.props;
return (
<div>
{data.map(catering => {
const { menus } = catering;
return (
<Row>
{menus.map(menu => (
<Col xs={12} md={12} lg={6} key={menu.id} className="m-bottom-15">
<Card style={{ height: '165px', border: 0, overflow: 'hidden' }}>
<CardActions>
<LoadingButton
className="button small primary pull-right"
loading={thi.state.loading || false}
onClick={e => this.addToCart(e, menu.id)}
>
<MdAdd size={18} color={white} />
<span className="font-14 font-400">Add to cart</span>
</LoadingButton>
</CardActions>
</Card>
</Col>
))}
</Row>
);
}
}
There will be around 20 button when the map function is done.
What I want to achieve is: every time users click add to cart button, I will call the ajax to save the cart and show loading for the specific clicked button.
After ajax is done, return the state back to normal.
On my current code I haven't put my ajax call yet, I still want to make sure the loading button work on press. Right now its not working.
How can I achieve this?
Thanks.
There is some error in your addToCart method. You should use id as index of loading and set loading array to state as this:
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading[id] = true;
this.setState({
loading: loading
});
};
Also, in your render method, change this.state.loading to this.state.loading[menu.id]:
<LoadingButton
className="button small primary pull-right"
loading={this.state.loading[menu.id] || false}
onClick={e => this.addToCart(e, menu.id)}
>
When ajax call is done, you just call setState function which sets loading array values to false in callback method.
Instead of maintaining bool in loading array, maintain the menu ids, whenever Add button is clicked, push the id of that item into loading array. When generating the card check whether loading array have that id or not, if yes then show loading otherwise show the card.
Write it like this:
class cart extends Component {
state = { loading: []};
addToCart = (e, id) => {
let loading = this.state.loading.slice();
loading.push(id);
this.setState({
loading
})
};
render() {
const { data } = this.props;
return (
<div>
{data.map((catering,i) => {
const { menus } = catering;
return (
<Row>
{menus.map(menu => {
return this.state.loading.indexOf(menu.id) >= 0 ?
<Col xs={12} md={12} lg={6} key={menu.id} className="m-bottom-15">
<Card style={{ height: '165px', border: 0, overflow: 'hidden' }}>
<CardActions>
<LoadingButton
className="button small primary pull-right"
loading={thi.state.loading || false}
onClick={e => this.addToCart(e, menu.id)}
>
<MdAdd size={18} color={white} />
<span className="font-14 font-400">Add to cart</span>
</LoadingButton>
</CardActions>
</Card>
</Col>
:
/*Loading icon*/
})}
</Row>
)
})}
)
}
}
for the hooks guys this is what I did
const [isLoading, setIsLoading] = useState([]);
<button
type="button"
disabled={isLoading[item.username]}
className="btn btn-success btn-rounded btn-sm my-0"
onClick={()=>activateOrDeactivate( item.activated,item.username)}
> {isLoading[item.username] && <i className="fa fa-circle-o-notch fa-spin"></i>}
{isLoading[item.username]&& <span> Activating...</span>}
{!isLoading[item.username]&& <span>Activate</span>}</button>
const activateOrDeactivate = (valTrueOrFalse,newusername) => {
let loading = isLoading.slice();
loading[newusername] = true;
setIsLoading(loading)
let config = {
headers: {
Authorization: "Bearer " + state.token
},
};
api.post(
"process-activation",
{
activated: !valTrueOrFalse,
username:newusername
},
config
).then(
response => {
getAllUsers()
let loading = isLoading.slice();
loading[newusername] = false;
setIsLoading(loading)

Resources