React when change state from child, the parent reset the state - javascript

I have the component in the snippet below
import { FC, useState, useEffect, createContext, useContext } from 'react';
import { Button } from '#swvl/button';
type option = {
name: string;
icon?: JSX.Element;
};
type SwitchContentContextType = {
options: option[];
setOptions: (option: option[]) => void;
};
type SwitchContentCompoundTypes = {
Content: FC<option>;
};
const SwitchContentContext = createContext({} as SwitchContentContextType);
export const SwitchContent: FC & SwitchContentCompoundTypes = ({ children }) => {
const [options, setOptions] = useState<option[]>([]);
useEffect(() => {
console.log('options', options);
}, [options]);
return (
<SwitchContentContext.Provider value={{ options, setOptions }}>
<div
sx={{
backgroundColor: 'secondary-light-90',
display: 'inline-flex',
borderRadius: '20px',
border: '1px solid transparent',
}}
>
{options.map((option, index) => {
return (
<Button
key={index}
sx={{
backgroundColor: 'secondary-light-90',
borderRadius: 'inherit',
color: 'content-quarternary',
'&:hover, &:active': {
bg: 'secondary',
borderRadius: '20px',
color: 'primary-light-100',
},
'& span': { variant: 'text.p-small' },
}}
>
{option.name}
</Button>
);
})}
</div>
{children}
</SwitchContentContext.Provider>
);
};
const Content: SwitchContentCompoundTypes['Content'] = ({ children, name, icon, ...rest }) => {
const { options, setOptions } = useContext(SwitchContentContext);
useEffect(() => {
const optionsCopy = [...options];
optionsCopy.push({ name, icon });
setOptions(optionsCopy);
}, []);
return <div {...rest}>{children}</div>;
};
SwitchContent.Content = Content;
when try to push a new option top options state it should has 2 members but it only has the last one. it seems that change the options re-render the parent and this cause to redefine the state, but what I know is re-render will not reset the state so I need to know how to cause and how to fix this
below is how I use the component
<SwitchContent>
<SwitchContent.Content name="Regular mode">
<p>Content 1</p>
</SwitchContent.Content>
<SwitchContent.Content name="B2B mode">
<p>Content 2</p>
</SwitchContent.Content>
</SwitchContent>
I try to console log the options value in useEffect in the parent component like below
useEffect(() => {
console.log('options', options);
}, [options]);
and got below result

Related

How to change prop of Material UIs Button component based on screen size breakpoint when using a class component

