How do I conditionally render CSS with Material UI useStyles/makeStyles? - javascript

I'm trying to display the latestMessageText variable as bold and black if the props meet a certain condition. The useEffect method works, an ugly solution; How do I use Material UI's useStyle hook to execute my goal. I've tried passing props to useStyle, using them inside the makeStyles function, to no effect.
import React, { useEffect, useRef } from "react";
import { Box, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "space-between",
marginLeft: 20,
flexGrow: 1,
},
username: {
fontWeight: "bold",
letterSpacing: -0.2,
},
previewText: {
fontSize: 12,
// color: "#9CADC8",
letterSpacing: -0.17,
color: (messages, otherUser) => {
if (messages.length && otherUser) {
const lastMessage = messages[messages.length - 1];
const { senderId, receiverHasRead } = lastMessage;
if (senderId === otherUser.id && !receiverHasRead) {
return 'black'
}
return "#9CADC8"
}
}
},
black: {
color: 'black',
fontWeight: 700,
}
}));
const ChatContent = (props) => {
const { conversation } = props;
const { latestMessageText, otherUser, messages } = conversation;
const classes = useStyles(messages, otherUser);
return (
<Box className={classes.root}>
<Box>
<Typography className={classes.username}>
{otherUser.username}
</Typography>
<Typography className={classes.previewText}>
{latestMessageText}
</Typography>
</Box>
</Box>
);
};
export default ChatContent;

I recommend here using A tiny (228B) utility for constructing className strings conditionally which is clsx.
I provided solution here.
First method with {[yourclass]: conditon} syntax:
import React, { useEffect, useRef } from "react";
import { Box, Typography } from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import clsx from 'clsx';
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
justifyContent: "space-between",
marginLeft: 20,
flexGrow: 1,
},
username: {
fontWeight: "bold",
letterSpacing: -0.2,
},
previewText: {
fontSize: 12,
color: "#9CADC8",
letterSpacing: -0.17,
},
black: {
color: 'black',
fontWeight: 700,
}
}));
const ChatContent = (props) => {
const classes = useStyles();
const typeographyEl = useRef();
const { conversation } = props;
const { latestMessageText, otherUser, messages } = conversation;
const lastMessage = messages?.length && messages[messages.length - 1];
const { senderId, receiverHasRead } = lastMessage;
return (
<Box className={classes.root}>
<Box>
<Typography className={classes.username}>
{otherUser.username}
</Typography>
<Typography ref={typeographyEl} className={clsx(classes.previewText, {[classes.black]: senderId === otherUser.id && !receiverHasRead})}>
{latestMessageText}
</Typography>
</Box>
</Box>
);
};
export default ChatContent;
Or second method with {conditon && yourstyle} syntax:
<Typography ref={typeographyEl}
className={clsx(classes.previewText, {(senderId === otherUser.id && !receiverHasRead) && classes.black})}>
{latestMessageText}
</Typography>

That's not the React way of handling this sort of function. You need to get the condition in a state variable (if you expect it to change by user interaction) or a constant variable otherwise, and set the class based on that.
const [messageRead, setMessageRead] = useState(false);
useEffect(() => {
if (messages.length) {
const { senderId, receiverHasRead } = messages[messages.length - 1];
setMessageRead(senderId === otherUser.id && !receiverHasRead);
}
}, [messages, otherUser]);
JSX
<Typography className={`${classes.previewText} ${messageRead ? classes.black : ''}`}>
{latestMessageText}
</Typography>
You can use the classnames or clsx npm package to simplify the ternary condition.

Related

Warning Reactjs - undefined

