React Component hook variable will not update - reactjs

I have a React component that gets data from a parent page/component. I use this data object (jobData) to populate a hook variable value when the component (a modal) is fired. The jobData looks like this: [{id: '17003', file_name: 'My_File', type: 'Medium', state: 'Arkansas'}]. In the browser debugger, I can see the jobData getting passed into the component, but when it gets to return ....<TextField .... value={productID} the productID says undefined! Any suggestions as to what I am doing wrong? I want the TextField to display the value of jobData[0]['id'] when it fires and then store the value of productID when it cahnges.
import React, { useState, useEffect } from 'react';
import { Button, TextField, Dialog, DialogActions, DialogContent, DialogTitle, Modal,
FormControl, Select, InputLabel } from '#mui/material';
import { createTheme, ThemeProvider } from '#mui/material/styles';
export default function ScheduleProdsModal({jobData}) {
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(jobData[0]["id"]);
return (
<div>
<ThemeProvider theme={theme}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleOpen}>Schedule Products</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Schedule Products</DialogTitle>
<DialogContent >
<FormControl fullWidth style={{marginTop: '5px', marginBottom: '5px'}}>
<TextField
autoFocus
margin="dense"
width="100%"
id="my_id"
label="My ID"
type="text"
value={productID}
variant="outlined"
onChange={(e) => {
setProductID(e.target.value);
}}
/>
</FormControl>
</DialogContent>
<DialogActions style={{marginRight: 'inherit'}}>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleClose}>Close</Button>
<Button color="neutral" variant="contained" cursor="pointer" onClick={handleScheduler}>Schedule</Button>
</DialogActions>
</Dialog>
</ThemeProvider>
</div>
);
}

It seems like you have set jobData array when initialising the projectId, which will be undefined in the first load unless it has been handled in the parent component. Therefore as a workaround we normally use useEffect hook to get the upcoming props changes. So, when the jobData array is not null, you can use setProductID to get the relevant product id inside the useEffect hook. For the time period that you are not getting the data, you can use your favourite loading mechanism to show a loading screen. Find the below code snippet.
const [open, setOpen] = useState(false);
const handleClose = () => setOpen(false);
const handleOpen = () => setOpen(true);
// Below is the hook with problems
let [productID, setProductID] = useState(0);
useEffect(() => {
if(jobData && jobData.length > 0){
setProductID(Number(jobData[0]["id"]))
}
}, [jobData])
// You can put more conditions like undefined and null checking if you want
if(productID === 0){
// Use your fav loading page or loader here
return <>Loading...</>
}
return (
<div>
<ThemeProvider theme={theme}>
Hope this would help.

Related

React - Snackbar as global component (Typescript)

Merry Christmas at first to all of you!
[React + TypeScript]
And yes, I'm a newbie in react, I'm more kind of backend geek, but we all need to learn new stuff :)
I am trying to make my snackbar work in all components globally, to not write a new one every time.
I saw one post about this before: How to implement material-ui Snackbar as a global function?
But unfortunatelly I can't make it work.
Is my function even correct?
And if it is, how can I call it from another component now?
I made this function:
SnackbarHOC.tsx
`
import { AlertTitle, IconButton, Snackbar } from '#mui/material';
import Slide from '#mui/material';
import Alert from '#mui/material';
import { useState } from 'react';
import CloseIcon from '#mui/icons-material/Close';
function SnackbarHOC<T>(WrappedComponent: React.ComponentType<T>, Alert: React.ElementType) {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState("I'm a custom snackbar");
const [duration, setDuration] = useState(2000);
const [severity, setSeverity] = useState(
"success"
); /** error | warning | info */
return (props: T) => {
const showMessage = (message: string, severity = "success", duration = 2000) => {
setMessage(message);
setSeverity(severity);
setDuration(duration);
setOpen(true);
};
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
}
return (
<>
<WrappedComponent {...props} snackbarShowMessage={showMessage} />
<Snackbar
anchorOrigin={{
vertical: "bottom",
horizontal: "center"
}}
autoHideDuration={duration}
open={open}
onClose={handleClose}
//TransitionComponent={Slide}
>
<Alert severity="error"
sx={{ width: '100%' }}
action={
<IconButton
aria-label="close"
color="inherit"
size="small"
onClick={handleClose}>
<CloseIcon fontSize="inherit" />
</IconButton>
}>
{message}
</Alert>
</Snackbar>
</>
);
};
};
export default SnackbarHOC;
`
I tried to call it from another component, but I have no idea how to show the actual snackbar now :(.
It's all that is not giving me errors right now:
`
import SnackbarHOC from '../../SnackbarHOC';
`
I found a solution for that but in JS basically, it's the same I guess it's just not typed. I'm implementing within NEXTJS latest
//index.jsx
import Head from 'next/head'
import { useState } from 'react';
import {Button} from '#mui/material'
import Notification from '../components/Notification.component'
//create the jsx file with the Snackbar component implemented
function Home() {
// we need a simple hook for the button
const [open, setOpen] = useState(false);
}
const handleClick = () => {
setOpen(true);
};
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
return(
<div >
<Head>
<title>HOME</title>
<meta name="description" content="AWESOME" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Button variant="outlined" onClick={handleClick}>SEND</Button>
{/*We can define new props for our component I'm mixin props from mui Alert and Snackbar, from Alert I'm using severity and from Snackbar the open and onClose and message goes inside the alert as a simple string :D */}
<Notification
open={open}
onClose={handleClose}
severity="error"
message="that is what I am talking about"/>
</div>
)
}
Now we just need to create the custom component that has multiple lil components inside that we can edit as we want!
//Notification.component.jsx file
import React, { forwardRef} from 'react';
import Snackbar from '#mui/material/Snackbar';
import MuiAlert from '#mui/material/Alert';
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
const Notification= ({open, onClose, severity, message}) => {
/*<> ... </> The empty tag is shorthand for <React. Fragment>, allowing you to have multiple top-most elements without wrapping further HTML.
*/
return (
<>
<Snackbar open={open} autoHideDuration={6000} onClose={onClose}>
<Alert onClose={onClose} severity={severity} sx={{ width: '100%' }}>
{message}
</Alert>
</Snackbar>
</>
);
}
export default Notification;
But if you really want use typed values You can check the returned type in your VSCode for each variable and function. but I really don't see any benefit for that. But as it doesn't change the result I will not implement it for me neither here.
I highly recommend to watch this video series https://www.youtube.com/watch?v=Roxf91mp3kw&t=367s

