React - Passing State Back to Parent - UseContext - reactjs

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>
</>
);

Related

How to test a component with reduce method in react using react testing and jest?

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.

Pass usestate array between sibling components in React

I am building a an app that has a map and side-tabs.
Every time a user clicks on the map a marker appears and the coordinates are stored in a used state array.
I want every time a new marker appears to show it as a list or an accordion item in my side-tabs.
My side-tabs component and my addmarker component have the App as a parent.
How can I pass the usestate array from my addmarker component to my sidebar component every time I click on the map ?
ADD MARKER COMPONENT
function AddMarker(callbackFunction){
const [coord, setPosition] = useState([]);
const map = useMapEvents({
click: (e) => {
setPosition([...coord,e.latlng])
const mark = e
//console.log(mark)
//setInfo(`${e.latlng}`)
},
SIDE-BAR COMPONENT
export default function VerticalTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Box
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 224 }}
>
<Tabs
orientation="vertical"
value={value}
onChange={handleChange}
aria-label="Vertical tabs"
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="Waypoints" {...a11yProps(0)} />
<Tab label="Sorting" {...a11yProps(1)} />
</Tabs>
<TabPanel value={value} index={0}>
</TabPanel>
<TabPanel value={value} index={1}>
Sorting
</TabPanel>
</Box>
);
}
APP.JS
function App() {
return (
<div className="App" >
<Sidetabs/>
<MapContainer center={[40.44695, -345.23437]} zoom={3}>
..............
<AddMarker />
</MapContainer>
</div>
)
}
Here, always lifting one step up, helps everytime, i have lifted your state up, and now both children have access to it, https://reactjs.org/docs/lifting-state-up.html
the similar implementation could be look like this,
as both siblings are nothing but children to parent App
function App() {
const [coord, setPosition] = useState([]);
return (
<div className="App" >
<Sidetabs coord={coord} setPosition={setPosition}/>
<MapContainer center={[40.44695, -345.23437]} zoom={3}>
<AddMarker coord={coord} setPosition={setPosition}/>
</MapContainer>
</div>
)
}
then extract out using props,
function AddMarker(props){
const {coord, setPosition} = props;
}

React component not rendering on state update

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>
);
}

React - Communicate between Material UI Components

