Get Child components constants/state in parent component in React - reactjs

I have a component which is a MUI TAB like this
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</Box>
<TabPanel value={value} index={0}>
<Child1 />
</TabPanel>
<TabPanel value={value} index={1}>
<Child2 />
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<Box>
<Button onClick={submittab}>Submit</Button>
</Box>
</Box>
As mentioned, there are 2 child components with some textfields in it.
What I am trying to achieve is to read the child data in parent and submit it to an API as a single object.
Please help
Below the Sandbox
https://codesandbox.io/s/naughty-shadow-u2icz1?file=/demo.tsx:1233-1962
Please note I am working in typescript
Here is my child component1
import { FormLabel, TextField } from "#mui/material";
import react, { useState } from "react";
function Child1() {
const clientObj = {
user: {
name: "",
age: ""
}
};
const [client, setClient] = useState(clientObj);
const handlechange = (event: { target: { name: any; value: any } }) => {
const { name, value } = event.target;
setClient((prevState) => ({
...prevState,
context: {
...prevState.user,
[name]: value
}
}));
};
return (
<>
<FormLabel>Name</FormLabel>
<TextField
name="name"
id="text-id"
value={client.user.name}
onChange={handlechange}
/>
<FormLabel>Age</FormLabel>
<TextField
name="name"
id="text-id"
value={client.user.name}
onChange={handlechange}
/>
</>
);
}
export default Child1;

You could use a state in your parent component that will be passed as props to your children, so that they can use the parent's update state function.
Parent code:
const [state, setState] = useState()
<Child parentState={{state, setState}} />
Children code:
export function Child({parentState}) {
const {parentStateValue, setParentState} = parentState
const childTitle = 'Acme'
return <button onClick={setParentState(e => {...e, childTitle })} >childTitle</button>
}
the ideal would be to provide keys to your children so that you can manage each of their data independently in the state.

Related

React: Using a same component twice in a page removes the data in first component

I have two tabs in my react app with each tab having a spreadsheet.
I have called the same spreadsheet tag in both the tabs. when i enter data in the first tab and move to second tab, the data in the second tab is getting replaced with the default value which i am setting in the useState.
import * as React from 'react';
import PropTypes from 'prop-types';
import Tabs from '#mui/material/Tabs';
import Tab from '#mui/material/Tab';
import Typography from '#mui/material/Typography';
import Box from '#mui/material/Box';
import Spreadsheet from './components/Spreadsheet.js'
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
export default function BasicTabs() {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</Box>
<TabPanel value={value} index={0}>
Item One
<div id='div_spreadsheet1'>
<Spreadsheet id="spreadsheet1"> </Spreadsheet>
</div>
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
<div id='div_spreadsheet12'>
<Spreadsheet id="spreadsheet2"> </Spreadsheet>
</div>
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
</Box>
);
}
EDIT:
Adding the Spreadsheet.js code
import Spreadsheet from "react-spreadsheet";
import { useState } from "react";
export default function Sheet(){
const [data, setData] = useState([
[{ value: "" }, { value: "1" }],
]);
return(
<div>
<h4>SpreadSheet</h4>
<Spreadsheet data={data} onChange={setData} />
</div>
)
};
How to restrict this from happening. Please help.

How to make a reusable Material UI tabs component in React

