React Js material UI not updating the value when set - reactjs

so I have this code in material UI react js which is not updating the value properly or so I thought, to explain this further I have this code
import * as React from 'react';
import Tabs from '#mui/material/Tabs';
import Tab from '#mui/material/Tab';
export function Home() {
const [value, setValue] = React.useState('Pending');
const handleChange = (event, newValue) => {
setValue(newValue);
console.log(value);
};
return (
<div>
<>
<Tabs
value={value}
onChange={handleChange}
textColor="secondary"
indicatorColor="secondary"
aria-label="secondary tabs example"
>
<Tab value="Pending" label="Pending" />
<Tab value="Received" label="Received" />
<Tab value="Prepared" label="Prepared" />
<Tab value="Cancelled" label="Cancelled" />
</Tabs>
</>
</div>
);
}
If I run this code and click on the Tab with the label "Pending" the console.log won't be triggered. if I click again on the Tag with the label "Received" the console.log with displays "Pending" instead of "Received".
This happens all the time I thought when you set the value and console log it should show the latest value you selected.
sample output:
As you can see am currently selected the "Received" Tag but on the display its showing "Pending"
Is this how react js/Material UI behaves or am I just missing something here?
thanks

Like Dave Newton said in the comments, setting the state is asynchronous as mentioned in the React Docs here:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
If you want to console.log the value, you can update your handler to log the new value instead
const handleChange = (event, newValue) => {
setValue(newValue);
console.log(newValue);
};
Or you can use useEffect outside the handler to always log value when it changes
useEffect(() => {
console.log(value);
}, [value]);

Related

Adding updated state to code editor only works once

I have an issue with my code below. When you click the add code button, it adds the code to the monaco code editor which is great. However, if you type some more code in the editor or erase whats currently there and then press the 'Add code' button, nothing is added. It's just blank.
Is there a way to whenever that 'Add code' button is clicked it clears everything in the editor and just adds the setAddCode state when the button is clicked?
And here is the code:
import { useState, useRef, useEffect } from "react";
import Editor from "#monaco-editor/react";
export default function IndexPage() {
const [input, setInput] = useState("");
const [addCode, setAddCode] = useState("# code goes here");
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
setInput((editorRef.current = editor));
editorRef.current = editor;
}
return (
<div>
<div className="code-editor">
<Editor
width="100vh"
height="40vh"
theme="vs-dark"
fontSize="14"
defaultLanguage="python"
defaultValue=""
value={addCode}
onMount={handleEditorDidMount}
/>
</div>
<br />
<button onClick={() => setAddCode("print('Hello World!')")}>
Add code
</button>
</div>
);
}
The way the Editor component is set up, it will only change the value in the editor if you pass a different value prop. It's probably doing something similar to the following:
const Editor = ({ value }) => {
const [codeToDisplay, setCodeToDisplay] = useState(value);
useEffect(() => {
setCodeToDisplay(value);
}, [value]);
// etc
In your parent component, when you call setAddCode("print('Hello World!')") the first time, that'll result in the child component seeing a difference in how it was called - the value prop changed - so it'll know that there's something different to display, and update its own internal state appropriately. When you press the button again, the addCode value will stay the same - the Editor component doesn't see any difference, so it won't update.
To fix it, you can listen for changes from inside Editor by using its onchange prop to update the state in the parent component when the code inside the editor changes - that way, when you click the button later, the prop will be different, and the editor will know to update its internal state.
export default function IndexPage() {
const [input, setInput] = useState("");
const [codeValue, setCodeValue] = useState("# code goes here");
const editorRef = useRef(null);
function handleEditorDidMount(editor, monaco) {
setInput((editorRef.current = editor));
editorRef.current = editor;
}
return (
<div>
<div className="code-editor">
<Editor
width="100vh"
height="40vh"
theme="vs-dark"
fontSize="14"
defaultLanguage="python"
defaultValue=""
value={codeValue}
onChange={(newValue) => { setCodeValue(newValue); }}
onMount={handleEditorDidMount}
/>
</div>
<br />
<button onClick={() => setCodeValue("print('Hello World!')")}>
Add code
</button>
</div>
);
}

click event is not triggering on input element from ref

I am using react 17.0.2 and material ui 4.11.4
I want to customize the select element appearnce (like Chip component of material ui). For this purpose I am using Autocoomplete component which renders an input element. I have rendered Chip component below the input element.
I am also getting the ref in the renderInput callback function which I am ustlising to trigger input click from Chip component.
When I log the params.inputProps.ref.current I am indeed getting the input element but calling click function does not show the dropdown but just focus the input element. When I click the input directly then it shows the dropdown.
I have created a sandbox of this behaviour.
CodeSandBox
I would recommend using state as a controlled component.
export default function App() {
const classes = useStyles();
const statusDropdownInput = useRef(null);
let [val, setVal] = useState(false) // <- Store the open state
const handleStatusDropdownClick = (params,e) => {
setVal(!val) // <- Will toggle the dropdown
};
let inputElement = null
return (
<Autocomplete
className={classes.statusDropdown}
id="status"
open={val} // <- Control the input elements state here
options={["Option 1", "Option 2"]}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<input type="text" {...params.inputProps} />
<Chip
size="small"
avatar={<DoneOutlinedIcon />}
label="Published"
clickable
onDelete={() => {
console.log(params.inputProps);
}}
onClick={(e) => handleStatusDropdownClick(params)}
deleteIcon={<ExpandMoreIcon />}
/>
</div>
)}
/>
);
}

