react hooks not setting the select value after fetching options - reactjs

I am fetching list of testsuites using api call on component mount. Api returns list in chronological order.
Setting them as options for a select dropdown(Material-UI).
Then set the selected option to latest testSuite and using its Id get the corresponding testSuite data.
Data is retrieved successfully and pie chart is getting displayed.
Api calls are working fine and React dev tools shows the selectedTestSuite value to be set correctly.But DOM doesn't show the selection in the select dropdown.
Can someone please advise what is the mistake I am doing in this code? Thanks in advance.
import clsx from 'clsx';
import PropTypes from 'prop-types';
import { Doughnut } from 'react-chartjs-2';
import { makeStyles } from '#material-ui/styles';
import axios from 'axios';
import { useSpring, animated } from 'react-spring';
import '../../Dashboard.css';
import MenuItem from '#material-ui/core/MenuItem';
import {
Card,
CardHeader,
CardContent,
Divider,
TextField,
} from '#material-ui/core';
import CircularProgress from '#material-ui/core/CircularProgress';
const useStyles = makeStyles(() => ({
circularloader: {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
},
actions: {
justifyContent: 'flex-end',
},
inputField: {
width: '150px',
},
}));
const TestSuiteVsScanCount = (props) => {
const { className, ...rest } = props;
const classes = useStyles();
const [doughData, setDoughData] = useState([]);
const [dataLoadedFlag, setDataLoadedFlag] = useState(false);
const [testSuites, setTestSuites] = useState([]);
const [selectedTestSuite, setSelectedTestSuite] = useState({});
useEffect(() => {
function getTestSuites() {
axios.get('http://localhost:3000/api/v1/testsuite/12').then((resp) => {
setTestSuites(resp.data.reverse());
});
}
getTestSuites();
}, []);
useEffect(() => {
if (testSuites.length > 0) {
setSelectedTestSuite(() => {
return {
type: testSuites[0].TestSuiteName,
id: testSuites[0].TestSuiteId,
};
});
}
}, [testSuites]);
useEffect(() => {
function getTestSuiteData() {
let doughData = [];
if (selectedTestSuite.id) {
axios
.get(
'http://localhost:3000/api/v1/summary/piechart/12?days=30&testsuiteid=' +
selectedTestSuite.id,
)
.then((resp) => {
resp.data.forEach((test) => {
doughData = [test.TestCount, test.ScanCount];
});
setDoughData({
labels: ['Test Count', 'Scan Count'],
datasets: [
{
data: doughData,
backgroundColor: ['#FF6384', '#36A2EB'],
hoverBackgroundColor: ['#FF6384', '#36A2EB'],
},
],
});
setDataLoadedFlag(true);
});
}
}
getTestSuiteData();
}, [selectedTestSuite]);
const ChangeType = (id) => {
testSuites.forEach((suite) => {
if (suite.TestSuiteId === id) {
setSelectedTestSuite({
type: suite.TestSuiteName,
id: suite.TestSuiteId,
});
}
});
};
return (
<Card {...rest} className={clsx(classes.root, className)}>
<CardHeader
action={
<TextField
select
label="Select Test Suite"
placeholder="Select Tests"
value={selectedTestSuite.id}
className={classes.inputField}
name="tests"
onChange={(event) => ChangeType(event.target.value)}
variant="outlined"
InputLabelProps={{
shrink: true,
}}
>
{testSuites.map((testSuite) => (
<MenuItem
key={testSuite.TestSuiteId}
value={testSuite.TestSuiteId}
>
{testSuite.TestSuiteName}
</MenuItem>
))}
</TextField>
}
title="Test Suite vs Scan Count"
/>
<Divider />
<CardContent>
<div>
{dataLoadedFlag ? (
<Doughnut data={doughData} />
) : (
<CircularProgress
thickness="1.0"
size={100}
className={classes.circularloader}
/>
)}
</div>
</CardContent>
<Divider />
</Card>
);
};
TestSuiteVsScanCount.propTypes = {
className: PropTypes.string,
};
export default TestSuiteVsScanCount;

