How to render API data in ReactJs by unique ID? - javascript

I have a page where every data saved in the database is rendering in a table with very limited information, I have an action button (Detail) to view all the information for the particular company.
Code for the table:
const PendingApplication = () => {
let history = useHistory();
const [data, setData] = useState([]);
const handleClick = (location) => {
console.log(location);
history.push(location);
};
useEffect(() => {
axios
.get('http://localhost:5000/api/kam')
.then((response) => {
console.log(response);
setData(response.data.kamData);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="content">
<Table>
<TableHead>
<TableRow>
<TableCell>Ticket No</TableCell>
<TableCell align="right">Category</TableCell>
<TableCell align="right">Sub Category</TableCell>
<TableCell align="right">Request Time & Date</TableCell>
<TableCell align="right">Company Name</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
<TableCell>{item.ticketno}</TableCell>
<TableCell>{item.approvecategory}</TableCell>
<TableCell>{item.subcategory}</TableCell>
<TableCell>{item.date}</TableCell>
<TableCell>{item.companyname}</TableCell>
<TableCell>
<Button color="#71BD44" onClick={() => handleClick('/detail')}>
Detail
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
};
export default PendingApplication;
Here is the code for detail page:
const Details = () => {
const setPopup = useContext(SetPopupContext);
let history = useHistory();
const [data, setData] = useState([]);
const handleClick = (location) => {
console.log(location);
history.push(location);
};
useEffect(() => {
axios
.get('http://localhost:5000/api/kam/:id')
.then((response) => {
console.log(response);
setData(response.data.kamData);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="content">
<Box
sx={{
width: '90%',
padding: '24px 20px', // theme padding
border: '1px solid rgba(0, 0, 0, 0.12)',
borderRadius: 4,
}}
>
<div className="ticket-details">
<h3>TICKET DETAILS</h3>
{data.map((item, index) => (
<TableRow>
<p>Ticket ID: {item.ticketno}</p>
<p>Category: {item.approvecategory}</p>
<p>Category: {item.subcategory}</p>
<p>Category: {item.date}</p>
</TableRow>
))}
</div>
<div className="additional-info">
<h3>ADDITONAL INFO</h3>
{data.map((item, index) => (
<TableRow>
<p>Company Name: {item.companyname}</p>
<p>KCP Name: {item.kcpname}</p>
<p>KCP Contact No: {item.kcpcontact}</p>
<p>KCP NID No: {item.kcpnid}</p>
<p>No of MSISDN: {item.msisdn}</p>
</TableRow>
))}
</div>
</Box>
</div>
);
};
export default Details;
I have created the API for unique ID, Here is the API:
router.get('/kam/:id', (req, res) => {
console.log(req.params.id);
kamForm
.findById(req.params.id)
.then((result) => {
res.status(200).json({
kamData: result,
});
})
.catch((err) => {
console.log(err);
res.status(500).json({
message: err,
});
});
});
After clicking the detail button i want that particular info in detail page, can anyone help me, how to do that?

In your Database, you must have an id column, send that column also with your data into fetch API. In your code, you will get item.id, use that id for the handleClick button.
see below code.
const PendingApplication = () => {
let history = useHistory();
const [data, setData] = useState([]);
const handleClick = (id) => {
console.log(id);
//use id here
history.push(location);
};
useEffect(() => {
axios
.get('http://localhost:5000/api/kam')
.then((response) => {
console.log(response);
setData(response.data.kamData);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
<div className="content">
<Table>
<TableHead>
<TableRow>
<TableCell>Ticket No</TableCell>
<TableCell align="right">Category</TableCell>
<TableCell align="right">Sub Category</TableCell>
<TableCell align="right">Request Time & Date</TableCell>
<TableCell align="right">Company Name</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
<TableCell>{item.ticketno}</TableCell>
<TableCell>{item.approvecategory}</TableCell>
<TableCell>{item.subcategory}</TableCell>
<TableCell>{item.date}</TableCell>
<TableCell>{item.companyname}</TableCell>
<TableCell>
<Button color="#71BD44" onClick={() => handleClick(item.id)}>
Detail
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
);
};
export default PendingApplication;

Related

Uncaught Error: Rendered more hooks than during the previous render. How to fix?

I need to fetch data from API based on key and place the data inside a tablecell. I have tried something like the following but didn't work. It is showing an uncaught error.In that case, I know hooks shouldn't be called inside loops, conditions, or nested functions. Then how I would get the item.id?
Uncaught Error: Rendered more hooks than during the previous render.
My code is:
import React, { useState, useEffect } from 'react';
import {
Table, TableRow, TableCell, TableHead, TableBody,
} from '#mui/material';
import makeStyles from '#mui/styles/makeStyles';
import { useEffectAsync } from '../reactHelper';
import { useTranslation } from '../common/components/LocalizationProvider';
import PageLayout from '../common/components/PageLayout';
import SettingsMenu from './components/SettingsMenu';
import CollectionFab from './components/CollectionFab';
import CollectionActions from './components/CollectionActions';
import TableShimmer from '../common/components/TableShimmer';
const useStyles = makeStyles((theme) => ({
columnAction: {
width: '1%',
paddingRight: theme.spacing(1),
},
}));
const StoppagesPage = () => {
const classes = useStyles();
const t = useTranslation();
const [timestamp, setTimestamp] = useState(Date.now());
const [items, setItems] = useState([]);
const [geofences, setGeofences] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetch('/api/geofences')
.then((response) => response.json())
.then((data) => setGeofences(data))
.catch((error) => {
throw error;
});
}, []);
useEffectAsync(async () => {
setLoading(true);
try {
const response = await fetch('/api/stoppages');
if (response.ok) {
setItems(await response.json());
} else {
throw Error(await response.text());
}
} finally {
setLoading(false);
}
}, [timestamp]);
return (
<PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsStoppages']}>
<Table>
<TableHead>
<TableRow>
<TableCell>{t('settingsStoppage')}</TableCell>
<TableCell>{t('settingsCoordinates')}</TableCell>
<TableCell>{t('sharedRoutes')}</TableCell>
<TableCell className={classes.columnAction} />
</TableRow>
</TableHead>
<TableBody>
{!loading ? items.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{`Latitude: ${item.latitude}, Longitude: ${item.longitude}`}</TableCell>
<TableCell>
{
geofences.map((geofence) => geofence.name).join(', ')
}
</TableCell>
<TableCell className={classes.columnAction} padding="none">
<CollectionActions itemId={item.id} editPath="/settings/stoppage" endpoint="stoppages" setTimestamp={setTimestamp} />
</TableCell>
</TableRow>
)) : (<TableShimmer columns={2} endAction />)}
</TableBody>
</Table>
<CollectionFab editPath="/settings/stoppage" />
</PageLayout>
);
};
export default StoppagesPage;
Refactor the mapped JSX into an actual React component so it can use the useEffect hook (and all other React hooks).
Example:
const Item = ({ item }) => {
const [newItems, setNewItems] = useState([]);
useEffect(() => {
fetch(`/api/newItems?newItemId=${item.id}`)
.then((response) => response.json())
.then((data) => setNewItems(data))
.catch((error) => {
throw error;
});
}, []);
return (
<TableRow>
<TableCell>{item.name}</TableCell>
<TableCell>{item.latitude}</TableCell>
<TableCell>{item.longitude}</TableCell>
<TableCell>
{newItems.map((newItem) => newItem.name).join(", ")}
</TableCell>
<TableRow/>
);
};
...
const StoppagesPage = () => {
...
return (
<PageLayout
menu={<SettingsMenu />}
breadcrumbs={['settingsTitle', 'settingsStoppages']}
>
<Table>
<TableHead>
<TableRow>
<TableCell>{t('settingsStoppage')}</TableCell>
<TableCell>{t('settingsCoordinates')}</TableCell>
<TableCell>{t('sharedRoutes')}</TableCell>
<TableCell className={classes.columnAction} />
</TableRow>
</TableHead>
<TableBody>
{loading
? <TableShimmer columns={2} endAction />
: items.map((item) => <Item key={item.id} item={item} />)
}
</TableBody>
</Table>
<CollectionFab editPath="/settings/stoppage" />
</PageLayout>
);
};
But I need to place data inside a table and render them as well. My question was simple. Since I can't fetch data inside the JSX, On the other hand I need item.id to fetch. So how would I fetch data by item.id and render it inside the table cell?
Example:
{!loading ? items.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{`Latitude: ${item.latitude}, Longitude: ${item.longitude}`}</TableCell>
<TableCell>
{
# need to fetch and render data here
geofences.map((geofence) => geofence.name).join(', ')
}
</TableCell>
<TableCell className={classes.columnAction} padding="none">
<CollectionActions itemId={item.id} editPath="/settings/stoppage" endpoint="stoppages" setTimestamp={setTimestamp} />
</TableCell>
</TableRow>
)) : (<TableShimmer columns={2} endAction />)}

How to update Api without making an infinite loop?

I want to update and display Api value whenever new POST request is create, without refreshing the page.
Here is the problem, In my React hook implementation when I make an API call it create's an infinite Api call to the server. I just want it to make a call when new POST request is added.
What I'm doing wrong and How to fix it?
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = () => {
fetch('http://127.0.0.1:8000/main/api/')
.then(response => response.json())
.then((res) => {
const data = res.map((item, index) => ({
title: item.title,
price: item.price,
quantity: item.quantity,
}));
setData(data);
});
};
console.log(data)
fetchData();
},[data]);
Full Code App.js
export default function OrderPreview(props) {
const classes = useStyles();
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = () => {
fetch('http://127.0.0.1:8000/main/api/')
.then(response => response.json())
.then((res) => {
const data = res.map((item, index) => ({
title: item.title,
price: item.price,
quantity: item.quantity,
}));
setData(data);
});
};
console.log(data)
fetchData();
},[data]);
return (
<main className={clsx(classes.content, {
[classes.contentShift]: props.openstate, })}>
<Grid container >
<Grid item sm={12} >
<TableContainer className={classes.tableStyle}>
<Table>
<TableHead>
<TableRow>
<TableCell align='center' >#</TableCell>
<TableCell style={{paddingLeft:'90px'}} >Name</TableCell>
<TableCell align='center' >Quantity</TableCell>
<TableCell align='center' >Price</TableCell>
</TableRow>
</TableHead>
<TableBody >
{data.map((item, index) => (
<TableRow key={index}>
<TableCell align='center' >{index + 1}</TableCell>
<TableCell style={{paddingLeft:'90px'}} >{item.title}</TableCell>
<TableCell align='center' >{item.quantity}</TableCell>
<TableCell align='center' >$ {item.price * item.quantity}</TableCell>
</TableRow>
))}
<TableCell colSpan={3} align='right' style={{paddingRight:'20px'}}> Total Price</TableCell>
<TableCell align='center' > non </TableCell>
</TableBody>
</Table>
</TableContainer>
</Grid>
</Grid>
</main>
);
}
If you don't want to keep fetching API, just remove data in the dependency array.
You are having setData(data); inside your useEffect so it will just keep running re-render over again result in useEffect and setData(data).
useEffect(() => {
const fetchData = () => {
fetch("http://127.0.0.1:8000/main/api/")
.then((response) => response.json())
.then((res) => {
const data = res.map((item, index) => ({
title: item.title,
price: item.price,
quantity: item.quantity,
}));
setData(data);
});
};
console.log(data);
fetchData();
}, []); // <-- REMOVE DATA HERE