UseRef only returns me undefined or null

Im trying to get the img element to be printed on the console but for some reason the only thing i get is undefined and null.
This is my code:
import CardContent from '#mui/material/CardContent';
import CardActions from '#mui/material/CardActions';
import UIButton from 'app/main/components/UIButton';
import { useRef, useEffect } from 'react';
function ExpandImageDialog({ open, onClose: close, url }) {
const refInput = useRef();
const styleImage = () => {
console.log(refInput.current, 'it got here');
};
useEffect(() => {
styleImage();
}, [open]);
return (
<Modal open={open} onClose={close}>
<Card className="px-20 pt-6 pb-32" sx={modalStyle}>
<CardHeader title="VisualizaĆ§Ć£o de imagem" />
<hr />
<CardContent>
<img
className="flex-img"
loading="lazy"
src={url}
alt="Documento"
style={imageStyle}
ref={refInput} />
</CardContent>
<CardActions className="justify-end">
<UIButton onClick={close}>Voltar</UIButton>
</CardActions>
</Card>
</Modal>
);
}
this is what it shows on the console
Im new to react so sorry if im doing something obviously wrong
Thank you for the help!
The way you are using your ref is correct, see this repro for a simple reproduction. Here is the code :
import React from 'react';
import { render } from 'react-dom';
let renderCount = 0;
const App = () => {
const [isVisible, setIsVisible] = React.useState(false);
const imgRef = React.useRef();
const styleImage = () => {
console.log('img ref = ', imgRef.current);
};
const handleClick = () => {
setIsVisible(!isVisible);
};
React.useEffect(() => {
styleImage();
}, [isVisible]);
renderCount += 1;
return (
<div>
<div>render count = {renderCount}</div>
<button onClick={handleClick}>{isVisible ? 'Hide' : 'Show'} image</button>
<br />
<CustomImg isVisible={isVisible}>
<img src="https://picsum.photos/200/300" ref={imgRef} />
</CustomImg>
<br />
</div>
);
};
// Same as your Modal component
const CustomImg = ({ isVisible, children }) => {
if (!isVisible) return <></>; // Component returns nothing, so img isn't known. Therefore, ref is undefined on the first render.
return <div>{children}</div>;
};
render(<App />, document.getElementById('root'));
The problem comes from the prop open/close that you pass to the Modal component. If it is not rendered, then your ref will stay undefined. The null status surely comes from your Modal as well. In my example, try to show and then hide the image, you will see that the ref is null after you hide it, which is normal. There is a re-render, so the ref is reset as well.
Note that I created the variable renderCount outside of the component, so it is a global variable that you can use to check how many times your component re-render. Try doing it to see what happen.
Your code looks correct.
It is null because you are logging the ref in the useEffect with a dependency on open. This would be running when the component is mounted.
If you try the following with refInput as a dependency, you should see the ref log.
useEffect(()=>{
console.log(refInput.current, 'it got here');
}, [refInput])