I was able to fix this issue with the help of my colleague by setting the initial state of selectedTestSuite to {type:'', id:0} instead of {}.
Changed this
const [selectedTestSuite, setSelectedTestSuite] = useState({});
To this
const [selectedTestSuite, setSelectedTestSuite] = useState({type:'', id:0});
But I am not sure why this worked.

I believe that the main problem is when you pass a value to TextField component with undefined, the TextField component will assume that is an uncontrolled component.
When you set you initial state for selectedTestSuite to be {} the value for selectedTestSuite.id will be undefined. You can find value API reference in https://material-ui.com/api/text-field/

Related

The data is fetched from the backend, but it is not displayed on the interface

I have an ECommerce project, and this project contains the operations of displaying products, creating a product, deleting a product, and displaying a specific product information.
And my problem is only in displaying the data.
Where through the App file, the data is fetched from the backend and passed to the component "Products" and then displayed on the interface.
The problem is that the data was fetched from the backend and the data coming from the backend appeared in the browser and the request succeeded, but the problem is that the data was not displayed on the interface, and the interface was completely empty.
How can I solve this problem?
App.js:
import * as React from "react";
import Navbar from "./Navbar";
import Products from "./Products";
import { getAllProducts } from "../services/ECommerceServices";
const App = () => {
console.log("Hi in App file");
// const [products, setProducts] = React.useState([]);
const getAllProductsFun = () => {
console.log("Hi I am in App file get all products");
getAllProducts().then((products) => {
// console.log(products);
// setProducts(products);
console.log("pppppppppppp: ", products);
return products;
});
};
return (
<>
<Navbar />
<Products getAllProductsFun={getAllProductsFun} />
</>
);
};
export default App;
products.js:
import * as React from "react";
import { styled } from "#mui/material/styles";
import Box from "#mui/material/Box";
import Paper from "#mui/material/Paper";
import Grid from "#mui/material/Grid";
import { makeStyles } from "#mui/styles";
import { Typography } from "#mui/material";
import Head from "next/head";
import useMediaQuery from "#mui/material/useMediaQuery";
import { useTheme } from "#mui/material/styles";
import Dialog from "./Dialog";
import Product from "./Product";
import { getAllProducts } from "../services/ECommerceServices";
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === "dark" ? "#1A2027" : "#fff",
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: "center",
color: theme.palette.text.secondary,
}));
const useStyles = makeStyles({
main: {
padding: "4rem ",
},
typo: {
color: "#ffc400 !impoertant",
},
// button: {
// textTransform: "none !important",
// backgroundColor: "#ffc400 !important",
// color: "white !important",
// padding: 14,
// },
});
function Products({ getAllProductsFun }) {
console.log("check: ", getAllProductsFun);
const theme = useTheme();
const breakpoint = useMediaQuery(theme.breakpoints.down("sm"));
return (
<>
<Head>
<title>Solek</title>
</Head>
<Grid
container
direction={breakpoint ? "column" : "row"}
style={{ margin: "4rem" }}
>
<Grid item xs={12} sm={9} md={9}>
<Typography
variant="h4"
gutterBottom
component="div"
// className={classes.typo}
style={{ fontWeight: 600 }}
>
Our Products
</Typography>
</Grid>
<Grid
item
xs={12}
md={3}
sm={3}
style={{
direction: "row",
justifyContent: "flex-end",
alignItems: "center",
}}
>
<Dialog />
</Grid>
</Grid>
<Box sx={{ flexGrow: 1, margin: 8 }}>
<Grid container spacing={3}>
{getAllProductsFun()?.map((product, index) => (
<Grid item xs={12} sm={6} md={3} key={index}>
<Item>
{" "}
<Product key={product.id} product={product} />;
</Item>
</Grid>
))}
</Grid>
</Box>
</>
);
}
export default Products;
EcommerceServices.js:
import axios from "axios";
// [id]
// get All Products
// export async function getAllProducts() {
// const res = await axios.get("https://fakestoreapi.com/products");
// const products = await res.data;
// console.log("products: ", products);
// return products;
// }
export async function getAllProducts() {
const res = await axios
.get("https://fakestoreapi.com/products")
.then((res) => {
const products = res.data;
console.log("products: ", products);
return products;
});
return res;
}
// get element by ID
export async function getSingleProductRequest(context) {
const id = context.params.id;
const req = await axios.get("https://fakestoreapi.com/products/" + id);
const product = await req.json();
console.log("product: ", product);
return product;
}
// get product by ID
export async function getProductsOneByOne() {
const req = await fetch("https://fakestoreapi.com/products");
const products = await req.json();
const paths = products.map((product) => {
return {
params: {
id: product.id.toString(),
},
};
});
return {
paths,
fallback: false,
};
}
// delete product
export const deleteProduct = async (id) => {
await axios
.delete(`https://fakestoreapi.com/products/${id}`)
.then((res) => {
res.json();
console.log("data: ", res.json());
})
.then((json) => {
console.log("json data: ", json);
})
.catch((err) => console.log("error: ", err));
};
The problem is that your getAllProductsFun api call is asynchronous. So you'll need to save those values and then display it rather than attempting to call it on render.
In your App.js you can create a state to store the fetched products and call the function on your app mounting like this:
const App = () => {
const [products, setProducts] = React.useState([]) // defaults to empty array
...
React.useEffect(() => {
getAllProducts().then(response => setProducts(response))
}, []) // empty deps array means it will only call once on mount
return (
<>
<Navbar />
<Products products={products} />
</>
);
}
Now you can render the results in Products like this:
function Products({ products }) {
...
return (
...
{products.map(...)}
...
)
}