Im using Material UI's Button component and would like to conditionally determine the variant of the button. That is, on screens medium and up, the variant should be 'outlined' and when the screen is smaller than medium, the variant type should be none. I am using a class component. I have done my research to see what others have done. I have seen the method of using useMediaQuery method but that would require me to change the component to a functional component. Im not sure if I know if that is a good idea because the component is rather a large one and that means will be a bit confusing to convert. I also tried using a ternary operator like this:
const variantType = theme.breakpoints.down("md") ? '' : 'outline';
<Button variant={variantType}>
Food Pick
</Button>
But this didnt do the job. Is there any other way to accomplish this?
Update: Here is my code after trying to wrap the component but in a functional component but its giving an error:
import { withStyles, withTheme, useTheme } from '#material-ui/core';
import PropTypes from 'prop-types';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import { PulseLoader } from 'react-spinners';
import Icon from '#material-ui/core/Icon';
import Chip from '#material-ui/core/Chip';
import useMediaQuery from '#material-ui/core/useMediaQuery';
const styles = theme => ({
beta: {
height: '17px',
fontSize: '12px',
marginLeft: '10px'
},
scrollContainer: {
flex: 1,
maxHeight: '400px',
minHeight: '300px',
overflowY: 'auto'
},
progressContainer: {
height: '350px',
},
modalDescription: {
color: theme.palette.text.secondary,
marginTop: '20px',
},
button: {
marginTop: '20px',
marginLeft: '10px',
marginRight: '10px',
},
smartSuggestContainer: {
textAlign: 'right',
paddingRight: '35px',
[theme.breakpoints.down('xs')]: {
display: 'flex',
justifyContent: 'center',
flexDirection: 'row',
margin: '40px 0'
},
},
});
export default function MyComponentWrapper({ ...rest }) {
const theme = useTheme();
const mediumScreen = useMediaQuery(theme.breakpoints.down('md'));
return <FoodDescriptions {...rest} mediumScreen={mediumScreen} />;
}
class FoodDescriptions extends Component {
static PAGE_SIZE = 25;
constructor(props) {
super(props);
this.state = {
showFoodDescriptionsModal: false,
dataLoaded: false,
data: [],
page: 0,
sortBy: 'return',
sortDir: 'desc',
};
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeypress);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeypress);
}
fetchData = async (page, sortBy, sortDir) => {
const { currentBotId } = this.props;
const offset = page * FoodDescriptions.PAGE_SIZE;
const data = await getFoodDescriptions(currentBotId, sortBy, sortDir, FoodDescriptions.PAGE_SIZE, offset);
this.setState({
dataLoaded: true,
data,
});
};
handleKeypress = (e) => {
const { showSnartConfigsModal } = this.state;
const { key } = e;
if (key === 'Escape' && showSnartConfigsModal) {
this.closeModal();
}
};
applyConfig = (botId, params) => {
const { updateConfig, botConfig, actions } = this.props;
updateConfig({ name: botConfig.name, config: Object.assign(botConfig.config, params) });
this.closeModal();
actions.showNotification({ data: 'Configuration has been applied' });
};
openModal = () => {
const { page, sortBy, sortDir } = this.state;
this.fetchData(page, sortBy, sortDir);
this.setState({
showFoodDescriptionsModal: true,
});
};
closeModal = () => {
this.setState({
showFoodDescriptionsModal: false,
});
};
changePage = (page) => {
const { sortBy, sortDir } = this.state;
this.setState({
page,
dataLoaded: false
}, () => this.fetchData(page, sortBy, sortDir));
};
changeSortBy = (sortBy) => {
/* eslint-disable-next-line prefer-destructuring */
let sortDir = this.state.sortDir;
if (sortBy === this.state.sortBy) {
sortDir = (this.state.sortDir === 'desc') ? 'asc' : 'desc';
}
this.setState({
sortBy,
sortDir,
dataLoaded: false,
}, () => this.fetchData(this.state.page, sortBy, sortDir));
};
renderEmptyState() {
const { classes } = this.props;
return (
<Grid container alignItems="center" justify="center" className={classes.progressContainer}>
<Typography className={classes.noCoinsText}>No configurations found</Typography>
</Grid>
);
}
renderLoader() {
const { classes } = this.props;
return (
<Grid container alignItems="center" justify="center" className={classes.progressContainer}>
<PulseLoader size={6} color="#52B0B0" loading />
</Grid>
);
}
renderTable() {
const { data, page } = this.state;
return (
<StrategiesTable
strategies={data}
onClickCopy={this.applyConfig}
page={page}
changePage={this.changePage}
sortBy={this.changeSortBy} />
);
}
render() {
const {
classes, userFeatures, botConfig, theme
} = this.props;
const { showFoodDescriptionsModal, dataLoaded } = this.state;
if (!botConfig) {
return null;
}
return (
<Fragment>
<div className={classes.smartSuggestContainer}>
<Button
name="discoverConfigs"
variant={theme.breakpoints.down(600) ? '' : 'outlined'}
color="primary"
size="small"
disabled={!userFeatures['smart_suggest_backtests'.toUpperCase()] || botConfig.status.toLowerCase() === STATUSES.RUNNING}
onClick={this.openModal}>
Food Buy
</Button>
</div>
</Fragment>
);
}
}
function mapStateToProps(state) {
return {
userFeatures: state.global.paywall.features,
};
}
function mapDispatchToProps(dispatcher) {
return {
actions: {
...bindActionCreators({
showNotification,
}, dispatcher)
}
};
}
connect(mapStateToProps, mapDispatchToProps)(withTheme()(withStyles(styles)(FoodDescriptions)));
Why don't you just create a functional component using useMediaQuery and returning the responsive Button ?