I'm working on a project for School and I have some problems with them.
So this is the code
import React from 'react';
import { AppBar, Container, Toolbar, Typography, MenuItem, Select, makeStyles, createTheme, ThemeProvider } from "#material-ui/core";
import { useNavigate } from "react-router-dom";
import { CryptoState } from '../CryptoContext';
//Creeare Design - Titlu
const useStyles = makeStyles(() => ({
title: {
flex:1,
color: "gold",
fontFamily: "Montserrat",
fontWeight: "bold",
cursor: "pointer",
}
}))
const Header = () => {
const classes = useStyles();
const navigate = useNavigate();
const {currency,setCurrency} = CryptoState() || {};
console.log(currency);
// TODO: Gandire sistem reintoarcere pagina principala
const handleClick = () => navigate.push('/'); // nu merge - reintoarcere la pagian principala
// FIXME: Creeare template temă
const darkTheme = createTheme({
palette:{
primary: {
main: "#fff",
},
type: "dark",
},
});
return (
<ThemeProvider theme={darkTheme}>
<AppBar color='transparent' position='static'>
<Container>
<Toolbar>
<Typography onClick={handleClick}
className={classes.title} variant='h5'>Crypto Hunter</Typography>
<Select variant="outlined" style={{
width:100,
height:40,
marginLeft: 15,
}}
value={currency} onChange={(e) => setCurrency(e.target.value)}
>
<MenuItem value={"RON"}>RON</MenuItem>
<MenuItem value={"EUR"}>EUR</MenuItem>
<MenuItem value={"USD"}>USD</MenuItem>
</Select>
</Toolbar>
</Container>
</AppBar>
</ThemeProvider>
)
};
export default Header;
And this is the problem, I don't know how I can fix them.
What I want?
Image here
Please help me!
If I change from select with RON or USD or EURO, i want to console.log to can go to the next step.
But it gives me this error (image), and i don't know how i can fix this
EDIT:
Also, from CryptoState i have this code
const Crypto = createContext();
const CryptoContext = ({children}) => {
const [currency, setCurrency] = useState("RON");
const [symbol, setSymbol] = useState("RON");
useEffect(() => {
if(currency === "RON") setSymbol("RON");
else if(currency === "EUR") setSymbol("€");
else if(currency === "USD") setSymbol("$");
},[currency]);
return <Crypto.Provider value={{currency,symbol,setCurrency}}>{children}</Crypto.Provider>
};
export default CryptoContext;
export const CryptoState = () => {
useContext(Crypto);
}
change that line
const {currency,setCurrency} = CryptoState() || {};
to
const [currency,setCurrency] =useState(CryptoState() || {}) ;

How to render multiple notifications with help of notistack in react?

I'm trying to use React's 'notistack' module to display several notifications as a stack. However, it appears that I am making a mistake, as Whenever I receive a warning:
react_devtools_backend.js:3973 Warning: Cannot update during an existing state transition (such as within render)
I'm using the state here to set the Response messages(they are coming from api), which are an array of notifications. Once the alerts have been rendered, I should also make the state of the Response messages an empty array.
renderPage.js
import React, { useState, useContext } from "react";
import axios from "axios";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import { SnackbarProvider } from "notistack";
import Zoom from "#material-ui/core/Slide";
import Notification from "../../components/Notification";
import { handleResponse } from "../../services/responseHandler";
import { SelectedRunnerContext } from "./RunnersPage";
export function RunnersForm() {
const { selected } = useContext(SelectedRunnerContext);
const [responseMessages, setResponseMessages] = useState([]);
const notistackRef = React.createRef();
const onClickDismiss = (key) => () => {
notistackRef.current.closeSnackbar(key);
};
const MakePostRequest = (event) => {
event.preventDefault();
setResponseMessages([]);
const data = {
selected_runners: selected,
};
const requestOptions = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: data,
};
axios
.post("/messages", requestOptions)
.then((response) => {
const result = handleResponse(response.data);
setResponseMessages(result);
})
.catch((error) => {
console.log(error);
});
};
return (
<>
<Button onClick={MakePostRequest}>Post</Button>
{!!responseMessages.length && (
<SnackbarProvider
maxSnack={4}
ref={notistackRef}
dense
preventDuplicate
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
TransitionComponent={Zoom}
sx={{
width: 700,
}}
style={{
fontSize: 15,
fontWeight: 700,
}}
action={(key) => (
<Button onClick={onClickDismiss(key)}>
<Typography
variant="subtitle2"
style={{
fontWeight: 600,
fontSize: 16,
color: "#00579b",
}}
>
Dismiss
</Typography>
</Button>
)}
>
<Notification messages={responseMessages} />
</SnackbarProvider>
)}
</>
);
}
Notification.js
import React, { Fragment } from "react";
import { useSnackbar } from "notistack";
export default function Notification(props) {
const { enqueueSnackbar } = useSnackbar();
const { messages } = props;
const options = {
variant: "success",
autoHideDuration: 6000,
transitionDuration: { enter: 400, exit: 400 },
};
messages.forEach((msg) => {
enqueueSnackbar(msg, options);
});
return <></>;
}
I've solved the issue by wrapping up the App with the Notification provider:
import React from "react";
import { useSnackbar } from "notistack";
import Button from "#mui/material/Button";
import { SnackbarProvider } from "notistack";
import Typography from "#mui/material/Typography";
function MakeGlobal() {
const { enqueueSnackbar } = useSnackbar();
window.enqueueSnackbar = enqueueSnackbar;
return null;
}
export function displayNotification(messages) {
const options = {
variant: "success",
autoHideDuration: 6000,
transitionDuration: { enter: 400, exit: 400 },
};
messages.forEach((msg) => {
window.enqueueSnackbar(msg, options);
});
}
export function Provider({ children }) {
const notistackRef = React.createRef();
const onClickDismiss = (key) => () => {
notistackRef.current.closeSnackbar(key);
};
return (
<SnackbarProvider
maxSnack={4}
ref={notistackRef}
dense
preventDuplicate
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
sx={{
width: 700,
}}
style={{
fontSize: 15,
fontWeight: 700,
}}
action={(key) => (
<Button onClick={onClickDismiss(key)}>
<Typography
variant="subtitle2"
style={{
fontWeight: 600,
fontSize: 16,
color: "#00579b",
}}
>
Dismiss
</Typography>
</Button>
)}
>
<MakeGlobal />
{children}
</SnackbarProvider>
);
}
App.js
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<BrowserRouter>
<snackbar.Provider>
<AppRoutes />
<CssBaseline />
</snackbar.Provider>
</BrowserRouter>
);