Jest: Cannot read property 'secondary' of undefined - after MUI upgrade from V4 toV5

We upgraded MUI from v4 to v5 and we have UI tests which started failing. Error is:
TypeError: Cannot read property 'secondary' of undefined (I added comment to which line in code this refers)
Test example:
describe('<AnonDragNDropFileUpload />', () => {
it('should render', () => {
const blob = () => {
return new File(['Test string'], 'Test file.txt');
};
const fileSet: AnonFileSet = {
originalFile: { get: blob, set: () => undefined },
compressedFile: { get: () => undefined, set: () => undefined },
};
const result = render(<AnonDragNDropFileUpload fileSet={fileSet} downloadFileAction={jest.fn()} clearFileAction={jest.fn()} />);
expect(result).toBeTruthy();
});
});
Code:
import { Paper } from '#mui/material';
import { green, red } from '#mui/material/colors';
import { lighten, Theme } from '#mui/material/styles';
import makeStyles from '#mui/styles/makeStyles';
import { JobInputFileTypeEnum } from 'app/api';
import React, { useCallback, useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { AnonFileSet } from '.';
const useDropZoneStyles = makeStyles((theme: Theme) => ({
dragndropZone: {
backgroundColor: lighten(theme.palette.secondary.light, 0.8), // <-- this line fails
width: '100%',
},
info: {
backgroundColor: green[100],
width: '100%',
},
}));
interface Props {
fileSet: AnonFileSet;
clearFileAction: (fileType?: JobInputFileTypeEnum) => void;
downloadFileAction: () => void;
}
export const AnonDragNDropFileUpload: React.FC<Props> = ({ fileSet, clearFileAction, downloadFileAction }) => {
const classes = useDropZoneStyles();
const [fileLabel, setFileLabel] = useState('');
const onDrop = useCallback(async (acceptedFiles: File[]) => {
setFileLabel(fileSet.originalFile.get()?.name ?? '');
fileSet.originalFile.set(acceptedFiles[0]);
}, []);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ onDrop, multiple: false, accept: '.csv' });
const { ref, ...rootProps } = getRootProps();
const handleDeleteFile = () => {
acceptedFiles.splice(
acceptedFiles.findIndex((x) => x.name === fileSet.originalFile.get()?.name),
1,
);
clearFileAction();
};
useEffect(() => {
setFileLabel(fileSet.originalFile.get()?.name ?? '');
}, [fileSet.originalFile.get()]);
if (fileSet.originalFile.get())
return (
<Paper variant="outlined">
<div className="flex px-8 py-32 justify-center">
<div className="flex">
<a style={{ color: '#888888', textDecoration: 'underline', cursor: 'default' }}>{fileLabel}</a>
<p className="mx-4"> </p>
<a onClick={handleDeleteFile} style={{ color: red[600], cursor: 'pointer' }} role="link">
{'[Clear File]'}
</a>
<p className="mx-4"> </p>
{fileSet.compressedFile?.get() && (
<a onClick={downloadFileAction} style={{ color: green[600], cursor: 'pointer' }} role="link">
{'[Download File]'}
</a>
)}
</div>
</div>
</Paper>
);
return (
<Paper {...rootProps} className={classes.dragndropZone} variant="outlined">
<div className="flex px-8 py-32 justify-center">
<input {...getInputProps()} name="customerCSVFilename" placeholder="CSV File"/>
<p>{fileLabel}</p>
</div>
</Paper>
);
};
What I've tried so far:
Checked if ThemeProvider is available
Added custom theme just to the code block which fails
All other tests which are testing hooks or custom logic (like pure TypeScript) are working without any issues, but it seems that somehow using styles from MUI is not working. When I remove these lines, test is passing, so my guess it has something with MUI makeStyles.
Any ideas? Thanks for helping me out.
Try to use a mocked component, wrapped inside a ThemeProvider instance:
import theme from './path/to/your/theme'
const MockAnonDragNDropFileUpload = (props: any) => {
return (
<ThemeProvider theme={theme}>
<AnonDragNDropFileUpload {...props} />
</ThemeProvider>
);
}
To mock the component using the existing theme you could separate its declaration into a distinct file:
const theme = createTheme({
...
});
export default theme;
Then use the mocked instance in the tests:
describe('<AnonDragNDropFileUpload />', () => {
it('should render', () => {
...
const result = render(
<MockAnonDragNDropFileUpload
fileSet={fileSet}
downloadFileAction={jest.fn()}
clearFileAction={jest.fn()}
/>
);
expect(result).toBeTruthy();
});
});