MUI Autocomplete and react-hook-form not displaying selected option with fetched data

I have a MUI Autocomplete inside a form from react hook form that works fine while filling the form, but when I want to show the form filled with fetched data, the MUI Autocomplete only displays the selected option after two renders.
I think it's something with useEffect and reset (from react hook form), because the Autocompletes whose options are static works fine, but the ones that I also have to fetch the options from my API only works properly after the second time the useEffect runs.
I can't reproduce a codesandbox because it's a large project that consumes a real api, but I can provide more information if needed. Thanks in advance if someone can help me with this.
The page where I choose an item to visualize inside the form:
const People: React.FC = () => {
const [show, setShow] = useState(false);
const [modalData, setModalData] = useState<PeopleProps>({} as PeopleProps);
async function showCustomer(id: string) {
await api
.get(`people/${id}`)
.then((response) => {
setModalData(response.data);
setShow(true);
})
.catch((error) => toast.error('Error')
)
}
return (
<>
{...} // there's a table here with items that onClick will fire showCustomer()
<Modal
data={modalData}
visible={show}
/>
</>
);
};
My form inside the Modal:
const Modal: React.FC<ModalProps> = ({data, visible}) => {
const [situations, setSituations] = useState<Options[]>([]);
const methods = useForm<PeopleProps>({defaultValues: data});
const {reset} = methods;
/* FETCH POSSIBLE SITUATIONS FROM API*/
useEffect(() => {
api
.get('situations')
.then((situation) => setSituations(situation.data.data))
.catch((error) => toast.error('Error'));
}, [visible]);
/* RESET FORM TO POPULATE WITH FETCHED DATA */
useEffect(() => reset(data), [visible]);
return (
<Dialog open={visible}>
<FormProvider {...methods}>
<DialogContent>
<ComboBox
name="situation_id"
label="Situação"
options={situations.map((item) => ({
id: item.id,
text: item.description
}))}
/>
</DialogContent>
</FormProvider>
</Dialog>
);
};
export default Modal;
ComboBox component:
const ComboBox: React.FC<ComboProps> = ({name, options, ...props}) => {
const {control, getValues} = useFormContext();
return (
<Controller
name={`${name}`}
control={control}
render={(props) => (
<Autocomplete
{...props}
options={options}
getOptionLabel={(option) => option.text}
getOptionSelected={(option, value) => option.id === value.id}
defaultValue={options.find(
(item) => item.id === getValues(`${name}`)
)}
renderInput={(params) => (
<TextField
variant="outlined"
{...props}
{...params}
/>
)}
onChange={(event, data) => {
props.field.onChange(data?.id);
}}
/>
)}
/>
);
};
export default ComboBox;
I think you simplify some things here:
render the <Modal /> component conditionally so you don't have to render it when you are not using it.
you shouldn't set the defaultValue for your <Autocomplete /> component as RHF will manage the state for you. So if you are resetting the form RHF will use that new value for this control.
it's much easier to just use one of the fetched options as the current/default value for the <Autocomplete /> - so instead of iterating over all your options every time a change is gonna happen (and passing situation_id as the value for this control), just find the default option after you fetched the situations and use this value to reset the form. In the CodeSandbox, i renamed your control from "situation_id" to "situation". This way you only have to map "situation_id" on the first render of <Modal /> and right before you would send the edited values to your api on save.
I made a small CodeSandbox trying to reproduce your use case, have a look:
mui#v4
mui#v5
Another important thing: you should use useFormContext only if you have deeply nested controls, otherwise just pass the control to your <ComboBox /> component. As with using FormProvider it could affect the performance of your app if the form gets bigger and complex. From the documentation:
React Hook Form's FormProvider is built upon React's Context API. It solves the problem where data is passed through the component tree without having to pass props down manually at every level. This also causes the component tree to trigger a re-render when React Hook Form triggers a state update