I am working on a React project, and I divided some of the pages into tabs using the MUI Tab component, so I can have multiple components in one page and render each component accordingly, so I have created the Tabs component. However, I can only see one first index tab.
Reusable tab MUI component:
export default function useTabs(props) {
const { children, value, index, label, ...other } = props;
const [selectedValue, setSelectedValue] = React.useState(0);
const handleChange = (event, newValue) => {
setSelectedValue(newValue);
};
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={selectedValue}
onChange={handleChange}
className={classes.tab}
textColor="primary"
indicatorColor="primary"
>
<Tab label={label} {...a11yProps(0)} className={classes.tabText} />
{/* <Tab className={classes.tabText} label={label} {...a11yProps(1)} /> */}
</Tabs>
</Box>
<TabPanel value={selectedValue} index={index}>
{children} // Rendering tab children here, but getting only the first component
</TabPanel>
</Box>
);
}
Here is how I am using it:
// Import the reusable component
import Tab from "../common/Tabs";
export default function JobsRecruitments() {
return (
<>
<Tab label="tab name" index={0}>
<MyComponent />
</Tab>
</>
)
}
I don't think it's a good idea to use children if you want reusable tabs. That's because your tabs have to be consistent: if you have 3 child nodes, you should also have 3 tab labels. If you're using children, you can easily lose track of that.
However, if you want to make a reusable tab component, I would suggest passing an array of objects containing the tab label and Component as a property to your custom Tab component. Here's an example of what I mean:
export default function BasicTabs({ tabs }) {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
{tabs.map(({ label }, i) => (
<Tab label={label} key={i} />
))}
</Tabs>
</Box>
{tabs.map(({ Component }, i) => (
<TabPanel value={value} index={i} key={i}>
{Component}
</TabPanel>
))}
</Box>
);
}
And then you can use this component like this:
import Tabs from "./MyTabs";
const tabs = [
{
label: "Tab 1",
Component: <div>Hello, I am tab 1</div>
},
{
label: "Tab 2",
Component: <div>Hello, I am tab 2</div>
},
{
label: "Tab 3",
Component: (
<div>
<h1>Tab with heading</h1>
<p>Hello I am a tab with a heading</p>
</div>
)
}
];
export default function App() {
return (
<div>
<Tabs tabs={tabs} />
</div>
);
}
Here's the Codesandbox with the full example

Why Active Tab Redirecting to First Tab in React Material

I have successfully redirect the tab depending on the routes. My problem is when I'm on the second and succeeding tabs, when i refresh the browser, the active highlight on the tabs is going back the first tab?
Why is this happening? Pls check my code below
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`scrollable-auto-tabpanel-${index}`}
aria-labelledby={`scrollable-auto-tab-${index}`}
{...other}
>
{value === index && (
<Box pt={4}>
<div>{children}</div>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
function a11yProps(index) {
return {
id: `scrollable-auto-tab-${index}`,
'aria-controls': `scrollable-auto-tabpanel-${index}`,
};
}
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
maxWidth: 640,
},
}));
export default function Settings() {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div>
<AppBar position="static" color="inherit" className={classes.root}>
<Tabs
value={value}
onChange={handleChange}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
<Tab label="Users" component={Link} to="/users" {...a11yProps(0)} />
<Tab label="Products" component={Link} to="/products" {...a11yProps(1)} />
<Tab label="Sales" component={Link} to="/sales" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<Users />
</TabPanel>
<TabPanel value={value} index={1}>
<Products />
</TabPanel>
<TabPanel value={value} index={2}>
<Sales />
</TabPanel>
</div>
);
So, you want to render same component at different routes
<BrowserRouter>
<Route path={"/"} exact component={TabComponent} />
<Route path={"/users"} exact component={TabComponent} />
<Route path={"/products"} exact component={TabComponent} />
<Route path={"/sales"} exact component={TabComponent} />
</BrowserRouter>
and you want to keep active tab depending on route path, you can achieve this by checking pathname in useEffect.
useEffect(() => {
const pathname = props.history.location.pathname;
switch (pathname) {
default:
setValue(0);
break;
case "/users":
setValue(0);
break;
case "/products":
setValue(1);
break;
case "/sales":
setValue(2);
break;
}
}, [props.history.location.pathname]);
Here working example: https://codesandbox.io/s/floral-rgb-6o55s?fontsize=14&hidenavigation=1&theme=dark

How to change tab from parent in react material Tabs