Binding multipal row hang or crash web page using React with redux toolkit

https://codesandbox.io/s/large-data-array-with-redux-toolkit-forked-7tp63?file=/demo.tsx
In the given codesandbox example I am using react typescript with redux-toolkit
in codesandbox example. I am trying to bind countries with checkbox and textbox.
When I check on checkboxes it looks slow and textbox edit also feels very slow.
some time it breaks the page.
I am not sure what I am doing wrong.
You should make CountryItem it's own component and make it a pure component:
import React, { FC, useEffect } from "react";
import { createStyles, Theme, makeStyles } from "#material-ui/core/styles";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import { countryList } from "./dummyData";
import { Checkbox, Grid, TextField } from "#material-ui/core";
import { useAppDispatch, useAppSelector } from "./store/hooks";
import {
setCountries,
setCountrySelected,
setCountryValue
} from "./store/slice/geography-slice";
import { Country } from "./interface/country.modal";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
width: "100%",
backgroundColor: theme.palette.background.paper
}
})
);
const CountryItem: FC<{ country: Country }> = ({ country }) => {
console.log('render item',country.name)
const dispatch = useAppDispatch();
const handleCheckboxChange = (
event: React.ChangeEvent<HTMLInputElement>,
country: Country
) => {
const selectedCountry = { ...country, isSelected: event.target.checked };
dispatch(setCountrySelected(selectedCountry));
};
const handleTextChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedCountry = { ...country, value: event.target.value };
dispatch(setCountryValue(selectedCountry));
};
return (
<ListItem button>
<Checkbox
checked={country?.isSelected ?? false}
onChange={(event) => {
handleCheckboxChange(event, country);
}}
/>
<ListItemText primary={country.name} />
<TextField value={country?.value ?? ""} onChange={handleTextChange} />
</ListItem>
);
};
const PureCountryItem = React.memo(CountryItem)
export default function SimpleList() {
const classes = useStyles();
const dispatch = useAppDispatch();
const { countries } = useAppSelector((state) => state.geography);
useEffect(() => {
dispatch(setCountries(countryList));
}, []);
return (
<div className={classes.root}>
<Grid container>
<Grid item xs={6}>
<List component="nav" aria-label="secondary mailbox folders">
{countries.map((country, index) => (
<PureCountryItem country={country} key={`CountryItem__${index}`} />
))}
</List>
</Grid>
</Grid>
</div>
);
}