antd cascader's default value if not working if set it in useEffect

it works if hard code it like :
<Cascader
defaultValue={["County"]}
className="custom_select"
expandTrigger="hover"
options={options_3}
onChange={onChange}
placeholder="Please select"
changeOnSelect
disabled={true}
/>
but if I use setDefaultValue() in useEffect, it is not working :
const [defaultValue, setDefaultValue] = useState([]);
useEffect(()=>{
setDefaultValue(['Global'])
},[defaultValue[0]])
online demo
it's a bug or I use it in a wrong way?
The defaultValue prop in AntD is used only on first render, since useEffect runs after first render, you end up without a default value for the cascader. Setting it afterwards does not have any effect. If you want to use defaultValue, you can make sure that when cascader renders, you will already have set the default value.
const [defaultVal, setDefaultVal] = useState();
useEffect(() => {
setDefaultVal(['zhejiang', 'hangzhou', 'xihu']);
}, []);
return (
<div>
{defaultVal && (
<Cascader
defaultValue={defaultVal}
options={options}
onChange={onChange}
/>
)}
</div>
)
You can also use a controlled component with value if you want the cascader rendered at all times.
Codesandbox

Implementing react hooks useState in material-ui Tab container not working

I am trying to implement react-hooks useState in material-ui/tabs component. I am able to do it with function handleChange, but I am trying to learn to implement hooks. useState is working for input field but not for material-ui tabs onChange function. Following is my code:
const [value, setValue] = useState(0)
<Tabs
value={value}
onChange={(event) => setValue(event.target.value)}
variant="scrollable"
scrollButtons="on"
indicatorColor="primary"
textColor="primary"
>
<Tab label="All" />
{subjects.map((subject) => (
<Tab label={subject.subjectName} />
))}
</Tabs>
I tried console log with useEffect, and it returns undefined onChange
The main issue I see is that
onChange={(event) => setValue(event.target.value)}
should instead be:
onChange={(event, newValue) => setValue(newValue)}
The event in this case is just a click event and the target will be the particular DOM element clicked on which could be any one of several elements (e.g. span, button) that make up the Tab. Unlike with input elements, none of the DOM elements in the Tab have a value property, so Material-UI passes the value as a separate argument to the onChange function.
Here is the relevant code from the Tab component:
handleChange = event => {
const { onChange, value, onClick } = this.props;
if (onChange) {
onChange(event, value);
}
if (onClick) {
onClick(event);
}
};
You'll find the onChange signature documented in the documentation for the Tabs props: https://material-ui.com/api/tabs/#props
onChange func Callback fired when the value changes. Signature: function(event: object, value: number) => void
Here's a working example based on your code:

Resources