React mui 5 checkbox don't change value under function

When Checkbox is under a FormControlLabel and function, the value don't change when useState change another variable.
If I comment the useState, checkbox works.
If I move the same code (checkbox and formcontrolLabel) on a render, not in function, checkbox work.
The sample code :
import * as React from "react";
import {
Theme,
useTheme,
Box,
Stack,
Checkbox,
FormControlLabel,
} from "#mui/material";
export const RunnerRaces2: React.FC<{}> = (props) => {
const theme = useTheme();
const [dataSource, setDataSource] = React.useState<[]>(
);
const FrmCheckBox: React.FC<{ label: string }> = (props) => {
const [checked, setChecked] = React.useState(true);
return (<FormControlLabel
label={props.label}
control={
<Checkbox
// checked={checked}
// defaultChecked
size="small"
onChange={(
event: React.ChangeEvent<HTMLInputElement>
) => {
const value = event.target.checked;
setDataSource([]);
}}
/>
}
/>);
};
return (
<Box>
<Stack
direction="row"
>
<FrmCheckBox
label={"t1"} />
<FrmCheckBox
label={"t2"} />
<FrmCheckBox
label={"t3"} />
<FrmCheckBox
label={"t4"} />
</Stack>
</Box>
);
};
I don't understand, it's mui 5 bug ? react bug ? or a wrong code from me ?
sample on codesandbox.io
Problem is caused by setDataSource([]), I fixed it by removing it.
You can change the dataSource by using a useEffect and passing checked state as a dependency to that.
The fixed codesandbox demo

Material-UI Autocomplete, React Hook Form - Changing InputValue in Material UI Autocomplete with Use State in an OnChange?

