Pass usestate array between sibling components in React - reactjs

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

Related

Mui TabList overflowing when wrapped inside a grid

In the code below the tabs are overflowing past the visible width after wrapping in the TabsWrapper. I have a use case where the tabs will always be rendered in a similar wrapper. Is it possible to override some styles in ScrollableTabs to achieve the default behaviour?
I'm getting the feeling that there should be a fix as this seems like a common use case w.r.t to CSS Grid and Flex.
Just in case you aren't familiar with the default behaviour, try rendering ScrollableTabs without it being wrapped in the TabsWrapper.
import React from "react";
import { TabContext, TabPanel, TabList } from "#mui/lab";
import { Box, Tab, Stack } from "#mui/material";
/** Cannot be changed */
function TabsWrapper(props) {
return (
<div style={{ display: "grid", gridAutoFlow: "row" }}>{props.children}</div>
);
}
/** Cannot be changed */
function MyTabList(props) {
return (
<Stack direction="row">
<TabList
orientation="horizontal"
variant="scrollable"
onChange={props.onChange}
>
{props.children}
</TabList>
</Stack>
);
}
/** Can be changed */
export default function ScrollableTabs() {
const tabValues = [...Array(30)].map((_, index) => String(index + 1));
const [tab, setTab] = React.useState("1");
const handleChange = (_, newValue) => {
setTab(newValue);
};
return (
<TabsWrapper>
<TabContext value={tab}>
<MyTabList onChange={handleChange}>
{tabValues.map((value) => (
<Tab label={`Tab ${value}`} value={value} key={value} />
))}
</MyTabList>
<Box>
{tabValues.map((value) => (
<TabPanel value={value} key={value}>
Tab content {value}
</TabPanel>
))}
</Box>
</TabContext>
</TabsWrapper>
);
}
Working codesandbox - https://codesandbox.io/s/mui-custom-scrollable-tabs-2mfcjr
Thanks!
Wrapping TabContext in <div style={{ overflow: "hidden" }}> fixes the issue.
The ScrollableTabs jsx should look like -
export default function ScrollableTabs() {
const tabValues = [...Array(30)].map((_, index) => String(index + 1));
const [tab, setTab] = React.useState("1");
const handleChange = (_, newValue) => {
setTab(newValue);
};
return (
<TabsWrapper>
<div style={{ overflow: "hidden" }}>
<TabContext value={tab}>
<MyTabList onChange={handleChange}>
{tabValues.map((value) => (
<Tab label={`Tab ${value}`} value={value} key={value} />
))}
</MyTabList>
<Box>
{tabValues.map((value) => (
<TabPanel value={value} key={value}>
Tab content {value}
</TabPanel>
))}
</Box>
</TabContext>
</div>
</TabsWrapper>
);
}
Working codesandbox - https://codesandbox.io/s/mui-custom-scrollable-tabs-forked-6ycqfv

Render React Component from name inside array map

This is either very simple or I am doing it completely wrong. I am a novice so please advise.
I am trying to show different components inside different tabs using Material UI using array map. The tabs are showing fine but the components do not render. Basically if the array label is 'Welcome', the tab name should be 'Welcome' and the Welcome component should show up and so on. Please help!
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
{fetchedCategories.map((category) => (
<Tab key={category.label} label={category.label} />
))}
</Tabs>
</Box>
{fetchedCategories.map((category, index) => {
const Component=myComponents[category.label];
})}
{fetchedCategories.map((category, index) => (
<TabPanel key={category.label} value={value} index={index}>
<Component label={category.label} />
</TabPanel>
))}
</Box>
);
Here is my props & Component function:
interface ComponentProps {
label: string;
value?: number;
}
function Component (props: ComponentProps)
{
const {label, value} = props;
return myComponents[label];
}
const myComponents = {
'Welcome': Welcome,
'Salad/Soup': Welcome
}
Try something like:
function Component({ label, value }: ComponentProps) {
const [Comp, setComponent] = useState(<div />);
React.useEffect(() => {
const LabelComp = myComponents[label];
if (label && LabelComp) {
setComponent(<LabelComp value={value} />); // <-- if you want to pass value to you component
}
}, [value, label]);
return Comp;
}
And you will use it like:
const App = () => {
return <Component label={"myComponentLabel"} value={"some value"} />;
};

How to map a array to create nodes in material UI

