I am loading some data from my API, it is returned successfully, but React is not rendering the cards afterwards.
export default function Subjects() {
const userApi = useUserService();
const auth = useRecoilValue(AuthAtom);
const [loading, setLoading] = React.useState<boolean>(true);
const [subjects, setSubjects] = React.useState<Subject[]>();
React.useEffect(() => {
userApi.getSubjects(auth?.userId ?? 0).then((value: Subject[]) => {
setSubjects(value);
setLoading(false);
});
}, []);
return (
<Box display="flex" flexDirection="row" alignItems="center">
{!loading &&
subjects?.map(function (subject: Subject) {
<Card key={subject.name}>
<CardActionArea>
<CardContent>{subject.name}</CardContent>
</CardActionArea>
</Card>;
})}
</Box>
);
}
userApi.getSubjects returns an array of Subjects.
You don't return anything inside the .map callback, so your component doesn't render anything. To fix it:
subjects?.map(function (subject: Subject) {
// add the return statement like below.
return (
<Card key={subject.name}>
<CardActionArea>
<CardContent>{subject.name}</CardContent>
</CardActionArea>
</Card>
);
})}
Your function in the body do not return anything. Try this:
return (
<Box display="flex" flexDirection="row" alignItems="center">
{!loading &&
subjects?.map(function (subject: Subject) {
return (
<Card key={subject.name}>
<CardActionArea>
<CardContent>{subject.name}</CardContent>
</CardActionArea>
</Card>;
);
})}
</Box>
);
}
Related
I have a MetaTable Component that has LeftSidePanel wrapped with the UseContext. I want to open the panel when button is click on the MetaTable (passing some data to show detail of a particular record). My code works to open the Panel but when I click outside it doesn't close.I think I would need to set the State back on the parent. Tried a few things but failed. Any guidance please?
MetaTable Component
export const DrawerDataContext = createContext();
export const DrawerContext = createContext();
const [isDrawerOpen, setDrawerOpen] = useState();
const [bookId, setBookId] = useState({});
const toggleSidePanel = (row) => {
setBookId(row)
setDrawerOpen(true);
}
... <TableCell className={classes.tableCell}>
<Stack spacing={0}>
<Typography variant="body2" my={0}>
{row}
</Typography>
<Typography variant="body2" my={0}>
{row}
</Typography>
</Stack>
<Stack spacing={2}>
<button onClick={() => { toggleSidePanel(row); }} >toggle drawer</button>
</Stack>
</TableCell>...
<DrawerDataContext.Provider value={bookId}>
<DrawerContext.Provider value={isDrawerOpen} >
<LeftSidePanel />
</DrawerContext.Provider>
</DrawerDataContext.Provider>
LeftSidePanel Component
const book= useContext(DrawerDataContext);
const open = useContext(DrawerContext);
return (
<>
<Drawer open={open} onClose={() => !open} anchor='left'>
<List style={{ width: 500 }}>
</List>
</Drawer>
</>
);
In addition to the value of your state, you can also share a function with your context to change its value:
<DrawerContext.Provider value={{
isOpen: isDrawerOpen,
close: () => setDrawerOpen(false)
}}>
And in your component:
const book = useContext(DrawerDataContext);
const { isOpen, close } = useContext(DrawerContext);
return (
<>
<Drawer open={isOpen} onClose={close} anchor='left'>
<List style={{ width: 500 }}>
</List>
</Drawer>
</>
);
I want to perform using the test on the component I have in my react app. when I simply tried to render my component using the render from react testing it is throwing me the error.
Can anyone please suggest to me the solution to this? please see the code below:
Code for testing the authorgroup componenet;
import { render, screen } from "#testing-library/react";
import AuthorsGroup from "./Authorsgroup";
it("should render author component", () => {
render(<AuthorsGroup />);
const headerEl = screen.getByRole("heading", { name: /happyusertapesh/i });
expect(headerEl).toBeInTheDocument();
});
Authorcomponent
const Authorsgroup = ({
posts,
groupDropdownValue,
setShowForm,
setPostId,
showForm
}) => {
const authorGroup = posts.reduce((group, authorgroup) => {
(group[authorgroup.author.replace(/ +/g, "")] =
group[authorgroup.author.replace(/ +/g, "")] || []).push(authorgroup);
return group;
}, {});
const [authorGroupValues, setAuthorGroupValues] = useState(authorGroup);
const authorGroupEntries = Object.entries(authorGroupValues);
useEffect(() => {
setAuthorGroupValues(authorGroup);
}, [groupDropdownValue, showForm]);
return (
<>
<Container>
<Container style={{ marginTop: "3rem" }}>
{authorGroupEntries.map(([author, posts]) => {
return (
<div key={author}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography variant="h6" style={{ color: "#EB1283" }}>
{author}
</Typography>
</AccordionSummary>
{posts.map(({ id, text, author, location }) => {
return (
<div key={id}>
<AccordionDetails>
<Typography variant="h4">
{id} {author}
</Typography>
<Typography>
{text} {location}
</Typography>
<Button
variant="outlined"
onClick={() => {
setShowForm(!showForm);
setPostId(id);
}}
startIcon={<EditIcon />}
>
Edit
</Button>
</AccordionDetails>
</div>
);
})}
</Accordion>
</div>
);
})}
</Container>
</Container>
</>
);
};
export default Authorsgroup;
Thanks for your help.
Error
You aren't passing any props into the test component. This means that the value posts is being set to undefined.
Simply pass a mock value for posts as a prop in the render method
render(<AuthorsGroup posts={/* posts mock value here */} />)
You will also need to do this for each of the other props you have defined.
Goal: I should display the specific contents of a specific button after one of three buttons was clicked. Then, after the specific button is clicked, all three buttons should be hidden and replaced with the contents of the clicked specific button.
Issue: I tried passing props and using if-else statement in terms of conditional rendering but I am having trouble figuring out how to properly state a condition for the functionality to work since the remaining if else statements are ignored. Only the Beef button is working but the rest of the buttons are not.
Source code:
import * as React from "react";
import { Stack } from '#mui/material';
import FoodTraysItemButton from "./FoodTraysItemButton";
import PastaNoodlesButtonsFT from "./foodTraysPages/PastaNoodlesButtonsFT";
import DessertsButtonsFT from "./foodTraysPages/DessertsButtonsFT";
import BeefButtonsFT from "./foodTraysPages/BeefButtonsFT";
import { useState } from "react";
function preventDefault(event) {
event.preventDefault();
}
export default function FoodTraysButtons(props) {
const [myBoolBeef, setmyBoolBeef] = useState(true);
const [myBoolDesserts, setmyBoolDesserts] = useState(true);
const [myBoolPastaNoodles, setmyBoolPastaNoodles] = useState(true);
function toggleBoolBeef() {
setmyBoolBeef(!myBoolBeef);
}
function toggleBoolDesserts() {
setmyBoolDesserts(!myBoolDesserts);
}
function toggleBoolPastaNoodles() {
setmyBoolPastaNoodles(!myBoolPastaNoodles);
}
return (
// stuck here: (I plan to use multiple separate if else statements to work the functionality out but it doesn't work)
<React.Fragment>
{(() => {
// only works here
if (myBoolBeef) {
return (<Landing toggleBoolBeef={toggleBoolBeef} />);
} else{
return <BeefFT/>;
}
// these are ignored:
if (myBoolDesserts) {
return (<Landing toggleBoolDesserts={toggleBoolDesserts} />);
} else{
return <DessertsFT/>;
}
if (myBoolPastaNoodles) {
return (<Landing toggleBoolPastaNoodles={toggleBoolPastaNoodles} />);
} else{
return <PastaNoodlesFT/>;
}
})()}
</React.Fragment>
);
}
function Landing(props) {
return (
<div>
<Stack spacing={0} direction="row" sx={{ mb: 4.5 }}>
<FoodTraysItemButton
title="Beef"
onClick={props.toggleBoolBeef}
/>
<FoodTraysItemButton
title="Desserts"
onClick={props.toggleBoolDesserts}
/>
<FoodTraysItemButton title="Pasta/Noodles" onClick={props.toggleBoolPastaNoodles} />
</Stack>
</div>
);
}
function BeefFT() {
return (
<div>
<BeefButtonsFT />
</div>
);
}
function DessertsFT() {
return (
<div>
<DessertsButtonsFT />
</div>
);
}
function PastaNoodlesFT() {
return (
<div>
<PastaNoodlesButtonsFT />
</div>
);
}
Full source codes in Codesandbox: https://codesandbox.io/s/show-hide-buttons-ralph-ecv9g2?file=/src/FoodTraysButtons.jsx:773-815
How it should look like:
Beef button:
Desserts button:
Pasta Noodles button:
In what way should I implement this in order to achieve its functionality?
Your responses would be highly appreciated as I am exploring MUI and React at the moment. It would be a really big help for my project. Thank you very much!!!
Update FoodTraysButtons to hold a single state, selection that is then used to conditionally render the Landing component or any of BeefFT, DessertsFT, or PastaNoodlesFT component.
export default function FoodTraysButtons(props) {
const [selection, setSelection] = useState();
const selectHandler = (selection) => setSelection(selection);
return (
<React.Fragment>
{!selection && <Landing onSelect={selectHandler} />}
{selection === "beef" && <BeefFT />}
{selection === "dessets" && <DessertsFT />}
{selection === "pastaNoodles" && <PastaNoodlesFT />}
</React.Fragment>
);
}
Update the Landing component to take a single onSelect prop callback.
function Landing({ onSelect }) {
const selectHandler = (selection) => () => onSelect(selection);
return (
<div>
<Stack spacing={0} direction="row" sx={{ mb: 4.5 }}>
<FoodTraysItemButton title="Beef" onClick={selectHandler("beef")} />
<FoodTraysItemButton
title="Desserts"
onClick={selectHandler("desserts")}
/>
<FoodTraysItemButton
title="Pasta/Noodles"
onClick={selectHandler("pastaNoodles")}
/>
</Stack>
</div>
);
}
You need a switch case block instead of multiple boolean value state. Consider this way of structuring your code:
const menuState = {
NONE: "none",
BEEF: "beef",
DESSERTS: "desserts",
PASTA: "pasta"
};
export default function FoodTraysButtons(props) {
const [selectedMenu, setSelectedMenu] = useState(menuState.NONE);
const renderMenu = () => {
switch (selectedMenu) {
case menuState.BEEF:
return <BeefFT />;
case menuState.DESSERTS:
return <DessertsFT />;
case menuState.PASTA:
return <PastaNoodlesFT />;
case menuState.NONE:
default:
return null;
}
};
return (
<React.Fragment>
{selectedMenu === menuState.NONE && (
<Landing setSelectedMenu={setSelectedMenu} />
)}
{renderMenu()}
</React.Fragment>
);
}
function Landing(props) {
return (
<div>
<Stack spacing={0} direction="row" sx={{ mb: 4.5 }}>
<FoodTraysItemButton
title="Beef"
onClick={() => props.setSelectedMenu(menuState.BEEF)}
/>
<FoodTraysItemButton
title="Desserts"
onClick={() => props.setSelectedMenu(menuState.DESSERTS)}
/>
<FoodTraysItemButton
title="Pasta/Noodles"
onClick={() => props.setSelectedMenu(menuState.PASTA)}
/>
</Stack>
</div>
);
}
Working Demo:
NOTE: If you want to always show the button menu then remove the selectedMenu === menuState.NONE wrapper condition.
I am using React Material UI. For loading purposes, I decided to use Skeleton structure. However, state change does not effect skeleton component's width and height so that it does not work properly.
export default function ComplexGrid(props) {
const [hover, setHover] = useState(false);
const [article, setArticle] = useState("");
const [year, setYear] = useState("");
const [month, setMonth] = useState("");
const [loaded, setLoaded] = useState(false);
const [width, setWidth] = useState(null);
const [height, setHeight] = useState(null);
const skeletonStructure = useRef(null);
const classes = useStyles();
useEffect(() => {
if (props.mainNew[0]) {
setArticle(props.mainNew[0]);
setYear(String(article.updated_at).split("-")[0]);
setMonth(String(article.updated_at).split("-")[1]);
}
if (skeletonStructure.current) {
setWidth(skeletonStructure.current.offsetWidth);
setHeight(skeletonStructure.current.offsetHeight);
}
setTimeout(() => {
setLoaded(true);
}, 500);
});
const stylehvr = loaded && hover && article ? { cursor: "pointer", opacity: "60%", transition: "all 0.5s linear" } : {};
const hoverbck = loaded && hover && article ? { backgroundColor: "black", cursor: "pointer" } : {};
return (
<div className={classes.root} onMouseOver={() => setHover(true)} onMouseOut={() => setHover(false)}>
<Grid container spacing={2} className={classes.grd}>
<div style={hoverbck}>
{article && loaded ? (
<img ref={skeletonStructure} className={classes.img} style={stylehvr} alt={article.title} src={article.img1} />
) : (
<Skeleton variant="rect" width={width} height={height} animation="pulse" />
)}
{article && loaded ? (
<Typography gutterBottom variant="subtitle1" className={classes.title}>
{article.title} - {width} - {height}
</Typography>
) : (
""
)}
{article && loaded ? (
<Typography variant="body2" color="textSecondary" className={classes.date}>
{month} / {year}
</Typography>
) : (
""
)}
{article && loaded ? (
<Typography variant="body2" color="textSecondary" className={classes.timer}>
{article.read_time} Dakikalık Okuma
</Typography>
) : (
""
)}
{article && loaded ? (
<Typography variant="body2" className={classes.type}>
{article.content_type}
</Typography>
) : (
""
)}
</div>
</Grid>
</div>
);
}
Since initial state of height and weight are 'null', skeleton does not work as intended, nevertheless weight and height are correctly set in useEffect. How to use correct state for skeleton component?
Try setting all your states as dependency in the useEffect hooks
useEffect(() => {//your code}, [//all states here])
To give dependency to UseEffect You need to give it like this.
useEffect(() => {
//your code which should be executed when dependent state updates.
}, [state names])
//UPDATED
useEffect(() => {
if(props.mainNew[0]){
setArticle(props.mainNew[0])
setYear(String(article.updated_at).split("-")[0])
setMonth(String(article.updated_at).split("-")[1])
}
if(skeletonStructure.current){
setWidth(skeletonStructure.current.offsetWidth)
setHeight(skeletonStructure.current.offsetHeight)
}
setTimeout(() => {
setLoaded(true)
}, 500)
},[skeletonStructure.current,props.mainNew[0]])
if you don't provide dependent states within [] then it will not be rendered again when state update occurs. and if you provide [] empty then use-effect will be called once only.
I am currently using Material ui v1.4.3 autocomplete. Material UI stated that this autocomplete is integrated with react-select.
I have followed the code here which is working like a charm but in order to handle fetching larger data in the future, I would like to implement the search function to call the database whenever the input changes so that I am able to narrow down the data that is being fetched from the database.
Has anyone had experience on this? Because the static method from this code is blocking me to call any reducer function that is passed from my parent component.
What would be an appropriate way that allows me to catch the input from the user so that I am able to call my function.
function NoOptionsMessage(props) {
return (
<Typography
color="textSecondary"
className={props.selectProps.classes.noOptionsMessage}
{...props.innerProps}
>
{props.children}
</Typography>
);
}
function inputComponent({ inputRef, ...props }) {
return <div ref={inputRef} {...props} />;
}
function Control(props) {
////console.dir(props.selectProps.inputValue); i am able to get the user input here
// so i was thinking i can call my reducer somewhere here but no luck
// my function is passed from my parent component so i wish to call this.props.theFunction here
return (
<TextField
fullWidth
InputProps={{
inputComponent,
inputProps: {
className: props.selectProps.classes.input,
ref: props.innerRef,
children: props.children,
...props.innerProps,
},
}}
onChange={(e) => IntegrationReactSelect.testing(e)}
/>
);
}
function Option(props) {
return (
<MenuItem
buttonRef={props.innerRef}
selected={props.isFocused}
component="div"
style={{
fontWeight: props.isSelected ? 500 : 400,
}}
{...props.innerProps}
>
{props.children}
</MenuItem>
);
}
function Placeholder(props) {
return (
<Typography
color="textSecondary"
className={props.selectProps.classes.placeholder}
{...props.innerProps}
>
{props.children}
</Typography>
);
}
function SingleValue(props) {
return (
<Typography className={props.selectProps.classes.singleValue} {...props.innerProps}>
{props.children}
</Typography>
);
}
function ValueContainer(props) {
return <div className={props.selectProps.classes.valueContainer}>{props.children}</div>;
}
function MultiValue(props) {
return (
<Chip
tabIndex={-1}
label={props.children}
className={classNames(props.selectProps.classes.chip, {
[props.selectProps.classes.chipFocused]: props.isFocused,
})}
onDelete={event => {
props.removeProps.onClick();
props.removeProps.onMouseDown(event);
}}
/>
);
}
const components = {
Option,
Control,
NoOptionsMessage,
Placeholder,
SingleValue,
MultiValue,
ValueContainer
};
class IntegrationReactSelect extends React.Component {
state = {
single: null,
multi: null,
};
handleChange = name => value => {
this.setState({
[name]: value,
});
this.props.getSelectMultipleValue(value);
};
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<Select
classes={classes}
options={this.props.theUserFromParentComponent}
components={components}
value={this.state.multi}
onChange={this.handleChange('multi')}
placeholder={this.props.reactSelectName}
isMulti
/>
</div>
);
}
}