I've been customising a Material UI Autocomplete within a Controller from React Hook Form, as part of a much larger form, with some difficulty.
The dropdown lists suggestions drawn from the database (props.items, represented here as objects) and if the suggestion is not there, there's the option to add a new one in a separate form with a button from the dropdown. This 'secondComponent' is opened with conditional rendering.
As it gets passed to the second form, the data is stored in state (heldData) and then passed back into the form via React Hook Form's reset, here as reset(heldData).
This updates the value of the form perfectly, as I have an onChange event that sets the value according to what was passed in. React Hook Form handles that logic with the reset and gives the full object to the onChange.
However, I also want to set the InputValue so that the TextField is populated.
In order to create a dynamic button when there are no options ('Add ....(input)... as a guest'), I store what is typed into state as 'texts'. I thought that I could then use the OnChange event to use the same state to update the inputValue, as below. However, when I setTexts from the onChange, the change isn't reflected in the inputValue.
Perhaps this is because the useState is async and so it doesn't update the state, before something else prevents it altogether. If so, it's much simpler than the other code that I have included, but wasn't certain. I have excluded most of the form (over 500 lines of code) but have tried to keep any parts that may be appropriate. I hope that I have not deleted anything that would be relevant, but can update if necessary.
Apologies. This is my first question on Stack Overflow and I'm quite new to React (and coding) and the code's probably a mess. Thank you
**Form**
import React, { useState, useEffect} from "react";
import AutoCompleteSuggestion from "../general/form/AutoCompleteSuggestion";
import SecondComponent from './SecondComponent'
import { useForm } from "react-hook-form";
const items = {
id: 2,
name: "Mr Anderson"
}
const items2 = {
id: 4,
name: "Mr Frog"
}
const defaultValues = {
guest: 'null',
contact: 'null',
}
const AddBooking = () => {
const { handleSubmit, register, control, reset, getValues} = useForm({
defaultValues: defaultValues,
});
const [secondComponent, setSecondComponent] = useState(false);
const [heldData, setHeldData] = useState(null)
const openSecondComponent = (name) => {
setSecondComponent(true)
const data = getValues();
setHeldData(data);
}
useEffect(() => {
!secondComponent.open?
reset(heldData):''
}, [heldData]);
const onSubmit = (data) => {
console.log(data)
};
return (
<>
{!secondComponent.open &&
<form onSubmit={handleSubmit(onSubmit)}
<AutoCompleteSuggestion
control={control}
name="guest"
selection="id"
label="name"
items={items}
openSecondComponent={openSecondComponent}
/>
<AutoCompleteSuggestion
control={control}
name="contact"
selection="id"
label="name"
items={items2}
openSecondComponent={openSecondComponent}
/>
</form>
};
{secondComponent.open?
<SecondComponent/>: ''
};
</>
);
};
And this is the customised AutoComplete:
**AutoComplete**
import React, { useState } from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete, from "#material-ui/lab/Autocomplete";
import parse from "autosuggest-highlight/parse";
import match from "autosuggest-highlight/match";
import { Controller } from "react-hook-form";
import Button from "#material-ui/core/Button";
const AutoCompleteSuggestion = (props) => {
const [texts, setTexts] = useState('');
return (
<>
<Controller
name={props.name}
control={props.control}
render={({ onChange }) => (
<Autocomplete
options={props.items}
inputValue={texts} //NOT GETTING UPDATED BY STATE
debug={true}
getOptionLabel={(value) => value[props.label]}
noOptionsText = {
<Button onClick={()=> props.opensSecondComponent()}>
Add {texts} as a {props.implementation}
</Button>}
onChange={(e, data) => {
if (data==null){
onChange(null)
} else {
onChange(data[props.selection]); //THIS ONCHANGE WORKS
setTexts(data[props.label]) //THIS DOESN'T UPDATE STATE
}}
renderInput={(params) => (
<TextField
{...params}
onChange = { e=> setTexts(e.target.value)}
/>
)}
renderOption={(option, { inputValue }) => {
const matches = match(option[props.label1, inputValue);
const parts = parse(option[props.label], matches);
return (
<div>
{parts.map((part, index) => (
<span
key={index}
style={{ fontWeight: part.highlight ? 700 : 400 }}
>
{part.text}
</span>
))}
</div>
);
}}
/>
)}
/>
</>
);
};
export default AutoCompleteSuggestion;

Material UI Dialog turned to Hook flickering

I'm trying to turn Material UI's dialog into a "useDialog" hook so it keeps track of it's own open state.
Unfortunately I've encountered a problem that whenever I update a state further up the hierarchy, the dialog flickers and I'm not exactly sure why and how to circumvent it. I feel like a useRef is needed there somewhere, but I'm not sure. Here's a reproduced minimal example: https://codesandbox.io/s/flickering-dialog-minimal-example-ehruf?file=/src/App.js
And the code in question:
import React, { useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle
} from "#material-ui/core";
export default function App() {
const [openDialog, Dialog] = useDialog();
const [counter, setCounter] = useState(0);
return (
<div>
<Dialog title="Hello">
<div>{counter}</div>
<button onClick={() => setCounter(counter => counter + 1)}>
Increase
</button>
</Dialog>
<button onClick={openDialog}>Open dialog</button>
</div>
);
}
const useDialog = () => {
const [open, setOpen] = useState(false);
const handleClose = () => {
setOpen(false);
};
const someDialog = ({ title, children }) => {
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
return [
() => {
setOpen(true);
},
someDialog
];
};
The reason the dialog flickers is that a new Dialog component is created on every render(as a result of state change) in App. The old Dialog is unmounted and replaced by the new Dialog.
A rule of thumb is you should never define components while rendering.
That's why I suggest you separate your custom dialog component from useDialog hook:
const MyDialog = ({ open, handleClose, title, children }) => {
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
You can, however, keep some of the logic inside useDialog and reuse them:
const useDialog = () => {
const [open, setOpen] = useState(false);
const openDialog = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const props = {
open,
handleClose
};
return [openDialog, props];
};
More about why returning components from hook can be a bad idea.
Custom hooks are not made for returning a component, instead they are used to create a common logic which will be shared by different components.
In your case I would suggest you to create a common component for your dialog. And use this component wherever you want. Like this:
<CustomDialog open={open}>
// Your jsx here
</CustomDialog>
const CustomDialog = ({children}) => {
return <Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
}
For more information about custom hooks:
https://reactjs.org/docs/hooks-custom.html
To whomever finds this issue because of my mistake. I was using a styled Dialog and defined the styled Dialog inside the functional component. Just put the custom styling outside of any component.
// This needed to be outstide of the BetterDialog component
const CustomDialog = styled(Dialog)({
"& .MuiDialog-paper": {
zIndex: 90
}
})
...
function BetterDialog(props: BetterDialogProps) {
...

Resources