Passing props to modal passes every object

I'm not 100% sure what's going on here. I've got a display component that displays a bunch of cards, using a map based on my database - On the card is an edit button that pops a modal up, passing props over to the edit form.. Here's kinda how it looks:
import React, { useState } from 'react'
import { useQuery, useMutation } from '#apollo/client'
import { GET_ALL_PROJECTS, REMOVE_PROJECT } from '../helpers/queries'
import { makeStyles } from '#material-ui/core/styles'
import DeleteIcon from '#material-ui/icons/Delete'
import EditIcon from '#material-ui/icons/Edit'
import AddForm from './AddForm'
import EditForm from './EditForm'
import AlertMessage from '../Alerts/AlertMessage'
import { Grid, Typography, Card, CardActionArea, CardActions, CardContent, CardMedia, Button, Modal, Backdrop, Fade } from '#material-ui/core'
const useStyles = makeStyles((theme) => ({
modal: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
paper: {
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
const DisplayProjects = () => {
const styles = useStyles()
const [deleteItem] = useMutation(REMOVE_PROJECT)
const { loading, error, data } = useQuery(GET_ALL_PROJECTS)
const [status, setStatusBase] = useState('')
const [resultMessage, setResultMessage] = useState('')
const [addOpen, setAddOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const onDelete = (id, e) => {
e.preventDefault()
deleteItem({
variables: { id },
refetchQueries: [{ query: GET_ALL_PROJECTS }]
}).then(
res => handleSuccess(res),
err => handleError(err)
)
}
// Handles Result of the Delete Operation
const handleSuccess = (res) => {
console.log(res.data.deleteProject.proj_name)
// console.log('success!');
setResultMessage(res.data.deleteProject.proj_name)
setStatusBase({
msg: `Successfully Deleted ${resultMessage}`,
key: Math.random()
})
}
const handleError = (err) => {
console.log('error')
}
//Handles the Modal for Add Project
const handleAddOpen = () => {
setAddOpen(true);
};
const handleAddClose = () => {
setAddOpen(false);
};
//Handles the Modal for Edit Project
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
if (loading) return '...Loading'
if (error) return `Error: ${error.message}`
return (
<div>
<div style={{ marginTop: 20, padding: 30 }}>
<Grid container spacing={8} justify='center' alignItems='center'>
{data.projects.map(p => {
return (
<Grid item key={p._id}>
<Card >
<CardActionArea>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<CardMedia
style={{ width: 400, height: 100, paddingTop: 10, }}
component='img'
alt='Project Image'
height='140'
image={require('../../images/html-css-javascript-lg.jpg')}
/>
</div>
<CardContent >
<Typography gutterBottom variant='h5' component="h2">
{p.proj_name}
</Typography>
<Typography component='p'>
{p.description}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button>
<DeleteIcon onClick={e => onDelete(p._id, e)} />
</Button>
<Button onClick={handleEditOpen}>
<Modal
open={editOpen}
onClose={handleEditClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={editOpen}>
<div className={styles.paper}>
<EditForm
id={p._id}
close={handleEditClose}
name={p.proj_name}
desc={p.description}
gh={p.gh_link}
live={p.live_link}
img={p.image_url}
/>
</div>
</Fade>
</Modal>
<EditIcon />
</Button>
</CardActions>
</Card>
{ status ? <AlertMessage key={status.key} message={status.msg} /> : null}
</Grid>
)
}
)}
</Grid>
<Button type='button' onClick={handleAddOpen}>Add Project</Button>
<Modal
open={addOpen}
onClose={handleAddClose}
closeAfterTransition
BackdropComponent={Backdrop}
className={styles.modal}
>
<Fade in={addOpen}>
<div className={styles.paper}>
<AddForm close={handleAddClose} />
</div>
</Fade>
</Modal>
</div>
</div >
)
}
export default DisplayProjects
And here's the form. I've destructured out the props into variables and placed them into a state object called details, so they can be overwritten and submitted to the database..
import React, { useState } from 'react'
import { useParams } from 'react-router-dom'
import { useMutation, useQuery } from '#apollo/client'
import { EDIT_PROJECT, GET_ALL_PROJECTS, GET_PROJECT_BY_ID} from '../helpers/queries'
const AddForm = (props) => {
const params = useParams()
const id = params.toString()
// console.log(id);
const [editProjectItem] = useMutation(EDIT_PROJECT)
const {loading, data, error} = useQuery(GET_PROJECT_BY_ID, {
variables: {
id
},
})
const [details, setDetails] = useState({})
if (loading) return '...Loading';
if (error) return <p>ERROR: {error.message}</p>;
if (!data) return <p>Not found</p>;
setDetails(data.projectById)
console.log(data.projectById)
const submitForm = e => {
e.preventDefault()
try {
editProjectItem({
variables: { id, proj_name, description, gh_link, live_link, image_url},
refetchQueries: [{query: GET_ALL_PROJECTS}]
})
}
catch (err) {
console.log('You Goofed')
}
// setDetails({
// proj_name: '',
// description: '',
// gh_link: '',
// live_link: '',
// image_url: ''
// })
props.close()
}
const changeDetails = (e) => {
setDetails({
...details,
[e.target.name]: e.target.value
})
}
const {_id, proj_name, description, gh_link, live_link, image_url} = details
return (
<div key = {_id}>
<h2>Edit {proj_name}</h2>
<form onSubmit = {submitForm} >
<label>
Project Name:
<input
name = 'proj_name'
value = {proj_name}
onChange = {changeDetails}
/>
</label>
<label>Description</label>
<input
name = 'description'
value = {description}
onChange = {changeDetails}
/>
<label>GitHub Link</label>
<input
name = 'gh_link'
value = {gh_link}
onChange = {changeDetails}
/>
<label>Live Link</label>
<input
name = 'live_link'
value = {live_link}
onChange = {changeDetails}
/>
<label>Preview Image</label>
<input
name = 'image_url'
value = {image_url}
onChange = {changeDetails}
/>
<button type = 'submit'>Submit</button>
</form>
</div>
)
}
export default AddForm
The problem I'm running into, is that when I access the modal, the props are sent from literally EVERY Object, instead of the one, and displays the data for the last record instead of the one I want to edit You can see what happens here (I logged props.id in order to test) https://imgur.com/a/pcEKl89
What did I miss? (Disclaimer: I am still a student, and learning the craft.. be gentle on my code please)
EDIT: I just realized that I didn't indicate that this is the final form of the EditForm component. I haven't added the logic in to make the updates yet, I just wanted to get the data showing properly first.
EDIT2: I made some changes to how the ID is passed over, I was already using React-Router, so I went ahead and made a route to /edit/:id and then using useParams(), I got the ID that way. It seems to be working, however now I'm getting a Too many re-renders message. Updated the AddForm code above to reflect the changes..
I figured out the re-render issue.. it was as simple as dropping the setDetails function into a useEffect Hook:
useEffect(()=> {
if(data){
setDetails(data.projectById)
}
},[data])

React: map is not a function on an empty array

I am trying to add items to a shopping cart.
I have declared cart as an empty array and I am still getting the error:
TypeError: cart.map is not a function ProductContext.js:34
addItemToCart ProductContext.js:34
addItemHandler ProductCard.js:47
onClick ProductCard.js:60
Here is my context:
// ProductContext.js
import React, { createContext, useState } from "react"
import { graphql, useStaticQuery } from "gatsby"
const ProductContext = createContext({
cart: [],
addItem: () => {}
})
const Provider = ({ children }) => {
const [cart, setCart] = useState([]);
const [productsList] = useState(
useStaticQuery(
graphql`
query SkusForProduct {
skus: allStripeSku(sort: { fields: [price] }) {
edges {
node {
id
currency
price
attributes {
name
}
}
}
}
}
`
)
)
const addItemToCart = skuId => {
let itemExisted = false
let updatedCart = cart.map(item => {
if (skuId === item.sku) {
itemExisted = true
return { sku: item.sku, quantity: ++item.quantity }
} else {
return item
}
})
if (!itemExisted) {
updatedCart = [...updatedCart, { sku: skuId, quantity: 1 }]
}
setCart({ cart: updatedCart })
localStorage.setItem('stripe_checkout_items', JSON.stringify(updatedCart))
}
return (
<ProductContext.Provider value={{ skus: productsList.skus, addItem: addItemToCart }}>
{children}
</ProductContext.Provider>
)
}
export { ProductContext, Provider }
And my ProductCard
// ProductCard.js
import React, { useState, useContext } from "react"
import { ProductContext } from "../context/ProductContext"
const cardStyles = {
display: "flex",
flexDirection: "column",
justifyContent: "space-around",
alignItems: "flex-start",
padding: "1rem",
marginBottom: "1rem",
boxShadow: "5px 5px 25px 0 rgba(46,61,73,.2)",
backgroundColor: "#fff",
borderRadius: "6px",
maxWidth: "300px",
}
const buttonStyles = {
fontSize: "13px",
textAlign: "center",
color: "#fff",
outline: "none",
padding: "12px",
boxShadow: "2px 5px 10px rgba(0,0,0,.1)",
backgroundColor: "rgb(255, 178, 56)",
borderRadius: "6px",
letterSpacing: "1.5px",
}
const formatPrice = (amount, currency) => {
let price = (amount / 100).toFixed(2)
let numberFormat = new Intl.NumberFormat(["en-US"], {
style: "currency",
currency: currency,
currencyDisplay: "symbol",
})
return numberFormat.format(price)
}
const ProductCard = () => {
const [disabled] = useState(false)
const [buttonText, setButtonText] = useState("Add to Cart")
const [paymentMessage, setPaymentMessage] = useState("")
const skus = useContext(ProductContext).skus
const addItem = useContext(ProductContext).addItem
const addItemHandler = (event, skuId, quantity = 1) => {
addItem(skuId);
}
return (
<div>
{skus.edges.map(({ node: sku }) => {
return (
<div style={cardStyles} key={sku.id}>
<h4>{sku.attributes.name}</h4>
<p>Price: {formatPrice(sku.price, sku.currency)}</p>
<button
style={buttonStyles}
onClick={event => addItemHandler(event, sku.id)}
disabled={disabled}
>
{buttonText}
</button>
{paymentMessage}
</div>
)
})}
</div>
)
}
export default ProductCard
I have logged the output of cart in the addItemToCart function and I get an empty array.
Not sure what I've done wrong.
Any help would be greatly appreciated!
Here is the problem line of code:
setCart({ cart: updatedCart })
After that runs, cart will no longer be an array (it will be an object with the property "cart", which is an array) and therefore not have a map function.

React Beginner Question: Textfield Losing Focus On Update

I wrote a component that is supposed to list out a bunch of checkboxes with corresponding textfields. When you click on the checkboxes, or type in the fields it's meant to update state.
The textbox is working ok, but when I type in the fields, it updates state ok, but I lose focus whenever I tap the keyboard.
I realized this is probably due to not having keys set, so I added keys to everything but it still is losing focus. At one point I tried adding in stopPropegation on my events because I thought maybe that was causing an issue?? I'm not sure.. still learning...didn't seem to work so I removed that part too.
Still can't seem to figure out what is causing it to lose focus... does anyone have any advice/solves for this issue?
I consolidated my code and cut out the unnecessary bits to make it easier to read. There are three relevant JS files.. please see below:
I'm still a beginner/learning so if you have useful advice related to any part of this code, feel free to offer. Thanks!
App.js
import React, { Component } from 'react';
import Form from './Form'
class App extends Component {
constructor() {
super();
this.state = {
mediaDeliverables: [
{label: 'badf', checked: false, quantity:''},
{label: 'adfadf', checked: false, quantity:''},
{label: 'adadf', checked: false, quantity:''},
{label: 'addadf', checked: false, quantity:''},
{label: 'adfdes', checked: false, quantity:''},
{label: 'hghdgs', checked: false, quantity:''},
{label: 'srtnf', checked: false, quantity:''},
{label: 'xfthd', checked: false, quantity:''},
{label: 'sbnhrr', checked: false, quantity:''},
{label: 'sfghhh', checked: false, quantity:''},
{label: 'sssddrr', checked: false, quantity:''}
]
}
}
setMediaDeliverable = (value, index) => {
let currentState = this.getStateCopy();
currentState.mediaDeliverables[index] = value;
this.setState(currentState);
}
getStateCopy = () => Object.assign({}, this.state);
render() {
return (
<div className="App">
<Form
key="mainForm"
mediaDeliverablesOptions={this.state.mediaDeliverables}
setMediaDeliverable={this.setMediaDeliverable}
/>
</div>
);
}
}
export default App;
Form.js
import React from 'react';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';
const useStyles = makeStyles(theme => ({
container: {
display: 'inline-block',
flexWrap: 'wrap',
},
root: {
display: 'inline-block',
flexWrap: 'wrap',
maxWidth: 600,
textAlign: 'left',
},
extendedIcon: {
marginRight: theme.spacing(1),
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
width: 370,
},
dense: {
marginTop: 19,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}));
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
function getStyles(name, accountName, theme) {
// console.log('>> [form.js] (getStyles) ',accountName)
return {
fontWeight:
accountName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
export default function Form(props) {
const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
const classes = useStyles();
const theme = useTheme();
const CheckboxGroup = ({ values, label, onChange }) => (
<FormControl component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{values.map((value, index) => (
<FormControlLabel
key={index}
control={
<Checkbox
checked={value.checked}
onChange={onChange(index)}
/>
}
label={value.label}
/>
))}
</FormGroup>
</FormControl>
);
const MediaDeliverableCheckBoxList = ({values, label}) => (
<FormControl component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{values.map((value, index) => (
<MediaDeliverablesCheckBox
key={index}
mediaDeliverablesOptions={value}
onMediaDeliverableChange={onMediaDeliverableChange(index)}
/>
))}
</FormGroup>
</FormControl>
);
const onCheckBoxChange = index => ({ target: { checked } }) => {
const newValues = [...values];
const value = values[index];
newValues[index] = { ...value, checked };
props.setDesignOrDigital(newValues);
};
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
return (
<div className={classes.root}>
<MediaDeliverableCheckBoxList
label="Please Choose Deliverables:"
values={mediaDeliverablesOptions}
key="media-deliverable-checkbox-list"
/>
</div>
);
}
MediaDeliverablesCheckbox.js
import React from 'react';
import Checkbox from '#material-ui/core/Checkbox';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import TextField from '#material-ui/core/TextField';
export default function MediaDeliverablesCheckBox(props) {
let deliverableData = Object.assign({}, props.mediaDeliverablesOptions);
const onCheckBoxChange = (e) => {
deliverableData.checked = e.target.checked;
props.onMediaDeliverableChange(deliverableData, e);
}
const onQuantityChange = (e) => {
deliverableData.quantity = e.target.value;
props.onMediaDeliverableChange(deliverableData, e);
}
const CheckboxGroup = ({ value, label }) => (
<FormControl component="fieldset">
<FormGroup>
<FormControlLabel
control={
<Checkbox
key={props.index}
checked={value.checked}
onChange={onCheckBoxChange}
/>
}
label={label}
/>
</FormGroup>
</FormControl>
);
return(
<div className="MediaDeliverablesCheckBox">
<CheckboxGroup
key={props.index}
label={props.mediaDeliverablesOptions.label}
value={props.mediaDeliverablesOptions}
/>
<TextField
key={'tf'+props.index}
id={'quantity-'+props.index}
label="Quantity"
placeholder="How many do you need?"
multiline
variant="outlined"
value={props.mediaDeliverablesOptions.quantity}
onChange={onQuantityChange}
fullWidth
/>
</div>
);
}
Updated Form.js based on recommended edits by Ryan C.
import React from 'react';
import { makeStyles, useTheme } from '#material-ui/core/styles';
import FormControl from '#material-ui/core/FormControl';
import FormLabel from '#material-ui/core/FormLabel';
import FormGroup from '#material-ui/core/FormGroup';
import FormControlLabel from '#material-ui/core/FormControlLabel';
import Checkbox from '#material-ui/core/Checkbox';
import MediaDeliverablesCheckBox from './MediaDeliverablesCheckBox';
const useStyles = makeStyles(theme => ({
container: {
display: 'inline-block',
flexWrap: 'wrap',
},
root: {
display: 'inline-block',
flexWrap: 'wrap',
maxWidth: 600,
textAlign: 'left',
},
extendedIcon: {
marginRight: theme.spacing(1),
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
width: 370,
},
dense: {
marginTop: 19,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}));
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};
function getStyles(name, accountName, theme) {
return {
fontWeight:
accountName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
// Failed to compile
// ./src/Form.js
// Line 86: Parsing error: Unexpected token, expected ","
// 84 |
// 85 | const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
// > 86 | {values.map((value, index) => (
// | ^
// 87 | <MediaDeliverablesCheckBox
// 88 | key={index}
// 89 | index={index}
// This error occurred during the build time and cannot be dismissed.
const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
{values.map((value, index) => (
<MediaDeliverablesCheckBox
key={index}
index={index}
mediaDeliverablesOptions={value}
onMediaDeliverableChange={onMediaDeliverableChange(index)}
/>
))}
);
export default function Form(props) {
const mediaDeliverablesOptions = props.mediaDeliverablesOptions;
const classes = useStyles();
const theme = useTheme();
const CheckboxGroup = ({ values, label, onChange }) => (
<FormControl component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{values.map((value, index) => (
<FormControlLabel
key={index}
control={
<Checkbox
checked={value.checked}
onChange={onChange(index)}
/>
}
label={value.label}
/>
))}
</FormGroup>
</FormControl>
);
const onCheckBoxChange = index => ({ target: { checked } }) => {
const newValues = [...values];
const value = values[index];
newValues[index] = { ...value, checked };
props.setDesignOrDigital(newValues);
};
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
return (
<div className={classes.root}>
<MediaDeliverableCheckBoxList
onMediaDeliverableChange={onMediaDeliverableChange}
/>
</div>
);
}
I see two main issues:
How you are defining your different components (nesting component types)
Not passing the index prop through to components that are expecting it
You have the following structure (leaving out details that are not directly related to my point):
export default function Form(props) {
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
const MediaDeliverableCheckBoxList = ({values, label}) => (
<FormGroup>
{values.map((value, index) => (
<MediaDeliverablesCheckBox key={index} onMediaDeliverableChange={onMediaDeliverableChange(index)}/>
))}
</FormGroup>
);
return (
<MediaDeliverableCheckBoxList/>
);
}
The function MediaDeliverableCheckBoxList represents the component type used to render the <MediaDeliverableCheckBoxList/> element. Whenever Form is re-rendered due to props or state changing, React will re-render its children. If the component type of a particular child is the same (plus some other criteria such as key being the same if specified), then it will update the existing DOM node(s). If the component type of a particular child is different, then the corresponding DOM nodes will be removed and new ones added to the DOM.
By defining the MediaDeliverableCheckBoxList component type within the Form function, you are causing that component type to be different on every render. This will cause all of the DOM nodes to be replaced rather than just updated and this will cause the focus to go away when the DOM node that previously had focus gets removed. It will also cause performance to be considerably worse.
You can fix this by moving this component type outside of the Form function and then adding any additional props that are needed (e.g. onMediaDeliverableChange) to convey the context known inside of Form. You also need to pass index as a prop to MediaDeliverablesCheckBox since it is using it.
const MediaDeliverableCheckBoxList = ({values, label, onMediaDeliverableChange}) => (
<FormGroup>
{values.map((value, index) => (
<MediaDeliverablesCheckBox key={index} index={index} onMediaDeliverableChange={onMediaDeliverableChange(index)}/>
))}
</FormGroup>
);
export default function Form(props) {
const onMediaDeliverableChange = index => (deliverableData, e) => {
props.setMediaDeliverable(deliverableData, index);
}
return (
<MediaDeliverableCheckBoxList onMediaDeliverableChange={onMediaDeliverableChange}/>
);
}
You have this same issue with CheckboxGroup and possibly other components as well.
This issue is solely because of your key in the TextField. You must ensure that the key remains the same on every update. Otherwise you would the face the current issue.

Cannot get input field to focus on click

So i am currently unable to focus on an input field - I have a card, which has a onClick handler. Once you clicked it - it brings up the input field, however, i cannot get the input field to focus:
The key area where things are going wrong are within renderTextField where i have set a reference, and then call this.myInput.focus() in handleClickInside, but still no luck.
Code below
import React, { Component } from 'react';
import { CSSTransition, transit } from 'react-css-transition';
import Text from './Text';
import Card from './Card';
import MenuIcon from '../icons/MenuIcon';
const style = {
container: { height: '60px', width: '300px' },
input: { border: 'none', fontSize: '14px', background: '#ff000000' },
icon: { position: 'absolute', right: '10px', top: '18px' },
};
class Input extends Component {
state = {
clickedOutside: true,
value: '',
};
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside);
}
myRef = React.createRef();
myInput = React.createRef();
handleClickOutside = (e) => {
if (!this.myRef.current.contains(e.target)) {
this.setState({ clickedOutside: true });
}
};
handleClickInside = () => {
this.setState({ clickedOutside: false });
this.myInput.current.focus();
}
renderField = () => {
const { clickedOutside, value } = this.state;
return (
<div>
<CSSTransition
defaultStyle={{ transform: 'translate(0, -2px)' }}
enterStyle={{ transform: transit('translate(0, -16px)', 150, 'ease-in-out') }}
leaveStyle={{ transform: transit('translate(0, -2px)', 150, 'ease-in-out') }}
activeStyle={{ transform: 'translate(0, -16px)' }}
active={!clickedOutside || value.length > 0}
>
<Text>Password</Text>
{ this.renderTextField() }
</CSSTransition>
</div>
);
}
renderTextField = () => {
const { clickedOutside, value } = this.state;
return (
<input
ref={this.myInput}
style={style.input}
type="text"
value={value}
placeholder={!clickedOutside ? 'Enter Password...' : null}
disabled={clickedOutside}
onChange={e => this.setState({ value: e.target.value })}
/>
);
}
renderIcon = () => (
<div style={style.icon}>
<MenuIcon />
</div>
)
render() {
return (
<div ref={this.myRef} onClick={this.handleClickInside} >
<Card override={style.container}>
{ this.renderField() }
{ this.renderIcon() }
</Card>
</div>
);
}
}
export default Input;
First create a ref:
myInput = React.createRef();
Assign the ref:
ref={this.myInput}
Then, to focus myInput, use:
this.myInput.current.focus()

Categories

Resources