I have a simple messenger app with a search bar that wont update the filter whenever I press backspace

So I have a basic messenger chat application with a search bar to retrieve chatrooms, and it works fine whenever I type in letters, yet whenever I backspace, why wont it retrieve the list I had previous?
Examples below
1.All chatrooms are shown below:
2.Typing "funny" filters a list with chatrooms labeled "funny":
3.Pressing backspace does not show all the chatrooms:
import { Avatar, IconButton } from '#material-ui/core';
import React, { useEffect, useState } from 'react';
import './Sidebar.css';
import SearchIcon from '#material-ui/icons/Search';
import { RateReviewOutlined } from '#material-ui/icons';
import { SidebarChat } from './SidebarChat';
import { useSelector } from 'react-redux';
import { selectUser } from './features/userSlice';
import db, { auth } from './firebase';
import { makeStyles } from '#material-ui/core/styles';
import Modal from '#material-ui/core/Modal';
import Backdrop from '#material-ui/core/Backdrop';
import Fade from '#material-ui/core/Fade';
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
export function Sidebar(props) {
const user = useSelector(selectUser);
const [chats, setChats] = useState([]);
const classes = useStyles();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState('');
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
useEffect(() => {
db.collection('chats').onSnapshot((snapshot) =>
setChats(
snapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}))
)
);
}, []);
const addChat = () => {
const chatName = prompt('Please enter a chat name');
if (chatName) {
db.collection('chats').add({
chatName: chatName,
});
}
};
const searchFunction = (e) => {
setSearch(e);
console.log(search);
console.log(chats);
const filtered = chats.filter(chat => {
return chat.data.chatName.toLowerCase().includes(e.toLowerCase())
});
// console.log(filtered)
setChats(filtered);
};
return (
<div className="sidebar">
<div className="sidebar__header">
<Avatar
onClick={handleOpen}
src={user.photo}
className="sidebar__avatar"
/>
<div className="sidebar__input">
<SearchIcon />
<input
placeholder="Search"
value={search}
onChange={(e) => searchFunction(e.target.value)}
/>
</div>
<IconButton variant="outlined" className="sidebar__inputButton">
<RateReviewOutlined onClick={addChat} />
</IconButton>
</div>
<div className="sidebar__chats">
{chats.map(({ id, data: { chatName } }) => (
<SidebarChat key={id} id={id} chatName={chatName} />
))}
</div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500,
}}
>
<Fade in={open}>
<div className={classes.paper}>
<h2 id="transition-modal-title">
Are you sure you want to sign out?
</h2>
<button onClick={() => auth.signOut()}>Yes</button>
<button onClick={handleClose}>No</button>
</div>
</Fade>
</Modal>
</div>
);
}
You need to save the initial list of chats in a variable and once the search is set to blank '' you need to update your chats with the initial saved chats.
Problem here is -
const filtered = chats.filter(chat => {
return chat.data.chatName.toLowerCase().includes(e.toLowerCase())
});
// console.log(filtered)
setChats(filtered);

React hooks and fetch() advice