Selection Checkbox in React using Hooks

I have a problem selecting a single checkbox or multiple checkbox in a table in React. I'm using Material-UI. Please see my codesandbox here
CLICK HERE
I wanted to achieve something like this in the picture below:
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={false}
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
{head.map((el) => (
<TableCell key={el} align="left">
{el}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{body?.excluded_persons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox checked={true} />
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
Seems you are just missing local component state to track the checked status of each checkbox, including the checkbox in the table header.
Here is the implementation for the AddedPersons component since it's more interesting because it has more than one row of data.
Create state to hold the selected persons state. Only add the additional local state, no need to duplicate the passed body prop data (this is anti-pattern anyway) nor add any derived state, i.e. is indeterminate or is all selected (also anti-pattern).
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
Create handlers to toggle the states.
const toggleAllSelected = () => setAllSelected((t) => !t);
const toggleSelected = (id) => () => {
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Use a useEffect hook to toggle all the selected users when the allSelected state is updated.
React.useEffect(() => {
body.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: allSelected
}),
{}
)
);
}, [allSelected, body]);
Compute the selected person count to determine if all users are selected manually or if it is "indeterminate".
const selectedCount = Object.values(selected).filter(Boolean).length;
const isAllSelected = selectedCount === body?.persons?.added_persons?.length;
const isIndeterminate =
selectedCount && selectedCount !== body?.persons?.added_persons?.length;
Attach all the state and callback handlers.
return (
<>
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell colSpan={4}>{selectedCount} selected</TableCell>
</TableRow>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={allSelected || isAllSelected} // <-- all selected
onChange={toggleAllSelected} // <-- toggle state
indeterminate={isIndeterminate} // <-- some selected
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
...
</TableRow>
</TableHead>
<TableBody>
{body?.persons?.added_persons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={selected[row.id] || allSelected} // <-- is selected
onChange={toggleSelected(row.id)} // <-- toggle state
/>
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
Update
Seems there was a bug in my first implementation that disallowed manually deselecting people while the select all checkbox was checked. The fix is to move the logic in the useEffect into the toggleAllSelected handler and use the onChange event to toggle all the correct states. Also to add a check to toggleSelected to deselect "select all" when any person checkboxes have been deselected.
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
const toggleAllSelected = (e) => {
const { checked } = e.target;
setAllSelected(checked);
body?.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: checked
}),
{}
)
);
};
const toggleSelected = (id) => (e) => {
if (!e.target.checked) {
setAllSelected(false);
}
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Note: Since both AddedPersons and ExcludedPersons components are basically the same component, i.e. it's a table with same headers and row rendering and selected state, you should refactor these into a single table component and just pass in the row data that is different. This would make your code more DRY.
I have updated your added person table as below,
please note that I am using the component state to update the table state,
const AddedPersons = ({ classes, head, body }) => {
const [addedPersons, setAddedPersons] = useState(
body?.persons?.added_persons.map((person) => ({
...person,
checked: false
}))
);
const [isAllSelected, setAllSelected] = useState(false);
const [isIndeterminate, setIndeterminate] = useState(false);
const onSelectAll = (event) => {
setAllSelected(event.target.checked);
setIndeterminate(false);
setAddedPersons(
addedPersons.map((person) => ({
...person,
checked: event.target.checked
}))
);
};
const onSelect = (event) => {
const index = addedPersons.findIndex(
(person) => person.id === event.target.name
);
// shallow clone
const updatedArray = [...addedPersons];
updatedArray[index].checked = event.target.checked;
setAddedPersons(updatedArray);
// change all select checkbox
if (updatedArray.every((person) => person.checked)) {
setAllSelected(true);
setIndeterminate(false);
} else if (updatedArray.every((person) => !person.checked)) {
setAllSelected(false);
setIndeterminate(false);
} else {
setIndeterminate(true);
}
};
const numSelected = addedPersons.reduce((acc, curr) => {
if (curr.checked) return acc + 1;
return acc;
}, 0);
return (
<>
<Toolbar>
{numSelected > 0 ? (
<Typography color="inherit" variant="subtitle1" component="div">
{numSelected} selected
</Typography>
) : (
<Typography variant="h6" id="tableTitle" component="div">
Added Persons
</Typography>
)}
</Toolbar>
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={isAllSelected}
inputProps={{ "aria-label": "select all desserts" }}
onChange={onSelectAll}
indeterminate={isIndeterminate}
/>
</TableCell>
{head.map((el) => (
<TableCell key={el} align="left">
{el}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{addedPersons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={row.checked}
onChange={onSelect}
name={row.id}
/>
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
};
export default AddedPersons;
Please refer to this for a working example: https://codesandbox.io/s/redux-react-forked-cuy51

Render data as a functional component with React.js and Material-UI

I have a problem with the management of the data, when i try to render some data from the pokemon api my table render multiple times the titles, i tried to move only the data to a different component but not luck.
How can i fix this?
API CAll
export const PokemonApi = () => {
const [poke, setPoke] = useState([]);
const data = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=20').then(( response ) => {
setPoke(response.data.results);
console.log(response.data.results);
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<>
{
poke.map(( info, name ) => {
return <Lista key={ name } info={ info } />
})
}
</>
)
}
component where I try to render
export const Lista = (props) => {
const classes = useStyles();
return (
<div>
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">URL</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow key={ props.info.name }>
<TableCell component="th" scope="row">
{ props.info.name }
</TableCell>
<TableCell align="right">{ props.info.url }</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
)
}
This is the page where i render the table
import React from 'react'
import { PokemonApi } from '../api/PokemonApi'
export const Pokes = () => {
return (
<>
<PokemonApi />
</>
)
}
And here is the table.
I hope anyone can help me!
As your code is written, you are not rendering one table with a row for each line. You are creating one Lista par record, you have as many tables as pokemon.
What you are looking to achieve is more like :
function PokemonRow(props) {
return (
<TableRow key={ props.info.name }>
<TableCell component="th" scope="row">
{ props.info.name }
</TableCell>
<TableCell align="right">{ props.info.url }</TableCell>
</TableRow>
)
}
export const PokemonTable() {
const classes = useStyles();
const [poke, setPoke] = useState([]);
const data = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=20').then(( response ) => {
setPoke(response.data.results);
console.log(response.data.results);
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<div>
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">URL</TableCell>
</TableRow>
</TableHead>
<TableBody>
{poke.map(infos => <PokemonRow info={infos}/>)}
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
)
}

Assign state to dynamically rendered component using map function

I have a table with players that is loaded in from an API call and rendered using a map function. The last column of the table contains a delete button. When clicking the delete button, I would like to disable all the other delete buttons until the delete call is completed.
Here is the function that performs the API call to get the players.
loadPlayers() {
const { cookies } = this.props;
const AuthStr = 'Bearer '.concat(this.state.access_token);
axios.get(
configVars.apiUrl + 'team/get-team',
{ headers: { Authorization: AuthStr } }
).then(response => {
var teamArray = response.data;
//Information gotten
this.setState({
teamArray: teamArray,
});
}).catch((error) => {
//Error, remove the access token from cookies and redirect home.
cookies.remove('access_token');
this.props.history.push("/");
});
}
}
Mapping and rendering is done like this:
<Grid item xs={12}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">Tier</TableCell>
<TableCell align="right">Value</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow>
</TableHead>
{this.state.teamArray.map((player, i) => {
return <TableBody key={i}>
<TableRow key={i}>
<TableCell align="left">{player.full_name}</TableCell>
<TableCell align="right">{player.tier}</TableCell>
<TableCell align="right">{player.value}</TableCell>
<TableCell align="right">
<IconButton disabled={this.state.deleteDisable}
onClick={() => {this.handleDelete(player.id)}} >
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
</TableBody>;
})}
</Table>
</Grid>
Inside the handleDelete function I start by setting deleteDisabledin the state to true. However, this has no effect since disabled is set to false once the table is loaded and never changed after.
How do I make sure this.state.deleteDisable is passed to the button as a variable instead of assigned once?
You should store the players into the state, then in the render method you can display the table
function loadPlayer() {
const { cookies } = this.props;
const AuthStr = 'Bearer '.concat(this.state.access_token);
axios.get(
configVars.apiUrl + 'team/get-team',
{ headers: { Authorization: AuthStr } }
)
.then(response => this.setState({players: response.data})})
.catch((error) => {
// Error ....
});
}
render() {
return (
...
{
this.state.players((player, i) => (
<TableBody key={i}>
<TableRow>
<TableCell align="left">{player.full_name}</TableCell>
<TableCell align="right">{player.tier}</TableCell>
<TableCell align="right">{player.value}</TableCell>
<TableCell align="right">
<IconButton disabled={this.state.deleteDisabled}
onClick={() => {this.handleDelete(player.id)}} >
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
</TableBody>
)}
...
);
}

Categories

Resources