I am trying to bind the onChange event of the dropdown menu to set the value to the selection. At the moment I can pass a reference to call handleChange. However since I don't know how to bind the dropdownmenu object to the this. I can't access this.state
Maybe my code structure need to shift to something like the demo: http://www.material-ui.com/#/components/dropdown-menu
However, if i do that, how would I pass in documentList?
So confused.
Gratefully appreciate a little support.
import React from 'react';
import { ListGroup, Alert, Row, Col} from 'react-bootstrap';
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import DropDownMenu from 'material-ui/DropDownMenu'
const handleChange = (event, index, value) =>
{
console.log("handle change (value) ", value);
console.log("handle change (event) ", event);
console.log("handle change (index) ", index);
//How do i set the state of the dropdown object?
}
export const widget = ({ documentList }) => (
documentList.length > 0 ? <Paper style={{ paddingTop: 16,
paddingBottom: 16,
marginTop: 3,
}}>
<form style={{ padding: 30 }} className="add-update-form" onSubmit={() => false}>
<Row>
<Col md={2}>
<DropDownMenu value={2} onChange={handleChange} openImmediately={true}>
<MenuItem value={1} primaryText="Starter" />
<MenuItem value={2} primaryText="Mains" />
<MenuItem value={3} primaryText="Drinks" />
</DropDownMenu>
</Col>
</Row>
The component that you wrote is a stateless functional component, which is supposed to work just like a normal function. There is no state object in this case. You will need a class constructor in order to have access to the state object in your component.
import React from 'react';
import { ListGroup, Alert, Row, Col} from 'react-bootstrap';
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import DropDownMenu from 'material-ui/DropDownMenu';
class Widget extends React.Component {
constructor(props) {
super(props);
}
handleChange(event, index, value) {
console.log("handle change (value) ", value);
console.log("handle change (event) ", event);
console.log("handle change (index) ", index);
//Set States
}
render() {
const documentList = this.props.documentList;
//ES6 object destructing
// const { documentList } = this.props;
return() {
if(!documentList.length > 0) return;
<div>
<Paper style={{ paddingTop: 16,
paddingBottom: 16,
marginTop: 3,
}}>
<form style={{ padding: 30 }} className="add-update-form" onSubmit={() => false}>
<Row>
<Col md={2}>
<DropDownMenu value={2} onChange={handleChange} openImmediately={true}>
<MenuItem value={1} primaryText="Starter" />
<MenuItem value={2} primaryText="Mains" />
<MenuItem value={3} primaryText="Drinks" />
</DropDownMenu>
</Col>
</Row>
</div>
}
}
}
export default Widget;
This is the current version of Widget.js
import React from 'react';
import { ListGroup, Alert, Row, Col} from 'react-bootstrap';
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import Snackbar from 'material-ui/Snackbar';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import DropDownMenu from 'material-ui/DropDownMenu'
import Toggle from 'material-ui/Toggle';
class Widget extends React.Component {
constructor(props) {
super(props);
}
handleChange(event, index, value) {
console.log("handle change (value) ", value);
console.log("handle change (event) ", event);
console.log("handle change (index) ", index);
//Set States
}
styles = {
block: {
maxWidth: 250,
},
toggle: {
marginBottom: 16,
},
thumbOff: {
backgroundColor: '#ffcccc',
},
trackOff: {
backgroundColor: '#ff9d9d',
},
thumbSwitched: {
backgroundColor: 'red',
},
trackSwitched: {
backgroundColor: '#ff9d9d',
},
labelStyle: {
color: 'red',
},
customWidth: {
width: 200,
},
};
render() {
const documentList = this.props.documentList;
return () => {
documentList.length > 0 ?
<Paper style={{
paddingTop: 16,
paddingBottom: 16,
marginTop: 3,
}}>
<form style={{padding: 30}} className="add-update-form" onSubmit={() => false}>
<Row>
<Col md={2}>
<DropDownMenu value={2} onChange={this.handleChange} openImmediately={true}>
<MenuItem value={1} primaryText="Starter" />
<MenuItem value={2} primaryText="Mains" />
<MenuItem value={3} primaryText="Drinks" />
</DropDownMenu>
</Col>
<Col md={2}>
<Toggle
label="Dessert"
labelPosition="right"
style={this.styles.toggle}
/>
</Col>
</Row>
<RaisedButton label="Save" primary={true} style={{marginRight: 15, marginTop: 15}} onClick=""/>
</form>
</Paper> :
<Alert bsStyle="warning">No stuff yet.</Alert>
}
}
}
Widget.propTypes = {
documentList: React.PropTypes.array,
};
export default Widget;
Haven't got a clue why, but managed to get the pages to render. So though i would share the solution..... I think it's soomething to do with prp types and maybe the version of reat i'm using... "react": "^15.6.1", might upgrade later when i get a moment and see what happens. However the page renders so I am happy. Just need to fix the event handling.
import React from 'react';
import { ListGroup, Alert, Row, Col} from 'react-bootstrap';
import Paper from 'material-ui/Paper';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import Snackbar from 'material-ui/Snackbar';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import DropDownMenu from 'material-ui/DropDownMenu'
import Toggle from 'material-ui/Toggle';
export class OrderWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
toggle: true,
},
this.styles = {
block: {
maxWidth: 250,
}
,
toggle: {
marginBottom: 16,
}
,
thumbOff: {
backgroundColor: '#ffcccc',
}
,
trackOff: {
backgroundColor: '#ff9d9d',
}
,
thumbSwitched: {
backgroundColor: 'red',
}
,
trackSwitched: {
backgroundColor: '#ff9d9d',
}
,
labelStyle: {
color: 'red',
}
,
customWidth: {
width: 200,
}
,
}
}
handleChange(event, index, value) {
console.log("handle change (value) ", value);
console.log("handle change (event) ", event);
console.log("handle change (index) ", index);
//Set States
this.state.setValue(value);
}
render() {
return (
<Paper style={{
paddingTop: 16,
paddingBottom: 16,
marginTop: 3,
}}>
<form style={{padding: 30}} className="add-update-form" onSubmit={() => false}>
<Row>
<Col md={2}>
<DropDownMenu value={2} onChange={this.handleChange} openImmediately={true}>
<MenuItem value={1} primaryText="Starter" />
<MenuItem value={2} primaryText="Mains" />
<MenuItem value={3} primaryText="Drinks" />
</DropDownMenu>
</Col>
<Col md={8}>
<TextField floatingLabelText="Units" hintText="Units" name="Units" ref="Units"
key="Units" defaultValue="" fullWidth={false}/>
<TextField floatingLabelText="Price" hintText="Price" name="Price" ref="Price"
key="Price" defaultValue="" fullWidth={false}/>
</Col>
<Col md={2}>
<Toggle
label="Side"
labelPosition="right"
style={this.styles.toggle}
/>
</Col>
</Row>
<RaisedButton label="Save" primary={true} style={{marginRight: 15, marginTop: 15}}
onClick=""/>
</form>
</Paper>
);
}
}
/*
Widget.propTypes = {
documentList: React.PropTypes.array,
};
*/
Related
New to React here and stuck with console errors:
Here's the code I have used. This is a simple to-do list app.
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './components/App';
import 'bootstrap/dist/css/bootstrap.min.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
app.js
import React, {Component} from "react";
import Container from "react-bootstrap/Container";
import {Row, Col, Button, InputGroup, FormControl, ListGroup} from "react-bootstrap";
class App extends Component{
constructor(props){
super(props);
this.state = {
userInput : "",
list: []
}
}
updateInput(value){
this.setState({
userInput: value,
});
}
addItem(){
if(this.state.userInput !== " "){
const userInput = {
id: Math.random(),
value: this.state.userInput
};
const list = [...this.state.list];
list.push(userInput);
this.setState({
list,
userInput: ""
})
}
}
deleteItem(key){
const list = [...this.state.list];
const UpdateList = list.filter(item => item.id !== key);
this.setState({
list:UpdateList,
});
}
render(){
return( <Container>
<Row style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
fontSize: "3rem",
fontWeight: "bolder",
}}
>To Do List</Row>
<hr/>
<Row>
<Col md={{ span: 5, offset: 4}}>
<InputGroup className= "mb-3">
<FormControl
placeholder= "Add Task"
size= "lg"
value= {this.state.userInput}
onChange = {item => this.updateInput(item.target.value)}
aria-label="Add something"
aria-describedby= "basic-addon2"
/>
<InputGroup.Append>
<Button
variant="dark"
size= "lg"
onClick = {() => this.addItem()}
>ADD</Button>
</InputGroup.Append>
</InputGroup>
</Col>
</Row>
<Row>
<Col md= {{ span: 5, offset: 4}}>
<ListGroup>
{}
{this.state.list.map(item => {return(
<ListGroup.Item variant="dark" action
onClick = { () => this.deleteItem(item.id) }>
{item.value}
</ListGroup.Item>
)})}
</ListGroup>
</ Col>
</Row>
</Container>
);
}
}
export default App;
My understanding is that it has something to do with imports but not entirely sure what's causing it. Have tried replacing the third line in app.js with individual imports like import Col from "react-bootstrap/Col",
but still I am getting the same error.
Two things. First React doesn't like the way you are importing the Container component.
import Container from "react-bootstrap/Container";
Import it like you are importing line 3 of the app.
import { Container } from "react-bootstrap";
Second error: There is no InputGroup.Append component in bootstrap as far as I can see and that is what is causing the problem. Try removing it and run.
<InputGroup.Append>
<Button
variant="dark"
size= "lg"
onClick = {() => this.addItem()}
>ADD</Button>
</InputGroup.Append>
For Examples This works. Without the .Append
<InputGroup>
<Button
variant="dark"
size= "lg"
onClick = {() => this.addItem()}
>ADD</Button>
</InputGroup>
Hope, that helps.
The error is with <InputGroup.Append> seems the append is not available , if you remove it , it should be working fine.
<Button variant="dark" size="lg"
onClick={() => this.addItem()}>
ADD
</Button>
We are trying to update our web application from material ui (v4) to mui (v5)
Using the available examples, we have managed for function based components, but it does not seem to work for class based components, and there is very little examples around on class based components and MUI.
Out pattern looks something like the below currently, but none of the styles are applied to the MUI components. The same pattern seems to work on a POC project with functional components.
The container file Login.js
import {connect} from 'react-redux'
import Layout from '../pages/login.js';
import { call } from '../reducers/helloWorld'
import { logIn, logOut } from "../reducers/userManagement"
const mapStateToProps = state => ({
user: state.userManagement.user,
requestState: state.helloWorld.requestState,
})
const mapDispatchToProps = dispatch => ({
helloWorld: () => dispatch(call()),
logIn: (user2) => dispatch(logIn(user2)),
logOut: () => dispatch(logOut()),
})
export default connect(mapStateToProps, mapDispatchToProps)(Layout)
the page file login.js
import React from "react";
import { Typography, Button, FormControlLabel, Checkbox, TextField, Grid, Paper} from '#mui/material';
import { styled } from '#mui/material/styles';
import theme from '../theme';
import classes from '../styles/login';
import logo from '../img/Ergopak-logo.png';
import { Navigate } from 'react-router-dom';
// SXprops
class Login extends React.Component {
state = {
email: "",
password: "",
showPassword: false,
navigateTo: ""
}
componentDidMount(){
const {logOut} = this.props
logOut()
}
goToHome(){
}
handleSubmit(e){
const {logIn} = this.props
console.log("handleSubmit pressed!!")
logIn({name: "Pieka"})
this.setState({navigateTo: <Navigate to="/home"/>})
}
setEmail(){
console.log("handleEmail pressed")
}
setPassword(){
console.log("setPassword pressed")
}
render(){
const {user, requestState} = this.props
const {email, password, showPassword, navigateTo} = this.state
console.log("requestState", requestState)
console.log("user", user)
console.log("that thing is, ", classes(theme).welcome)
return(
<div style={{backgroundColor: "green", height: "100%", position: "fixed", width: "100%"}}>
<Grid container direction = "column" alignItems="stretch" justifyContent="flex-start" style={{backgroundColor: "grey", height: "100%", display: "flex"}} >
{navigateTo}
<Grid item sx = {classes(theme).welcomeDiv}>
<div sx = {classes(theme).welcome} >Welcome To SightPlan </div>
</Grid>
<Grid item >
<img style={{maxHeight: "2em"}} src={logo} alt="logo" />
</Grid>
<Grid item sx = {classes(theme).centerDivs}>
<Typography>Please log in with your email address and password</Typography>
<TextField
sx={{
width: 400
}}
type="email"
value={email}
placeholder="Email"
onChange={(e) => this.setState({email: e.target.value})}
/>
<Grid container direction = "column" alignItems = "center">
<TextField
sx = {classes(theme).loginFields}
type= {showPassword ? "string" : "password"}
variant="standard"
value={password}
placeholder="Password"
onChange={(e) => this.setState({password: e.target.value})}
/>
<FormControlLabel
control={<Checkbox size="small" name="showClosed" />}
checked={showPassword}
label={<span style={{ fontSize: '0.8em' }}> Show password </span>}
onChange={e => this.setState({showPassword: !showPassword})}
style={{margin: "auto"}}
/>
</Grid>
<Button
sx = {classes(theme).loginButton}
type="submit" onClick={this.handleSubmit.bind(this)}
variant="contained"
color="primary"
>
Log In
</Button>
</Grid>
<Paper style = {{backgroundColor: "blue", borderRadius: "0em", display: "flex", flexDirection: "column"}} />
</Grid>
</div>
)}
}
export default styled(Login)({theme})
The styles file login.js
import commonstyles from "./common"
const styles = (theme) => ({
centerDivs: commonstyles(theme).centerDivs,
welcomeDiv: commonstyles(theme).welcomeDiv,
white: commonstyles(theme).white,
logo: {
margin: "auto",
marginTop: "2em",
marginBottom: "2em",
...commonstyles(theme).logo,
},
welcome: {
fontSize: "3em",
fontFamily: "Segoe UI",
color: "white",
marginBottom: "1em",
margin: theme.spacing(2),
textAlign: "center"
},
loginFields: {
...commonstyles(theme).textField,
width: "20em"
},
loginButton: {
...commonstyles(theme).button,
width: "10em"
},
myTextBox: {
...commonstyles(theme).textField,
// "& .MuiInputBase-root": {
// color: 'black',
// borderColor: "green"
// }
}
})
export default styles
the theme file theme.js
import { createTheme } from '#mui/material/styles';
const theme = createTheme({
palette: {
primary: {
main: "#065f92", // blue
},
secondary: {
main: "#F79007", // orange
},
error: {
main: "#d32f2f",
},
warning: {
main: "#ed6c02",
},
info: {
main: "#0288d1",
},
success: {
main: "#2e7d32",
},
},
spacing: (factor) => `${factor}rem`,
});
export default theme;
and finally the App.js file that pulls it all together:
import React from "react";
import { Provider } from 'react-redux'
import theme from './theme';
import { ThemeProvider } from '#mui/material/styles';
import "./App.css";
import store from "./app/store";
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Login from "./containers/Login";
function App() {
return (
<ThemeProvider theme={theme}>
<Provider store={store}>
<div className="App" style={{maxHeight: "80%"}}>
<Router>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/login" element={<Login />} /></Routes>
</Router>
<div style={{clear:"both", "height": "40px"}}></div>
</div>
</Provider>
</ThemeProvider>
);
}
export default App;
I'm trying to figure out how to use Formik field arrays in a react project.
I have one form (glossary) that has 3 Field Arrays within it (one for each of relatedTerms, templates and referenceMaterials).
Each of the field arrays is set out in a separate component. When I only used one of them, I had this working. Adding the next one has caused a problem that I can't solve.
My form has:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {render} from 'react-dom';
import { Link } from 'react-router-dom';
import firebase, {firestore} from '../../../../firebase';
import { withStyles } from '#material-ui/core/styles';
import {
Button,
LinearProgress,
MenuItem,
FormControl,
Divider,
InputLabel,
FormControlLabel,
TextField,
Typography,
Box,
Grid,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from '#material-ui/core';
import MuiTextField from '#material-ui/core/TextField';
import {
Formik, Form, Field, ErrorMessage, FieldArray,
} from 'formik';
import * as Yup from 'yup';
import {
Autocomplete,
ToggleButtonGroup,
AutocompleteRenderInputParams,
} from 'formik-material-ui-lab';
import {
fieldToTextField,
TextFieldProps,
Select,
Switch,
} from 'formik-material-ui';
import RelatedTerms from "./RelatedTerms";
import ReferenceMaterials from "./ReferenceMaterials";
import Templates from "./Templates";
const allCategories = [
{value: 'one', label: 'One'},
{value: 'two', label: 'Two'},
];
function UpperCasingTextField(props: TextFieldProps) {
const {
form: {setFieldValue},
field: {name},
} = props;
const onChange = React.useCallback(
event => {
const {value} = event.target;
setFieldValue(name, value ? value.toUpperCase() : '');
},
[setFieldValue, name]
);
return <MuiTextField {...fieldToTextField(props)} onChange={onChange} />;
}
function Glossary(props) {
const { classes } = props;
const [open, setOpen] = useState(false);
const [isSubmitionCompleted, setSubmitionCompleted] = useState(false);
function handleClose() {
setOpen(false);
}
function handleClickOpen() {
setSubmitionCompleted(false);
setOpen(true);
}
return (
<React.Fragment>
<Button
// component="button"
color="primary"
onClick={handleClickOpen}
style={{ float: "right"}}
variant="outlined"
>
Create Term
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
{!isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Create a defined term</DialogTitle>
<DialogContent>
<DialogContentText>
</DialogContentText>
<Formik
initialValues={{ term: "", definition: "", category: [], context: "", relatedTerms: [], templates: [], referenceMaterials: [] }}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
firestore.collection("glossary").doc().set({
...values,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
})
.then(() => {
setSubmitionCompleted(true);
});
}}
validationSchema={Yup.object().shape({
term: Yup.string()
.required('Required'),
definition: Yup.string()
.required('Required'),
category: Yup.string()
.required('Required'),
context: Yup.string()
.required("Required"),
// relatedTerms: Yup.string()
// .required("Required"),
// templates: Yup.string()
// .required("Required"),
// referenceMaterials: Yup.string()
// .required("Required"),
})}
>
{(props) => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
} = props;
return (
<form onSubmit={handleSubmit}>
<TextField
label="Term"
name="term"
// className={classes.textField}
value={values.term}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.term && touched.term) && errors.term}
margin="normal"
style={{ width: "100%"}}
/>
<TextField
label="Meaning"
name="definition"
multiline
rows={4}
// className={classes.textField}
value={values.definition}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.definition && touched.definition) && errors.definition}
margin="normal"
style={{ width: "100%"}}
/>
<TextField
label="In what context is this term used?"
name="context"
// className={classes.textField}
multiline
rows={4}
value={values.context}
onChange={handleChange}
onBlur={handleBlur}
helperText={(errors.context && touched.context) && errors.context}
margin="normal"
style={{ width: "100%"}}
/>
<Box margin={1}>
<Field
name="category"
multiple
component={Autocomplete}
options={allCategories}
getOptionLabel={(option: any) => option.label}
style={{width: '100%'}}
renderInput={(params: AutocompleteRenderInputParams) => (
<MuiTextField
{...params}
error={touched['autocomplete'] && !!errors['autocomplete']}
helperText={touched['autocomplete'] && errors['autocomplete']}
label="Category"
variant="outlined"
/>
)}
/>
</Box>
<Divider style={{marginTop: "20px", marginBottom: "20px"}}></Divider>
<Box>
<Typography variant="subtitle2">
Add a related term
</Typography>
<FieldArray name="relatedTerms" component={RelatedTerms} />
</Box>
<Box>
<Typography variant="subtitle2">
Add a reference document
</Typography>
<FieldArray name="referenceMaterials" component={ReferenceMaterials} />
</Box>
<Box>
<Typography variant="subtitle2">
Add a template
</Typography>
<FieldArray name="templates" component={Templates} />
</Box>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</Button>
<Button type="submit" disabled={isSubmitting}>
Submit
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</form>
);
}}
</Formik>
</DialogContent>
</React.Fragment>
}
{isSubmitionCompleted &&
<React.Fragment>
<DialogTitle id="form-dialog-title">Thanks!</DialogTitle>
<DialogContent>
<DialogContentText>
We appreciate your contribution.
</DialogContentText>
<DialogActions>
<Button
type="button"
className="outline"
onClick={handleClose}
>
Close
</Button>
{/* <DisplayFormikState {...props} /> */}
</DialogActions>
</DialogContent>
</React.Fragment>}
</Dialog>
</React.Fragment>
);
}
export default Glossary;
Then, each subform is as follows (but replacing relatedTerms for templates or referenceMaterials).
import React from "react";
import { Formik, Field } from "formik";
import { withStyles } from '#material-ui/core/styles';
import {
Button,
LinearProgress,
MenuItem,
FormControl,
InputLabel,
FormControlLabel,
TextField,
Typography,
Box,
Grid,
Checkbox,
} from '#material-ui/core';
import MuiTextField from '#material-ui/core/TextField';
import {
fieldToTextField,
TextFieldProps,
Select,
Switch,
} from 'formik-material-ui';
const initialValues = {
title: "",
description: "",
source: ""
};
class RelatedTerms extends React.Component {
render() {
const {form: parentForm, ...parentProps} = this.props;
return (
<Formik
initialValues={initialValues}
render={({ values, setFieldTouched }) => {
return (
<div>
{parentForm.values.relatedTerms.map((_notneeded, index) => {
return (
<div key={index}>
<TextField
label="Title"
name={`relatedTerms.${index}.title`}
placeholder=""
// className="form-control"
// value={values.title}
margin="normal"
style={{ width: "100%"}}
onChange={e => {
parentForm.setFieldValue(
`relatedTerms.${index}.title`,
e.target.value
);
}}
>
</TextField>
<TextField
label="Description"
name={`relatedTerms.${index}.description`}
placeholder="Describe the relationship"
// value={values.description}
onChange={e => {
parentForm.setFieldValue(
`relatedTerms.${index}.description`,
e.target.value
);
}}
// onBlur={handleBlur}
// helperText={(errors.definition && touched.definition) && errors.definition}
margin="normal"
style={{ width: "100%"}}
/>
<Button
variant="outlined"
color="secondary"
size="small"
onClick={() => parentProps.remove(index)}
>
Remove this term
</Button>
</div>
);
})}
<Button
variant="contained"
color="secondary"
size="small"
style={{ marginTop: "5vh"}}
onClick={() => parentProps.push(initialValues)}
>
Add a related term
</Button>
</div>
);
}}
/>
);
}
}
export default RelatedTerms;
Then when I try to render the data submitted in the form, I have:
import React, { useState, useEffect } from 'react';
import {Link } from 'react-router-dom';
import Typography from '#material-ui/core/Typography';
import ImpactMetricsForm from "./Form";
import firebase, { firestore } from "../../../../firebase.js";
import { makeStyles } from '#material-ui/core/styles';
import clsx from 'clsx';
import ExpansionPanel from '#material-ui/core/ExpansionPanel';
import ExpansionPanelDetails from '#material-ui/core/ExpansionPanelDetails';
import ExpansionPanelSummary from '#material-ui/core/ExpansionPanelSummary';
import ExpansionPanelActions from '#material-ui/core/ExpansionPanelActions';
import ExpandMoreIcon from '#material-ui/icons/ExpandMore';
import Chip from '#material-ui/core/Chip';
import Button from '#material-ui/core/Button';
import Divider from '#material-ui/core/Divider';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
marginTop: '8vh',
marginBottom: '5vh'
},
heading: {
fontSize: theme.typography.pxToRem(15),
},
heading2: {
fontSize: theme.typography.pxToRem(15),
fontWeight: "500",
marginTop: '3vh',
marginBottom: '1vh',
},
secondaryHeading: {
fontSize: theme.typography.pxToRem(15),
color: theme.palette.text.secondary,
textTransform: 'capitalize'
},
icon: {
verticalAlign: 'bottom',
height: 20,
width: 20,
},
details: {
alignItems: 'center',
},
column: {
flexBasis: '20%',
},
columnBody: {
flexBasis: '70%',
},
helper: {
borderLeft: `2px solid ${theme.palette.divider}`,
padding: theme.spacing(1, 2),
},
link: {
color: theme.palette.primary.main,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
},
}));
const Title = {
fontFamily: "'Montserrat', sans-serif",
fontSize: "4vw",
marginBottom: '2vh'
};
const Subhead = {
fontFamily: "'Montserrat', sans-serif",
fontSize: "calc(2vw + 1vh + .5vmin)",
marginBottom: '2vh',
marginTop: '8vh',
width: "100%"
};
function useGlossaryTerms() {
const [glossaryTerms, setGlossaryTerms] = useState([])
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setGlossaryTerms(glossaryTerms)
})
}, [])
return glossaryTerms
}
const GlossaryTerms = () => {
const glossaryTerms = useGlossaryTerms()
const classes = useStyles();
return (
<div style={{ marginLeft: "3vw"}}>
<div className={classes.root}>
{glossaryTerms.map(glossaryTerm => {
return (
<ExpansionPanel defaultcollapsed>
<ExpansionPanelSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1c-content"
id="panel1c-header"
>
<div className={classes.column}>
<Typography className={classes.heading}>{glossaryTerm.term}</Typography>
</div>
<div className={classes.column}>
{glossaryTerm.category.map(category => (
<Typography className={classes.secondaryHeading}>
{category.label}
</Typography>
)
)}
</div>
</ExpansionPanelSummary>
<ExpansionPanelDetails className={classes.details}>
<div className={clsx(classes.columnBody)}>
<div>
<Typography variant="subtitle2" className={classes.heading2}>Meaning</Typography>
<Typography>{glossaryTerm.definition}</Typography>
</div>
<div>
<Typography variant="subtitle2" className={classes.heading2}>Context</Typography>
<div>
<Typography>{glossaryTerm.context}</Typography>
</div>
<div className={clsx(classes.helper)}>
<div>
<Typography variant="caption">Related Terms</Typography>
{glossaryTerm.relatedTerms.map(relatedTerm => (
<Typography variant="body2" className="blogParagraph" key={relatedTerm.id}>
{relatedTerm.title}
</Typography>
))}
</div>
<div>
<Typography variant="caption" >Related Templates</Typography>
{glossaryTerm.templates.map(template => (
<Typography variant="body2" className="blogParagraph" key={template.id}>
{template.title}
</Typography>
))}
</div>
<div>
<Typography variant="caption">Related Reference Materials</Typography>
{glossaryTerm.referenceMaterials.map(referenceMaterial => (
<Typography variant="body2" className="blogParagraph" key={referenceMaterial.id}>
{referenceMaterial.title}
</Typography>
))}
</div>
</div>
</ExpansionPanelDetails>
<Divider />
<ExpansionPanelActions>
{glossaryTerm.attribution}
</ExpansionPanelActions>
</ExpansionPanel>
)
})}
</div>
</div>
);
}
export default GlossaryTerms;
When I try this using only the relatedTerms field array, I can submit data in the form and render the list.
When I add in the next two Field Array components for Templates and ReferenceMaterials, I get an error that says:
TypeError: glossaryTerm.referenceMaterials.map is not a function
Each of the 3 field arrays is a duplicate, where I've only changed the name of the value in the main form. You can see from the screen shot attached that the data within each map from the form fields is the same for each of relatedTerms, templates and referenceMaterials. When I comment out templates and referenceMaterials from the rendered output, everything renders properly. When I comment out relatedTerms and try to render either templates or referenceMaterials, I get the error I reported.
If I remove the templates and referenceMaterials map statements from the rendered output, I can use the form with all 3 field arrays in it. They save properly in firebase. I just can't display them using the method that works for relatedTerms.
Everything seems ok with your code. I suspect that the problem is in the data coming from firebase in useGlossaryTerms, some entries in the glossary collection may not have referenceMaterials or templates fields (maybe from a previous form submit that did not have those yet).
You could :
Run a migration script on the collection to add defaults for those fields if they don't exist.
Add defaults on client side :
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => {
const data = doc.data();
return {
id: doc.id,
...data,
referenceMaterials: data.referenceMaterials || [],
templates: data.templates || []
};
}
setGlossaryTerms(glossaryTerms)
})
On the client side, check if those fields exists before rendering :
{
glossaryTerm.templates ? (
<div>
<Typography variant="caption" >Related Templates</Typography>
{glossaryTerm.templates.map(template => (
<Typography variant="body2" className="blogParagraph" key={template.id}>
{template.title}
</Typography>
))}
</div>
) : null
}
I need to reuse some basic components to avoid code duplication but some examples are not serving me
the problem is here const [cpf, setCpf] = useState (data.cpf);
this data.cpf is getting information from an api but not filling the const as initial value
HOC
const onHandleChange = onChange => event => {
const { value } = event.target;
onChange(value);
};
Field:
export const Field = ({ component: Component, onChange, ...props }) => (
<div>
<Component onChange={onHandleChange(onChange)} {...props} />
</div>
);
InputField:
import React, { useState } from "react";
//Componentes
import { InputGroup, Button, FormControl } from "react-bootstrap";
import EditIcon from "#material-ui/icons/Edit";
import CheckIcon from "#material-ui/icons/Check";
export function InputField({ value, name, type, onChange }) {
const [teste, setTeste] = useState(true);
function click(e) {
setTeste(!teste);
}
return (
<div>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text
style={{
backgroundColor: "white",
borderStyle: "hidden"
}}
>
{name}
</InputGroup.Text>
</InputGroup.Prepend>
<FormControl
style={{ borderStyle: "hidden" }}
disabled={teste}
value={value}
type={type}
onChange={onChange}
/>
<InputGroup.Append>
{teste === true ? (
<Button variant="white" onClick={click}>
<EditIcon />
</Button>
) : (
<Button variant="white" onClick={click}>
<CheckIcon />
</Button>
)}
</InputGroup.Append>
</InputGroup>
</div>
);
}
ViewClient:
import React, { useState } from "react";
//UI-Components
import { Modal, Card, ListGroup, Button } from "react-bootstrap";
import Account from "#material-ui/icons/AccountCircle";
//Reecriação de componentes
import { Field } from "./Components/Input";
import { InputField } from "./Components/InputFieldComponent";
//Redux
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
//Creators
import { Creators as ClientActions } from "../../../redux/store/ducks/cliente";
import { Creators as MapActions } from "../../../redux/store/ducks/map";
import { Creators as CaboActions } from "../../../redux/store/ducks/cabo";
function ViewClient(props) {
const { viewClient } = props.redux.client;
const { data } = viewClient; //Informações do usuario.
const [cpf, setCpf] = useState(data.cpf);
const [name, setName] = useState(data.name);
function handleHideModal() {
const { hideClientViewModal } = props;
hideClientViewModal();
}
function changeActive() {
console.log(cpf);
}
return (
<>
<Modal size="lg" show={viewClient.visible} onHide={handleHideModal}>
<Modal.Header
style={{
justifyContent: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
backgroundColor: "#F7D358"
}}
>
<h6 style={{ fontSize: "10px" }}>{data.created_at}</h6>
<Account
style={{
display: "block",
fontSize: "50px",
marginTop: "10px",
marginBottom: "10px"
}}
/>
<Modal.Title style={{ color: "#585858" }}>{data.name}</Modal.Title>
</Modal.Header>
<Modal.Body style={{ backgroundColor: "#FFFFFF" }}>
<Card style={{ width: "100%" }}>
<Card.Header
style={{ backgroundColor: "#D8D8D8", textAlign: "center" }}
>
Informações do cliente
</Card.Header>
<ListGroup variant="flush">
<ListGroup.Item>
<Field
component={InputField}
name={cpf}
type={"text"}
value={cpf}
onChange={setCpf}
/>
</ListGroup.Item>
<ListGroup.Item>
<Field
component={InputField}
name={"Nome"}
type={"text"}
value={name}
onChange={setName}
/>
</ListGroup.Item>
</ListGroup>
</Card>
</Modal.Body>
<Modal.Footer>
<Button variant="info">Salvar Alterações</Button>
{data.status === null ? (
<Button variant="primary" onClick={changeActive}>
Ativar cliente
</Button>
) : (
<Button variant="danger" onClick={changeActive}>
Desativar
</Button>
)}
<Button variant="danger">Excluir</Button>
<Button variant="secondary">Adicionar Cabo</Button>
<Button variant="secondary">Fechar</Button>
</Modal.Footer>
</Modal>
</>
);
}
const mapStateToProps = state => ({
redux: state
});
//Ações
const mapDispatchToProps = dispatch =>
bindActionCreators(
{ ...ClientActions, ...MapActions, ...CaboActions },
dispatch
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(ViewClient);
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; }}/>