I need to create bunch of Tab nodes in a Tabs. I thought that map a array would be easier to manage it. But I was kind of don't know how to make it works with MATERIAL UI Taps components.
My target is when I click the tab, the TabPanel supposed to show the correct components pending on the index.
The Tabs part works just fine, and it will be siwtch components properly if I keep the TabPanel one by one. But it won't be work if I map the array to create the TabPanel.
Please advise how to fix it.
//TODO set the router for each tab, wondering if it could be done in an array and map it
const tab_item = [
{
index: 1,
label: 'Purchase',
path: '/Linx_Homeline/Purchase',
tabPanel_comp:<LawyerPurchase />
},
{
index: 2,
label: 'Refinance',
path: '/Linx_Homeline/Refinance',
tabPanel_comp:<Refinance />
},
// {},
]
function TabPanel(props) {
const { children, value, index } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`wrapped-tabpanel-${index}`}
>
{value === index && (
<Box p={3}>
<Typography component={'div'}>{children}</Typography>
</Box>
)}
</div>
);
}
// TabPanel.propTypes = {
// // children: PropTypes.node,
// index: PropTypes.any,
// value: PropTypes.any,
// };
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: theme.palette.background.paper,
},
item: {
minWidth: '0px'
}
}));
export default function TabsWrappedLabel() {
const classes = useStyles();
const [value, setValue] = React.useState(false);
const updateNotes = useContext(NotesUpdate);
const history = useHistory();
const handleChange = (event, newValue) => {
setValue(newValue);
};
const clean_notes_push = (item) => {
//comments
updateNotes.setCondition('');
updateNotes.setFunNotes('');
updateNotes.setBusNotes('');
history.push(item.path);
}
return (
<div className={classes.root}>
<AppBar position="static">
<Tabs
value={value}
onChange={handleChange}
variant='fullWidth'
TabIndicatorProps={{ style: { background: '#00ff33' } }}
>
{tab_item.map((item) => ( // The Tab works fine here.
<Tab
wrapped
key={item.index}
index={item.index}
label={item.label}
onClick={() => (clean_notes_push(item))}
/>
))}
</Tabs>
</AppBar>
{/* <TabPanel value={value} index={0}> // It works if I put the TabPanel one by one, but I'm trying to map the tab_item array to generate them, problem is I don't know how to make it works.
<LawyerPurchase/>
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Two
</TabPanel>
*/}
{tab_item.map((item) => ( // Not working here, not even generate a TabPanel
<TabPanel
key={item.index}
value={value}
index={1}
>
{item.tabPanel_comp}
</TabPanel>
))}
</div>
);
}
You may need to update the tab_item object by:
//TODO Declare the function to render the component in a tab pane
const tab_item = [
{
index: 1,
label: 'Purchase',
path: '/Linx_Homeline/Purchase',
tabPanel_comp: () => <LawyerPurchase /> // function returns the component
},
{
index: 2,
label: 'Refinance',
path: '/Linx_Homeline/Refinance',
tabPanel_comp: () => <Refinance />
},
]
And replace the TabPanel render map function by:
{tab_item.map((item) => ( // Not working here, not even generate a TabPanel
<TabPanel
key={item.index}
value={value}
index={1}
>
{item.tabPanel_comp()} //calls the function to render the component
</TabPanel>
))}

Why does useRef never update current from null?

Problem: cannot get ref to update from {current: null} to the actual ref on the component.
What i want to happen: {current: null}, as i understand it, should update to include the div that ref is on in order to be able to click ouside of it (eventually to close it). 9 understand that it does not update on first render, but it does not ever update. It does run twice on page load, both returning current: null.
What i tried: i have followed all the SO advice to use useEffect and then finally separating it into this function which appears to be the most appropriate and up to date method to do this. It just never updates current.
function useOutsideAlerter(ref) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
console.log(ref);
} else {
console.log("else", ref);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
}
export const Modal = (props) => {
const [showModal, setShowModal] = useState(props.showModal);
const wrapperRef = useRef(null);
useOutsideAlerter(wrapperRef);
return (
<Layout>
<ModalOuter
showModal={showModal || props.showModal}
id={styles["modalOuter"]}
handleClose={props.handleClose}
>
<ModalInner
ref={wrapperRef}
handleClose={props.handleClose}
>
<Layout display="flex" flexDirection="column">
<Layout display="flex" flexDirection="column">
<ModalTitle title={props.title} />
</Layout>
<HR />
<Layout display="flex" flexDirection="column">
<ModalBody body={props.body} />
</Layout>
</Layout>
</ModalInner>
</ModalOuter>
</Layout>
);
};
ModalInner
export const ModalInner = (props) => {
return (
<Layout
id={props.id}
ref={props.ref}
display="flex"
justifyContent="center"
alignItems="center"
padding="2rem"
margin="2rem"
backgroundColor="white"
>
{props.children}
</Layout>
);
};
Layout Component
export const Layout = (props) => {
return (
<div
id={props.id}
ref={props.ref}
...
Issue
In React, there are a few special "props", ref and key are a couple of them. I put quotes around props because while they are passed as props, they are not passed on to or accessible on the props object in children components.
Solution
Use React.forwardRef to forward any passed React refs to functional components and expose them in children components.
export const ModalInner = React.forwardRef((props, ref) => { // <-- access ref
return (
<Layout
id={props.id}
ref={ref} // <-- pass ref *
display="flex"
justifyContent="center"
alignItems="center"
padding="2rem"
margin="2rem"
borderRadius="5px"
backgroundColor="white"
border={`1px solid ${Color.LightGray}`}
boxShadow={`0rem 0rem 1rem white`}
>
{props.children}
</Layout>
);
});
* Note: The Layout and children components will similarly need to forward the ref until you get to where it's actually attached to a DOMNode.
An alternative solution is to pass the ref as a normal prop.
<ModalInner
wrapperRef={wrapperRef}
handleClose={props.handleClose}
>
...
export const ModalInner = (props) => {
return (
<Layout
id={props.id}
wrapperRef={props. wrapperRef} // <-- pass wrapperRef prop
display="flex"
justifyContent="center"
alignItems="center"
padding="2rem"
margin="2rem"
borderRadius="5px"
backgroundColor="white"
border={`1px solid ${Color.LightGray}`}
boxShadow={`0rem 0rem 1rem white`}
>
{props.children}
</Layout>
);
};
Similarly, you need to drill the wrapperRef prop on through to children until you get to the actual DOMNode where you attach the ref.
Example
<div ref={props.wrapperRef> .... </div>
You may also find Refs and the DOM docs useful for working with React refs.

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

Resources