Can't set state in js react-native

Getting error while trying to setState in React Native.
Code
import React from "react";
import { TextInput, Text, View, Button, Alert } from "react-native";
const UselessTextInput = () => {
state = { currentDate: "" };
const setCurentDate = (val) => {
this.setState({currentDate : val});
};
const [value, onChangeText] = React.useState("");
return (
<View>
<Text
style={{
alignSelf: "center",
marginTop: 60,
fontWeight: "bold",
fontSize: "25",
}}
>
BirthReminder
</Text>
<Text style={{ alignSelf: "center", marginTop: 15, fontSize: 15 }}>
Enter your friend's birthdate, please
</Text>
<TextInput
clearTextOnFocus={true}
style={{
height: 40,
borderColor: "gray",
borderWidth: 1,
marginTop: 20,
width: 250,
alignSelf: "center",
}}
onChangeText={(value) => setCurentDate(value)}
value={value}
/>
<Button title="Add to list"></Button>
</View>
);
};
export default UselessTextInput;
Error
TypeError: undefined is not an object (evaluating '_this.setState')
useState Hook
Functional components don't have access to setState method but useState hook.
useState hook works by defining the name of value, e.g. foo followed by it's setter. It's a convention to name the setter with the same name as that of value with set prefix, i.e. setFoo
const [foo, setFoo] = useState('hi');
// pass the initial value here -^^-
Solution
import { useState } from 'react';
import { TextInput } from 'react-native';
const Component = () => {
const [value, setValue] = useState('');
return <TextInput value={value} onChangeText={setValue} />;
};
this.setState isn't allowed in functional component. Try to use React.useState for currentDate
const [currentDate, setCurrentDate] = React.useState("");
...
const setCurentDate = (val) => {
setCurrentDate(val);
};
I think you got a bit mixed up
replace this as this is the syntax if you use Class component
state = { currentDate: "" };
const setCurentDate = (val) => {
this.setState({currentDate : val});
};
with:
const [date, setDate] = React.useState();
const setCurentDate = (val) => {
setDate(val);
};
and have a look at the documentation

CSS in JS, how to make props.className the priority class

We need components where the class passed as props should have more priority than the default class.
When passing classes as a prop, the component gives priority to the
class created in his own file.
Text.jsx
// this will be a component in another folder, it will be used in the whole app so it
// should haveDYNAMIC styling
function Text(props) {
const useStyles = makeStyles(theme => ({
default: {
fontSize: 18,
color: "black"
}
}));
const classes = useStyles();
return (
<div className={classNames(classes.default, props.className)}>
{props.children}
</div>
);
}
App.jsx
function App() {
const useStyles = makeStyles(theme => ({
title: {
fontSize: 80,
color: "red"
},
notGoodPractice: {
fontSize: "80px !important"
}
}));
const classes = useStyles();
return (
<div className="App">
<Text className={classes.title}>Text in here is 18px</Text>
<Text className={classes.notGoodPractice}>
Text in here is 80px
</Text>
</div>
);
}
React Snippet => CodeSandBox
You can prioritise classes passed as props this way.
Just make sure you don't apply makeStyles on it so that you can access them correctly in child.
import React from "react";
import ReactDOM from "react-dom";
import { makeStyles } from "#material-ui/core/styles";
// this is how we will use the Text component
function App() {
const style = {
title: {
fontSize: "80px",
color: "red",
"#media (max-width: 767px)": {
color: "green"
}
},
notGoodPractice: {
fontSize: "80px"
}
};
return (
<div className="App">
<Text class1={style.title}>Title should be with size 80px</Text>
<Text class1={style.notGoodPractice}>Title should be with size 80px</Text>
</div>
);
}
// this will be a component in another folder, it will be used throw the in app so it should be as DYNAMIC as possible
function Text(props) {
const useStyles = makeStyles(theme => ({
default1: {
fontSize: 18,
color: "black"
},
priorityClass: props => props.class1
}));
const { default1, priorityClass } = useStyles(props);
return <div className={`${default1} ${priorityClass}`}>{props.children}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Check out live sandbox https://codesandbox.io/s/zen-voice-mb60t

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.

Categories

Resources