I have a parent component Dashboard.js. Here I have three values of state namely yesterday, lastWeek, lastMonth and I'm passing this value to my child component. Now I want to render my data depending on my child component. The problem is I'm using componentDidMount() lifecycle method which is rendering the child component only once. How do I render the data based on the props passed to the child component? The parent component is a react hook and the child component called DataFetchDetails is a class based component. Attaching their respective codes
Parent Component :- Dashboard.js
import React from 'react';
import { makeStyles } from '#material-ui/styles';
import { Tabs, Tab, Grid } from '#material-ui/core';
import AppBar from '#material-ui/core/AppBar';
import Typography from '#material-ui/core/Typography';
import Box from '#material-ui/core/Box';
import PropTypes from 'prop-types';
import InputLabel from '#material-ui/core/InputLabel';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select'
import {
TotalUsers,
LoggedInUsers,
TimePicker,
UnregisteredUsers
} from './components';
import DataFetchDetails from './components/DataFetchDetails';
const useStyles = makeStyles(theme => ({
root: {
paddingTop: theme.spacing(4),
padding: theme.spacing(4)
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}));
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<Typography
component="div"
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
<Box p={3}>{children}</Box>
</Typography>
);
}
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
const Dashboard = () => {
const classes = useStyles();
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const [period, setPeriod] = React.useState("yesterday");
const handleChange1 = event => {
setPeriod(event.target.value);
};
return (
<div className={classes.root}>
<Select
labelId="demo-simple-select-label"
id="demo-sample-select"
value={time}
onChange={handleChange1}
>
<MenuItem value={"yesterday"}>Previous day</MenuItem>
<MenuItem value={"lastWeek"}>Last Week</MenuItem>
<MenuItem value={"lastMonth"}>Last Month</MenuItem>
</Select>
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value} onChange={handleChange} aria-label="simple tabs example">
<Tab label="CONSENT DETAILS" {...a11yProps(0)} />
<Tab label="ACCOUNT DETAILS" {...a11yProps(1)} />
<Tab label="DATA FETCH DETAILS" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
</TabPanel>
<TabPanel value={value} index={1}>
</TabPanel>
<TabPanel value={value} index={2}>
<DataFetchDetails period={period} handlePeriodChange1={handleChange1} />
</TabPanel>
</div>
</div>
);
};
export default Dashboard;
Child component DataFetchDetails.js :-
import React from 'react';
import {
Card,
CardHeader,
Button,
Divider,
CardContent,
TextField,
CardActions,
FormControl,
InputLabel,
Select,
MenuItem
} from '#material-ui/core';
import Paper from '#material-ui/core/Paper';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableHead from '#material-ui/core/TableHead';
import TableRow from '#material-ui/core/TableRow';
import axios from 'axios';
import 'izitoast/dist/css/iziToast.min.css'; // loading css
import iziToast from 'izitoast/dist/js/iziToast.min.js'; // you have access to iziToast now
import 'izitoast/dist/css/iziToast.min.css';
const url = 'MY_ENDPOINT_URL';
export default class DataFetchDetails extends React.Component {
constructor(props) {
super(props);
this.state = {
items : [],
isLoaded : true,
renderJsx: false,
}
}
componentDidMount() {
this.setState({period: this.props.period});
const periodStatus = {
period : this.props.period
};
{console.log("Props period = ",this.props.period)}
axios.post(url, periodStatus)
.then((response) => {
this.setState({period : this.props.period})
this.setState({items : [response.data]});
.catch((error) => {
console.log("Error");
});
}
render() {
let {isLoaded, items, renderJsx } = this.state;
if(!isLoaded) {
return <div>Loading</div>
}
else {
return (
<div>
<div>
<Card className="Lock-user"
>
<form >
<CardHeader
title=""
/>
<Divider></Divider>
<CardContent id="form-input" className=""
>
</CardContent>
<Divider></Divider>
</form>
</Card>
</div>
<div>
<Card>
<Paper>
<Table>
<TableHead>
<TableRow>
<TableCell> success </TableCell>
<TableCell align="right">failure</TableCell>
<TableCell align="right">inProgress</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ items.map(item => (
<TableRow key={item.success}>
<TableCell component="th" scope="row">
{item.success}
</TableCell>
<TableCell align="right">{item.failure}</TableCell>
<TableCell align="right">{item.inProgress}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{console.log("Props period render = ",this.props.period)}
</Paper>
</Card>
</div>
</div>
);
}
}
}
the backend and the api works fine. I want to re render my child component based on the value of the period. How do I solve this problem?
You you compare the props i.e prevProps and current props(this.props) object inside
ComponentDidUpdate
lifecycle method to re-render the child component based on props.
As ComponentWillReceiveProps is deprecated now.
https://egghead.io/lessons/react-refactor-componentwillreceiveprops-to-getderivedstatefromprops-in-react-16-3
Go through the react lifecycle docs or https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/postrender_with_componentdidupdate.html.
Use componentWillRecieveProps in child component.
componentWillRecieveProps(props) {
// props => new props passed
// this.props => current props
}
I hope that helps.
Related
My app has two buttons, one that displays information and another that hides it. This works as intended, however, some of the images loaded in from the API don't have any data available, and when the button to display the info is pressed, it returns a []
How would I target those that load with an empty array, and add in a string. For example "no information for this breed is available?"
App.js
import './App.css';
import './Dog.js';
import './index.css';
import "./Grid.js";
import NestedGrid from './Grid.js';
import Album from './Grid.js';
import AppLayout from './Grid.js';
function DogApp() {
return (
<div className="dogApp">
<AppLayout />
</div>
);
}
export default DogApp;
Grid.js
import * as React from 'react';
import AppBar from '#mui/material/AppBar';
import Button from '#mui/material/Button';
import Card from '#mui/material/Card';
import CardActions from '#mui/material/CardActions';
import CardContent from '#mui/material/CardContent';
import CardMedia from '#mui/material/CardMedia';
import CssBaseline from '#mui/material/CssBaseline';
import Grid from '#mui/material/Grid';
import Stack from '#mui/material/Stack';
import Box from '#mui/material/Box';
import Toolbar from '#mui/material/Toolbar';
import Typography from '#mui/material/Typography';
import Container from '#mui/material/Container';
import Link from '#mui/material/Link';
import { createTheme, ThemeProvider } from '#mui/material/styles';
import FetchAPI from './FetchAPI';
const cards = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const theme = createTheme();
export default function AppLayout() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<AppBar position="relative">
</AppBar>
<main>
{/* Hero unit */}
<Box
sx={{
bgcolor: 'background.paper',
pt: 8,
pb: 6,
}}
>
<Container maxWidth="sm">
<Stack
sx={{ pt: 4 }}
direction="row"
spacing={2}
justifyContent="center"
>
</Stack>
</Container>
</Box>
<Container sx={{ py: 8 }} maxWidth="md">
{/* End hero unit */}
<Grid container spacing={4}>
{cards.map((card) => (
<Grid item key={card} xs={12} sm={6} md={4}>
<Card
sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<FetchAPI />
<CardContent sx={{ flexGrow: 1 }}>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Container>
</main>
</ThemeProvider>
);
}
FetchAPI.js
import React, { useState, useEffect } from 'react'
const FetchAPI = () => {
const [show, setShow] = useState({});
const [data, setData] = useState([]);
const apiGet = () => {
const API_KEY = "";
fetch(`https://api.thedogapi.com/v1/images/search?limit=2&page=10&order=Desc?API_KEY=${API_KEY}`)
.then((response) => response.json())
.then((json) => {
console.log(json);
setData([...data, ...json]);
});
};
useEffect(() => { //call data when pagee refreshes/initially loads
apiGet();
}, []);
return (
<div>
{data.map((item, id) => (
<div>
<img alt="dog photos" className="dogImg" src={item.url}></img>
{show[id] === false ? <p>{JSON.stringify(item.breeds, null, '\t')}</p> : null}
<button onClick={() => setShow((prev) => ({ ...prev, [id]: false }))}>Learn more about this dog!</button>
<button onClick={() => setShow((prev) => ({ ...prev, [id]: true }))}>Hide information</button>
</div>
))}
</div>
)
}
export default FetchAPI;
You can compare the length of array like:
if(arr.length===0){
return "no information for this breed is available"
}
The above condition is just for understanding you can change the logic as well either you want to return or save in some state.
I have a page ("/paymentsucess"). In this page, I am using the react-countdown-circle-timer component (https://github.com/vydimitrov/react-countdown-circle-timer#props-for-both-reactreact-native). Upon reaching this page ("/paymentsuccess"), countdown beings. After countdown reaches zero, I want to redirect user back to home page ("/").
To achieve this, for the CountdownCircleTimer component, when onComplete, I set the state initialMount to false.
<CountdownCircleTimer
onComplete={() => {
setInitialMount(false);
return [true, 1500];
}}
isPlaying
duration={2}
colors={[
["#004777", 0.33],
["#F7B801", 0.33],
["#A30000", 0.33],
]}
>
{renderTime}
</CountdownCircleTimer>
Given that initialMount is changed, my useEffect (in the paymentsuccess component) will kick in and redirect users to "/". I am using history.push("/") here.
useEffect(() => {
if (initialMount !== true) {
console.log("On change in status,");
console.log(initialMount);
history.push("/");
}
}, [initialMount, history]);
I was able to redirect user back to "/" successfully. But upon reaching "/", they got redirected back to /paymentsuccess again. And then from /paymentsuccess it goes back to / and then the loops continue. Infinite loop.
Any idea what I am doing wrong here? :( I need to stop this loop and lands user back to / and stops there.
I am using Router from react-router-dom and createBrowserHistory from history.
Below is the full code for my paymentsuccess component
import React, { useEffect, useStatem} from "react";
import { useHistory } from "react-router-dom";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import { CountdownCircleTimer } from "react-countdown-circle-timer";
function PaymentSuccess() {
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
}));
const classes = useStyles();
const history = useHistory();
const [initialMount, setInitialMount] = useState(true);
useEffect(() => {
console.log("On component mount, status is");
console.log(initialMount);
}, []);
useEffect(() => {
return () => {
console.log("On component unmount, status is");
setInitialMount(true);
console.log(initialMount);
};
}, []);
useEffect(() => {
if (initialMount !== true) {
console.log("On change in status,");
console.log(initialMount);
history.push("/");
}
}, [initialMount, history]);
const renderTime = ({ remainingTime }) => {
if (remainingTime === 0) {
return <div className="timer">Starting...</div>;
}
return (
<div className="timer">
<div className="value">{remainingTime}</div>
</div>
);
};
return (
<div className={classes.root}>
<Grid container spacing={0}>
<Grid item xs={6} sm={6}>
<Paper
className="paymentSuccessLeftPaper"
variant="outlined"
square
></Paper>
</Grid>
<Grid item xs={6} sm={6}>
<Paper className="paymentSuccessRightPaper" variant="outlined" square>
<h1>Payment Success</h1>
<CountdownCircleTimer
onComplete={() => {
setInitialMount(false);
return [true, 1500];
}}
isPlaying
duration={2}
colors={[
["#004777", 0.33],
["#F7B801", 0.33],
["#A30000", 0.33],
]}
>
{renderTime}
</CountdownCircleTimer>
</Paper>
</Grid>
</Grid>
</div>
);
}
export default PaymentSuccess;
I have checked my "/" page and I don't think there is any logic there redirecting to "/paymentsuccess". The page ("/") code is as below.
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
import Button from "#material-ui/core/Button";
import Link from "#material-ui/core/Link";
function LandingPage() {
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
paper: {
minHeight: "100%",
padding: theme.spacing(0),
textAlign: "center",
color: theme.palette.text.secondary,
},
}));
const classes = useStyles();
return (
<div className={classes.root}>
<Grid container spacing={0}>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips</h1>
<Button variant="contained" color="primary">
<Link href="/photo10">SELECT</Link>
</Button>
</Paper>
</Grid>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips and GIF</h1>
<Button variant="contained" color="primary">
<Link href="/photogif12">
SELECT
</Link>
</Button>
</Paper>
</Grid>
<Grid item xs={4} sm={4}>
<Paper className={classes.paper} variant="outlined" square>
<h1>Photo Strips and Boomerang</h1>
<Button variant="contained" color="primary">
<Link href="/photoboomerang12">
SELECT
</Link>
</Button>
</Paper>
</Grid>
</Grid>
</div>
);
}
export default LandingPage;
Thank you all in advance! Appreciate all the help and advise
UPDATE
Below is Router code
import React from "react";
import { Router, Switch } from "react-router-dom";
import LandingPage from "./components/LandingPage";
import Photo10 from "./components/Photo10";
import PhotoGIF12 from "./components/PhotoGIF12";
import PhotoBoomerang12 from "./components/PhotoBoomerang12";
import PaymentSuccess from "./components/PaymentSuccess";
import DynamicLayout from "./router/DynamicLayout";
import { history } from "./helpers/history";
const App = () => {
return (
<Router history={history}>
<div className="App">
<Switch>
<DynamicLayout
exact
path="/"
component={LandingPage}
layout="LANDING_NAV"
/>
<DynamicLayout
exact
path="/photo10"
component={Photo10}
layout="PHOTO10_PAGE"
/>
<DynamicLayout
exact
path="/photogif12"
component={PhotoGIF12}
layout="PHOTOGIF12_PAGE"
/>
<DynamicLayout
exact
path="/photoboomerang12"
component={PhotoBoomerang12}
layout="PHOTOBOOMERANG12_PAGE"
/>
<DynamicLayout
exact
path="/paymentsuccess"
component={PaymentSuccess}
layout="PAYMENTSUCCESS_PAGE"
/>
</Switch>
</div>
</Router>
);
};
export default App;
Below is the code for the DynamicLayout component
import React from "react";
const DynamicLayout = (props) => {
const { component: RoutedComponent, layout } = props;
const actualRouteComponent = <RoutedComponent {...props} />;
switch (layout) {
case "LANDING_NAV": {
return <>{actualRouteComponent}</>;
}
case "PHOTO10_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PHOTOGIF12_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PHOTOBOOMERANG12_PAGE": {
return <>{actualRouteComponent}</>;
}
case "PAYMENTSUCCESS_PAGE": {
return <>{actualRouteComponent}</>;
}
default: {
return (
<>
<h2>Default Nav</h2>
{actualRouteComponent}
</>
);
}
}
};
export default DynamicLayout;
This problem occurs when you changes value of variable in useEffect and that variable also added in useEffect dependency array, So when ever your useEffect change the value due to presence of that variable in dependency array, it again called the useEffect thus a infinite loop occurs
so just remove 'history' variable from your dependency array
I am working on an app with React and Redux and displaying some data from API in TextInput control. But now I am not able to edit the data in the TextInput. Following is my complete code of the class:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Article from "grommet/components/Article";
import Box from "grommet/components/Box";
import Button from "grommet/components/Button";
import Header from "grommet/components/Header";
import Heading from "grommet/components/Heading";
import Section from "grommet/components/Section";
import AdminMenu from "../../components/Nav/Admin";
import NavControl from "../../components/Nav/Control";
import { getMessage } from "grommet/utils/Intl";
import Notices from "../../components/Notices";
import CheckBox from "grommet/components/CheckBox";
import TextInput from "grommet/components/TextInput";
import { pageLoaded } from "../utils";
import {
recognitionSettingsLoaded,
recognitionSettingsSaved,
} from "../../actions/settings-recognition";
import dashboard from "../../reducers/dashboard";
class Settings extends Component {
constructor(props) {
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount() {
const { dispatch, settingRecognition } = this.props;
console.log(this.props.state);
console.log(dashboard);
dispatch(recognitionSettingsLoaded("2"));
pageLoaded("Configuration");
}
onSave() {
const { survey, dispatch } = this.props;
dispatch(
recognitionSettingsSaved(
this.props.settingRecognition.days,
this.props.settingRecognition.active
)
);
}
handleDaysChange(e) {
const days = e.target.value;
settingRecognition.days = days;
}
handleActiveChange(e) {
const active = e.target.value;
settingRecognition.active = active;
}
render() {
const { dispatch, settingRecognition } = this.props;
console.log("render method");
console.log(settingRecognition);
const { intl } = this.context;
return (
<Article primary={true}>
<Header
direction="row"
justify="between"
size="large"
pad={{ horizontal: "medium", between: "small" }}
>
<NavControl name={getMessage(intl, "Configuration")} />
<AdminMenu />
</Header>
<Box pad={{ horizontal: "medium", vertical: "medium" }}>
<Heading tag="h4" margin="none">
{getMessage(intl, "RecognitionLifetime")}
</Heading>
<Heading tag="h5" margin="none">
{getMessage(intl, "DefineIsRecognitionTemporary")}
</Heading>
<Box direction="row">
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "NewUserActive")}
</Heading>
</Box>
<Heading tag="h3" margin="none">
{getMessage(intl, "HideAfter")}
</Heading>
<Box direction="row">
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>{" "}
<Heading tag="h3" margin="none">
{getMessage(intl, "Days")}
</Heading>
</Box>
<Button
path="/recognition-settings"
label={getMessage(intl, "NewUserSave")}
primary={true}
onClick={() => {
this.onSave();
}}
/>
</Box>
<Notices />
</Article>
);
}
}
Settings.propTypes = {
dispatch: PropTypes.func.isRequired,
settingRecognition: PropTypes.object.isRequired,
};
Settings.contextTypes = {
intl: PropTypes.object,
};
const mapStateToProps = (state) => ({
settingRecognition: state.settingRecognition,
});
export default connect(mapStateToProps)(Settings);
I have created handleDaysChange function which should run on the text change of TextInput control. I have done similar thing for the checkbox and that works fine but I am not able to get it working for the TextInput.
You are not binding your change events.
Try this....
class Settings extends Component {
constructor(props){
super(props);
this.handleDaysChange = this.handleDaysChange.bind(this);
this.handleActiveChange = this.handleActiveChange.bind(this);
}
componentDidMount(){
....
}
......
}
and change this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={(e) => this.handleActiveChange(e)}
/>
To this
<CheckBox
toggle={true}
checked={settingRecognition.active}
onChange={this.handleActiveChange}
/>
same for text input
<TextInput
placeholder="type here"
value={settingRecognition.days.toString()}
onChange={this.handleDaysChange}
/>
You need to set up two-way-binding so that the content of the textInput reflects the prop that you set in your onChange function. Try giving your textInput a property of value={this.settingRecognition.days}
The problem is that I can't get the value customInput and customSelect and write it to the state? I can’t show all the code I’m trying to connect react dashboard material-ui. If I do the same with normal input and select I get data in state.
Can someone help with this? I can not give a working example, too much code...
import React from "react";
import { connect } from 'react-redux';
// #material-ui/core components
import withStyles from "#material-ui/core/styles/withStyles";
// core components
import GridItem from "components/Grid/GridItem.jsx";
import GridContainer from "components/Grid/GridContainer.jsx";
import CustomInput from "components/CustomInput/CustomInput.jsx";
import Button from "components/CustomButtons/Button.jsx";
import Card from "components/Card/Card.jsx";
import CardHeader from "components/Card/CardHeader.jsx";
import CardBody from "components/Card/CardBody.jsx";
import CardFooter from "components/Card/CardFooter.jsx";
import FormControl from "#material-ui/core/FormControl/FormControl";
import CustomSelect from "../../components/CustomSelect/CustomSelect";
import "../../components/CustomSelect/Select.css";
class NewExercise extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Enter text',
};
}
handleChange = (event) => {
this.setState({
value: event.target.value,
});
};
handleClick = () => {
this.setState({
value: '',
});
console.log(this.state);
};
render() {
console.log('store', this.props.newExercise);
const { classes } = this.props;
return (
<div>
<GridContainer>
<GridItem xs={12} sm={12} md={12} lg={12}>
<form >
<Card>
<CardHeader color="primary">
<h4 className={classes.cardTitleWhite}>Create new exercise</h4>
<p className={classes.cardCategoryWhite}>Please, add a new exercise name and measurement type</p>
</CardHeader>
<CardBody>
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
<CustomInput
value={this.state.value}
onChange={this.handleChange}
labelText="Exercise Name"
id="exercise"
formControlProps={{
fullWidth: true
}}
/>
</GridItem>
<GridItem xs={12} sm={12} md={12}>
<FormControl style={{width: "100%"}} className={classes.formControl}>
<div className="materialSelect">
<CustomSelect
labelText="Measurement"
id="custom-select"
formControlProps={{
fullWidth: true
}}
>
<option value="kg">kilograms</option>
<option value="min">minutes</option>
<option value="m">meters</option>
</CustomSelect>
</div>
</FormControl>
</GridItem>
</GridContainer>
</CardBody>
<CardFooter>
<Button color="primary" onClick={this.handleClick}> Create Exercise</Button>
</CardFooter>
</Card>
</form>
</GridItem>
</GridContainer>
</div>
);
}
}
export default connect (
state => ({
newExercise: state
}),
dispatch => ({})
) (withStyles(styles)(NewExercise));
// Custom Input of material-ui dashboard react
import React from "react";
import classNames from "classnames";
import PropTypes from "prop-types";
// #material-ui/core components
import withStyles from "#material-ui/core/styles/withStyles";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Input from "#material-ui/core/Input";
// #material-ui/icons
import Clear from "#material-ui/icons/Clear";
import Check from "#material-ui/icons/Check";
// core components
import customInputStyle from "assets/jss/material-dashboard-
react/components/customInputStyle.jsx";
function CustomInput({ ...props }) {
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
success
} = props;
const labelClasses = classNames({
[" " + classes.labelRootError]: error,
[" " + classes.labelRootSuccess]: success && !error
});
const underlineClasses = classNames({
[classes.underlineError]: error,
[classes.underlineSuccess]: success && !error,
[classes.underline]: true
});
const marginTop = classNames({
[classes.marginTop]: labelText === undefined
});
return (
<FormControl
{...formControlProps}
className={formControlProps.className + " " + classes.formControl}
>
{labelText !== undefined ? (
<InputLabel
className={classes.labelRoot + labelClasses}
htmlFor={id}
{...labelProps}
>
{labelText}
</InputLabel>
) : null}
<Input
classes={{
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
{...inputProps}
/>
{error ? (
<Clear className={classes.feedback + " " + classes.labelRootError} />
) : success ? (
<Check className={classes.feedback + " " + classes.labelRootSuccess} />
) : null}
</FormControl>
);
}
CustomInput.propTypes = {
classes: PropTypes.object.isRequired,
labelText: PropTypes.node,
labelProps: PropTypes.object,
id: PropTypes.string,
inputProps: PropTypes.object,
formControlProps: PropTypes.object,
error: PropTypes.bool,
success: PropTypes.bool
};
export default withStyles(customInputStyle)(CustomInput);
check this example:
//custom component
import React from 'react'
import PropTypes from 'prop-types'
import Radio from '#material-ui/core/Radio'
export const BmRadio = (props) => {
return <Radio onClick={this.props.onClick} {...props} />
}
BmRadio.defaultProps = {
color: 'primary'
}
BmRadio.propTypes = {
onClick: PropTypes.func,
color: PropTypes.oneOf(['default', 'primary', 'secondary']),
icon: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
checkedIcon: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
value: PropTypes.string,
disabled: PropTypes.bool,
onChange: PropTypes.func,
type: PropTypes.string
}
//main component
import React, {PureComponent} from 'react'
import {BmRadio} from '#components'
class MainComponent extends PureComponent {
handleOnClick =(event)=>{
console.log(event) //event from Radio button then click in main component
}
render(){
return(
<BmRadio onClick={this.handleOnClick}/>
)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
try event.currentTarget.value. Maybe help if i understand your question correctly
if you want to get value from custom component, you need to pass onChange property in custom component.
const {
classes,
formControlProps,
labelText,
id,
labelProps,
inputProps,
error,
success
onChange //get like props
} = props;
//and in component
<Input
classes={{
root: marginTop,
disabled: classes.disabled,
underline: underlineClasses
}}
id={id}
onChange={onChange}
{...inputProps}
/>
//and in place, where you render custom component create change handler and pass it in this component like onChange={this.handleOnChange}
handleOnChange = (event) => {
this.setState({
value = event.target.value
})
}
I have a sample code for App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Child from './child';
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<Child ref={instance => { this.child = instance; }}/>
<button onClick={() => { this.child.onAlert(); }}>Click</button>
</div>
);
}
}
export default App;
And child component like
import React, { Component } from 'react';
class Child extends Component {
state = { }
onAlert =()=>
alert("hey");
render() {
return (
<div> IM kid</div>
);
}
}
export default Child;
here when I click on button in App.js I am able to get the expected output
i.e., i am able to call the child function onAlert()
I am using the same scenario in material-ui and react where I need to trigger Drawer Component from Toolbar Component
and my code is like Titlebar.js below code is my parent component here
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Toolbar from 'material-ui/Toolbar'
import AppBar from 'material-ui/AppBar';
import Typography from 'material-ui/Typography';
import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import Child from './child';
const styleSheet = createStyleSheet('Titlebar', () => ({
root: {
position: 'relative',
width: '100%',
},
appBar: {
position: 'relative',
},
flex: {
flex: 1,
}
}));
class TitleBar extends Component {
render() {
const classes = this.props.classes;
return (
<div className={classes.root}>
<AppBar className={classes.appBar}>
<Toolbar>
<IconButton contrast onClick={() => { this.child.handleLeftOpen(); }}>
<MenuIcon />
</IconButton>
<Typography type="title" colorInherit className={classes.flex}>Productivity Dashboard</Typography>
</Toolbar>
</AppBar>
<Child ref={instance => { this.child = instance; }}/>
</div>
)
}
}
TitleBar.PropTypes={
classes:PropTypes.object.isRequired,
}
export default withStyles(styleSheet)(TitleBar);
and my child component code Child.js is below
import React, { Component } from 'react';
import { withStyles, createStyleSheet } from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Divider from 'material-ui/Divider';
import InboxIcon from 'material-ui-icons/Inbox';
import DraftsIcon from 'material-ui-icons/Drafts';
import StarIcon from 'material-ui-icons/Star';
import SendIcon from 'material-ui-icons/Send';
import MailIcon from 'material-ui-icons/Mail';
import DeleteIcon from 'material-ui-icons/Delete';
import ReportIcon from 'material-ui-icons/Report';
const styleSheet = createStyleSheet('Child', {
list: {
width: 250,
flex: 'initial',
},
listFull: {
width: 'auto',
flex: 'initial',
},
});
class Child extends Component {
state = {
open: {
top: false,
left: false,
bottom: false,
right: false,
},
}
handleLeftOpen = () =>{
console.log("im here")
this.toggleDrawer('left', true);
}
handleLeftClose = () => this.toggleDrawer('left', false);
toggleDrawer = (side, open) => {
const drawerState = {};
drawerState[side] = open;
this.setState({ open: drawerState });
};
render() {
const classes=this.props.classes;
return (
<Drawer
open={this.state.open.left}
onRequestClose={this.handleLeftClose}
onClick={this.handleLeftClose}
>
<List className={classes.list} disablePadding>
<ListItem button>
<ListItemIcon>
<InboxIcon />
</ListItemIcon>
<ListItemText primary="Inbox" />
</ListItem>
<ListItem button>
<ListItemIcon>
<StarIcon />
</ListItemIcon>
<ListItemText primary="Starred" />
</ListItem>
<ListItem button>
<ListItemIcon>
<SendIcon />
</ListItemIcon>
<ListItemText primary="Send mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DraftsIcon />
</ListItemIcon>
<ListItemText primary="Drafts" />
</ListItem>
</List>
<Divider />
<List className={classes.list} disablePadding>
<ListItem button>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText primary="All mail" />
</ListItem>
<ListItem button>
<ListItemIcon>
<DeleteIcon />
</ListItemIcon>
<ListItemText primary="Trash" />
</ListItem>
<ListItem button>
<ListItemIcon>
<ReportIcon />
</ListItemIcon>
<ListItemText primary="Spam" />
</ListItem>
</List>
</Drawer>
);
}
}
export default withStyles(styleSheet)(Child);
Here I am calling handleLeftOpen() function from my parent when I click on the IconButton in the Tiltlbar Component I am not getting the expected output. I am getting error like below in my console
Uncaught TypeError: Cannot read property 'handleLeftOpen' of null
at onClick (http://localhost:3000/static/js/bundle.js:90993:50)
at Object.ReactErrorUtils.invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:17236:17)
at executeDispatch (http://localhost:3000/static/js/bundle.js:17019:22)
at Object.executeDispatchesInOrder (http://localhost:3000/static/js/bundle.js:17042:6)
at executeDispatchesAndRelease (http://localhost:3000/static/js/bundle.js:16430:23)
at executeDispatchesAndReleaseTopLevel (http://localhost:3000/static/js/bundle.js:16441:11)
at Array.forEach (native)
at forEachAccumulated (http://localhost:3000/static/js/bundle.js:17339:10)
at Object.processEventQueue (http://localhost:3000/static/js/bundle.js:16644:8)
at runEventQueueInBatch (http://localhost:3000/static/js/bundle.js:24266:19)
please check the code and let me know if anything need to be changed
The difference here is that in your first example you export:
export default Child;
In the second example you export:
export default withStyles(styleSheet)(Child);
This returns a decorated component, so the ref is put on this decorated component and not your Child component. To solve this issue the decorated component accepts a property called innerRef so you can pass a ref to your own component. So to solve this you change:
<Child ref={instance => { this.child = instance; }}/>
to
<Child innerRef={instance => { this.child = instance; }}/>