I've got a component like FeedSwitcher with two tabs inside
one for the general feed the other one only for the posts of the current user
in FeedSwitcher component at the start the value is 0 therefore
the current user can view all the feed.
const FeedSwitcher = ({feed, posts, user }: FeedSwitcherProps) => {
const classes = useStyles();
const [value, setValue] = useState(0);
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
return (
<div className={classes.root}>
<Tabs
value={value}
onChange={handleChange}
variant="fullWidth"
indicatorColor="primary"
textColor="primary"
aria-label="switcher tabs"
>
<Tab icon={<PeopleIcon />} aria-label="phone" />
<Tab icon={<PersonIcon />} aria-label="favorite" />
</Tabs>
<TabPanel value={value} index={0}>
<Feed feed={feed} />
</TabPanel>
<TabPanel value={value} index={1}>
<Posts posts={posts} user={user} />
</TabPanel>
</div>
);
};
After the current user post a new thread
(the form is in the parent component)
I want to show the tab with index 1
How can set the value from the parent?
Should I use a redux state or is there
an other direct and simpler way?
The state needs to be in the parent component.
You can feed the child component with the value, and pass it a function argument like onValueChange that it can use to trigger an update on the parent's state.
// in parent
const [feedSwitcherValue, setFeedSwitcherValue] = useState(0);
return (
<FeedSwitcher
feed={feed}
posts={posts}
user={user}
value={feedSwitcherValue}
onValueChange={value => setFeedSwitcherValue(value)}
/>
);
// child
const FeedSwitcher = ({feed, posts, user, value, onValueChange }: FeedSwitcherProps) => {
const classes = useStyles();
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
onValueChange(newValue);
};
return (
<div className={classes.root}>
<Tabs
value={value}
onChange={handleChange}
variant="fullWidth"
indicatorColor="primary"
textColor="primary"
aria-label="switcher tabs"
>
<Tab icon={<PeopleIcon />} aria-label="phone" />
<Tab icon={<PersonIcon />} aria-label="favorite" />
</Tabs>
<TabPanel value={value} index={0}>
<Feed feed={feed} />
</TabPanel>
<TabPanel value={value} index={1}>
<Posts posts={posts} user={user} />
</TabPanel>
</div>
);
};

React Material UI - Tabs - How to disable lazy loading of tabs?

I am creating some react tabs based off of the example shown here for Material UI: https://material-ui.com/components/tabs/.
The problem I am encountering is that my tabs are lazy loading the linegraph components instead of performing all the queries on initial page load. Does anyone have some suggestions of where I can optimize this to make each tab load immediately on page load?
function TabPanel(props) {
const { children, value, index} = props;
return (
<Typography>
{value === index && <Box p={3}>{children}</Box>}
</Typography>
);
}
class SimpleTabs extends Component {
constructor(props) {
super(props)
console.log(props.color)
this.state = {
value: 0
};
}
handleChange = (event, newValue) => {
this.setState({value: newValue});
};
render() {
return (
<div>
<AppBar position="static">
<Tabs value={this.state.value} onChange={this.handleChange}>
<Tab label="Item One"/>
<Tab label="Item Two"/>
<Tab label="Item Three"/>
</Tabs>
</AppBar>
<TabPanel value={this.state.value} index={0}>
<LineGraph className={styles.graphBox} name={"Guest Count"} url={'http://127.0.0.1:5000/***/***'} initialFilterData={this.props.initialFilterData} filterData={this.props.filterData}/>
</TabPanel>
<TabPanel value={this.state.value} index={1}>
<LineGraph className={styles.graphBox} name={"Total Sales"} url={'http://127.0.0.1:5000/***/***'} initialFilterData={this.props.initialFilterData} filterData={this.props.filterData}/>
</TabPanel>
<TabPanel value={this.state.value} index={2}>
<LineGraph className={styles.graphBox} name={"Avg Check Size"} url={'http://127.0.0.1:5000/***/***'} initialFilterData={this.props.initialFilterData} filterData={this.props.filterData}/>
</TabPanel>
</div>
)}
}
export default SimpleTabs;
Add hidden={value !== index} to the Typography in TabPanel and remove the value === index && condition inside the Typography so that the children of all the tab panels are rendered immediately but hidden (except for the currently selected one).
Example:
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
{...other}
>
<Box p={3}>{children}</Box>
</Typography>
);
}

Resources