I'm using Material UI's nested/select ItemList component to dynamically generates any number of dropdown menu items based on how many items belong in this header as you can maybe tell from the mapping function. On another file 1 layer above this one, I am again mapping and generating multiple of these DropDownMenus, is it possible for these components to communicate to each other?
This is the file in question
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
maxWidth: 330,
backgroundColor: theme.palette.background.paper,
},
nested: {
paddingLeft: theme.spacing(4),
}
}));
export default function DropDownMenu(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(true);
let unitName = props.unit[0];
let chapterList = props.unit.slice(1);
const [selectedIndex, setSelectedIndex] = React.useState(1);
const handleListItemClick = (index) => {
console.log("ItemClicked");
console.log(index);
setSelectedIndex(index);
};
const handleClick = () => {
setOpen(!open);
};
const selectMenuItem = (chapter, index) => {
props.chooseChapter(chapter)
handleListItemClick(index)
}
let dropDownUnit = chapterList.map((chapter, index) => {
return (
<ListItem button
className={classes.selected}
selected={selectedIndex === index}
onClick={() => selectMenuItem(chapter, index)}
key={index}>
<ListItemText primary={chapter} />
</ListItem>
)
})
return (
<List
component="nav"
aria-labelledby="nested-list-subheader"
subheader={
<ListSubheader component="div" id="nested-list-subheader">
</ListSubheader>
}
className={classes.root}
>
<ListItem button onClick={handleClick}>
<ListItemText primary={unitName} />
{!open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={!open} timeout="auto" unmountOnExit>
<List component="div" disablePadding className={classes.selected}>
{dropDownUnit}
</List>
</Collapse>
</List>
);
}
Psudo Style - What I'm trying to accomplish
<DropDownMenu>
<MenuItem> // Suppose this is selected
<MenuItem>
<DropDownMenu>
<MenuItem> // onClick --> Select this and deselect all other selected buttons
You can have a parent of these components such that the parent will keep the state of who is active. That way you can pass that state & the state setter as props so that everyone will know who is active
export default function App() {
const [selectedItem, setSelectedItem] = React.useState();
return (
<>
<DropDownMenu
selectedItem={selectedItem} // pass down as props
setSelectedItem={setSelectedItem} // pass down as props
unit={...}
chooseChapter={function () {}}
/>
...
On the children, simply refactor the Call To Action (in this case onClick) to set the state using the passed down props. Pay attention to the selected prop of ListItem, we now use the state we have passed down from the parent
let dropDownUnit = chapterList.map((chapter, index) => {
return (
<ListItem
button
className={classes.selected}
selected={props.selectedItem === chapter}
onClick={() => props.setSelectedItem(chapter)}
key={index}
>
<ListItemText primary={chapter} />
</ListItem>
);
});

Any way to render a dynamically added modal from api with routing in react?

working on a project for a hospital in which data on patients gets pulled from their api and gets loaded as cards on the page (will provide screenshots). When you click on a card more info of the patient gets pulled up as a modal. The goal here is for them to render when someone searches for it based on slug. Each endpoint from the api has a slug: API Data
for example if you go to localhost:3000/WebersWarriors (localhost:3000/${shirt.slug}) it will render that specific modal and if you click on a card it would append "WebersWarriors" to the end of the URL. Any help or suggestions would be greatly appreciated thank you!
Layout
When card gets clicked
Modal code being displayed dynamically:
const TshirtItem = props => {
const classes = useStyles();
const { shirt } = props;
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const matches = useMediaQuery(theme.breakpoints.down('sm'));
const handleClickOpen = () => {
setOpen(true);
setTimeout(() => {
handleClose();
}, 30000);
};
const handleClose = () => {
setOpen(false);
};
const handleDetail = content => (
<Dialog
fullScreen={matches}
className={classes.dialog}
open={open}
TransitionComponent={Transition}
keepMounted
onClose={handleClose}
aria-labelledby="alert-dialog-slide-title"
aria-describedby="alert-dialog-slide-description"
>
<DialogContent>
<Grid container>
<Grid item>
{shirt.ar_lens_card !== null ? (
<img
key={shirt.ar_lens_card.id}
src={shirt.ar_lens_card.url}
title={shirt.ar_lens_card.name}
alt={shirt.ar_lens_card.name}
className={classes.dialog_img}
/>
) : null}
</Grid>
<Grid item container>
<Grid item xs={2} container direction="column">
<Typography
className={classes.tshirt_number}
color="textSecondary"
>
#{shirt.Tshirt_Number}
</Typography>
</Grid>
<Grid item xs={10} container>
<Grid item xs>
<Typography className={classes.label}>Team</Typography>
<Typography className={classes.team_name}>
{shirt.team_name}
</Typography>
<hr className={classes.hr} />
<Typography className={classes.patient_name}>
{shirt.patient_first_name}
</Typography>
<Typography
color="textSecondary"
className={classes.patient_diagnosis}
>
{shirt.patient_diagnosis}
</Typography>
<Typography className={classes.patient_bio}>
{shirt.patient_bio}
</Typography>
</Grid>
</Grid>
{matches ? (
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="close"
className={classes.arrowback_icon}
>
<ArrowBackIosIcon fontSize="large" />
</IconButton>
) : null}
</Grid>
</Grid>
</DialogContent>
</Dialog>
);
Your code will be pretty similar to the React-router gallery example.
You just need to fetch your data in your list component and render the cards. In the demo below I've used Bulma for styling and React-masonry-css for creating the Masonry grid.
The demo can be found here.
The important part of the demo is the Cards component with the following code:
const Cards = () => {
const [users, setUsers] = useState([]);
const [isFetching, setFetchStatus] = useState(true);
useEffect(() => {
const fetchUsers = async () => {
try {
const { data } = await axios.get(API_URL);
setUsers(data);
} catch (err) {
console.error("failed", err);
}
setFetchStatus(false);
};
fetchUsers();
}, []);
const location = useLocation();
const background = location.state && location.state.background;
return isFetching ? (
"loading..."
) : (
<Fragment>
<Switch location={background || location}>
<Route path="/" component={() => <Home users={users} />} />
</Switch>
{background && (
<Route
path="/user/:id"
component={() => <RouterModal users={users} />}
/>
)}
</Fragment>
);
};
It is like in the gallery example except the useEffect that fetches the data from https://jsonplaceholder.typicode.com/ API.
useEffect hook with an empty array as a parameter is in a class-based component the life cycle method componentDidMount - so it's just called on the first render.
Things you could improve in your final app:
Check caching of the fetched data
Limit the rendered cards (e.g. show only 1000 cards and filter by letters)
Add a search field with typeahead
Change the query to slug (in the demo it's the id because there's no slug in the fake api data)
Move the components into different files (kept everything in one file just for the demo)

Resources