I have a component that fetches a random person, saves it to a state variable array and after mapping through that array, it returns certain elements. My goal is to render a "social card" with an avatar and personal infos, I want to use a material UI CARD component for this. Also I'd like to get any feedback on my logic here, whether this is a good way to achieve what I have achieved. The setTimeout() is only there so I can render some loading animation. Also when I tried to return a full card component inside the array map, I could not render {item.something.something}
export default function SocialCardsfunction() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const classes = useStyles();
const nextPerson = () => {
fetch("https://randomuser.me/api")
.then((response) => response.json())
.then((response) => {
setItems(response.results);
});
};
useEffect(() => {
fetch("https://randomuser.me/api")
.then((response) => response.json())
.then((response) => {
setTimeout(() => {
setItems(response.results);
setLoading(false);
}, 1000);
});
}, []);
if (loading) {
return <div>Loading ...</div>;
} else {
return (
<div>
{items.map((item, i) => {
return (
<div>
<h2>{item.name.first}</h2>
<img src={item.picture.large} alt='picture' key={i} />
<button onClick={() => nextPerson()}>click me</button>
</div>
);
})}
</div>
);
}
}
const useStyles = makeStyles({
root: {
minWidth: 275,
},
bullet: {
display: "inline-block",
margin: "0 2px",
transform: "scale(0.8)",
},
title: {
fontSize: 14,
},
pos: {
marginBottom: 12,
},
});
Your logic looks good but there's always something that can be improved a little further.
If you would like to store only a single user a time in your SocialCard then I would extract only one single user from the API rather than a list because the API returns an array of only one object anyway.
First, I would change the state and include status and error. With status, you can easily check in which status your component is at the moment and based on that render different things/messages in your App. With error, you can define your own error in case something goes wrong and then render an error message in your App. Also, I've re-used your fetch as it was used twice and it was redundant. This way you have a nice single function that can be used anywhere while also making sure that loading is shown while it fetches the result. I've also used MaterialUI Card component for rendering the user data. You can check how the result looks like here
import React, { useState, useEffect } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Card from "#material-ui/core/Card";
import CardActionArea from "#material-ui/core/CardActionArea";
import CardActions from "#material-ui/core/CardActions";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles({
root: {
maxWidth: 345
}
});
function App() {
const classes = useStyles();
const [state, setState] = useState({
user: {},
status: "idle",
error: null
});
const { user, status, error } = state;
const getUser = () => {
setState((prevState) => ({
...prevState,
status: "loading"
}));
fetch("https://randomuser.me/api").then(async (res) => {
if (res.ok) {
const data = await res.json();
setState((prevState) => ({
...prevState,
user: data.results[0],
status: "processed"
}));
} else {
setState({
user: {},
status: "failed",
error: "Error message"
});
}
});
};
useEffect(() => {
getUser();
}, []);
if (status === "loading") {
return <div>Loading ...</div>;
}
if (status === "failed") {
return <div>{error}</div>;
}
if (status === "processed") {
return (
<Card className={classes.root}>
<CardActionArea>
<CardMedia
component="img"
alt="user"
height="140"
image={user.picture.large}
title="user"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{user.name.first}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary" onClick={getUser}>
Show another user
</Button>
</CardActions>
</Card>
);
} else {
// Default placeholder
return <div>hi</div>;
}
}
export default App;
However, if you would like to store all the users you fetch when clicking the button, I would suggest moving the fetch and state of users into the parent component and leave SocialCard component only for rendering a single user. Then, in the parent component I would ensure that the setState would look something like this in the getUser function
setState((prevState) => ({
...prevState,
users: [...prevState.users, ...data.results], // This merges your previous user objects with new user object
status: "processed"
}));
This way, you can keep all the users in your parent component and using map you can render each user with your SocialCard component. Take a note that you would need to refactor your components further in order to make this work. I'll leave it as an exercise for you if you want to go this route.
Changes Made
Instead of repeating the same code make a function and call that function in useEffect.
Each child in a list should have a unique "key" prop.
Card from Material UI is used.(I have not focused on styling much XD)
import {
Button,
Card,
CardActions,
makeStyles,
} from "#material-ui/core";
import React, { useEffect, useState } from "react";
export default function SocialCardsfunction() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const classes = useStyles();
const fetchPerson = () => {
fetch("https://randomuser.me/api")
.then((response) => response.json())
.then((response) => {
setTimeout(() => {
setItems(response.results);
setLoading(false);
}, 1000);
});
};
useEffect(() => {
fetchPerson();
}, []);
if (loading) {
return <div>Loading ...</div>;
} else {
return (
<div>
{items.map((item, i) => {
return (
<div key={i}>
<Card className={classes.root}>
<h2>{item.name.first}</h2>
<img
alt="img"
src={item.picture.large}
className={classes.large}
/>
<CardActions>
<Button onClick={() => fetchPerson()} size="small">Next</Button>
</CardActions>
</Card>
</div>
);
})}
</div>
);
}
}
const useStyles = makeStyles({
root: {
minWidth: 275
},
bullet: {
display: "inline-block",
margin: "0 2px",
transform: "scale(0.8)"
},
title: {
fontSize: 14
},
pos: {
marginBottom: 12
}
});

Resources