I have this FormControl element with Select that accepts an array of options that is being used for MenuItem options and also a value as props and this component looks like this:
const TaxonomySelector = (props) => {
const { isDisabled, taxonomies, selectedTaxonomy, handleTaxonomyChange } = props;
return (
<Grid item xs={12}>
{console.log(selectedTaxonomy)}
{console.log(taxonomies)}
<FormControl disabled={isDisabled} fullWidth>
<InputLabel>Таксономия</InputLabel>
<Select
value={selectedTaxonomy || ''}
onChange={handleTaxonomyChange}>
{Object.values(taxonomies).map((taxonomy) => (
<MenuItem key={taxonomy.id} name={taxonomy.name} value={taxonomy}>
{taxonomy.name} от {moment(taxonomy.date).format('YYYY-MM-DD')}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
);
};
The values that I pass as props are correctly displaying as filled out in the console at all stages the component is being rendered. And also in the case when this component is used for selection through using handleTaxonomyChange function everything is working correctly with user being able to select a particular option out of the array provided to the MenuItem. However, the problem occurs in case when the parent component of this component is being open for View Only or with already pre-defined values. In this case I get the following:
It seems like there's something is being passed to the Select component (even I checked through React Component in DevTools and value was showed correctly) but for some reason it is not being displayed.
The parent component contains the following code related to this part:
const INITIAL_SELECTED_TAXONOMY = null;
const [selectedTaxonomy, setSelectedTaxonomy] = useState(INITIAL_SELECTED_TAXONOMY);
const handleTaxonomyChange = (e) => setSelectedTaxonomy(e.target.value);
useEffect(() => {
getTaxonomies();
}, []);
useEffect(() => {
if (viewTypeOnlyView) {
handleStageChange(1);
handleDialogTitleChange('Конструктор КС. Режим просмотра');
}
if (viewTypeEdit) {
handleDialogTitleChange('Конструктор КС. Режим редактирования');
}
if (viewTypeCopy) {
handleDialogTitleChange('Конструктор КС. Дублирование КС');
}
if (defaultData) {
if (defaultData.name) setName(defaultData.name);
if (defaultData.taxonomy) setSelectedTaxonomy(defaultData.taxonomy);
// if (defaultData.entryPoints) setSelectedEntryPoints(defaultData.entryPoints);
if (defaultData.entryPoints) {
getEntryPointDescsFn('4.1', defaultData.entryPoints);
}
if (defaultData.message) setMessage(defaultData.message);
}
}, [viewType]);
ViewType is a prop that is being passed to this component and calling those methods in order to fill up the state with predefined values.
And the TaxonomySelector component inside the return statement:
<TaxonomySelector
taxonomies={taxonomies}
isDisabled={currentStage === 1}
selectedTaxonomy={selectedTaxonomy}
handleTaxonomyChange={handleTaxonomyChange} />
At first I thought that the issue could be related to how the component is being rendered and maybe it renders before that data pre-fill useEffect hook is being triggered. However, it seems that other elements, like the ones with string values name and message are being correctly filled out with no issues. Seems like that the issue is specifically related to Select elements. Could you let me know what could it possibly be?
Looks like disabled prop in FormControl is true.
For debug set disabled prop false
Related
I have a data array that I am mapping onto a material-ui Typography.
{
array.map((item, id)=> <Typography key={id} value={item.name} />)
}
The code displays the typography with their respective values on the browser as expected, however, I am trying to set the mapped value of Typography into a state like this...
const [data, setData] = useState();
...
{
array.map((item, id) =>
<Typography key={id} value={item.name} {(e)=>setData(e.target.value)} />
)}
This method does not work.
How do I make data to be the value of <Typography/>
If I understand correctly, you're trying to store item.name within local state? How are you receiving the data that you are mapping through?
Are you trying to store each item you're mapping through and represent key-value pairs of an object within local state? What are you trying to do with the state once you have it?
As far as I know, there's is no way to assign values to local state while you are mapping through an array. However, there are multiple ways to assign the data to local state outside of the return.
Also, what is this line doing?
{(e)=>setData(e.target.value)}
It seems you're trying to create a controlled input on a Typography component without an onChange prop, as well as on a component that does not take input.
One more aside, although it is not a big deal -
{
array.map((item, id) =>
<Typography key={id} value={item.name} ..../>
)}
id here is usually referred to as index
Edited for answer
I normally use Redux to manage the global state, but the process should be the same for Context Provider:
const yourComponent = () => {
const [data, setData] = useState();
// This is the array you get from the Context Provider
const yourArray = getArrayFromContextProvider();
useEffect(() => {
// If you want to normalize the array data
if (yourArray) {
const dataObj = {};
yourArray.forEach((item, index) => {
// Make the key unique by using the item id OR you can use index
dataObj[item.id] = item;
// OR you can add the item value as the key
dataObj[item.value] = item;
});
setData(dataObj);
}
// If you just want to store the array
if (yourArray) {
setData(yourArray)
}
}, [yourArray]);
return <div>
{/* ...Your map */}
</div>;
};
export default yourComponent
I am new to react and material UI and struggling to load Autocomplete component options dynamically.
Initially, I am setting empty array as initial option until data is fetched from the database. Once I get my data I update the Autocomplete options with my data and it's working but at the same time I am getting the following warning
Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
My code
const [listItems, setListItems] = useState([]);
const {formik, items} = props;
const handleGetOptionSelected = (option, value) => {
if (!items) {
return {};
}
return option.id === value.id;
};
const handleGetOptionLabel = (option) => {
if (!items) {
return 'No Options';
}
return option.name;
};
useEffect(() => {
if (items) {
setListItems(items);
}
}, [items]);
return (
<Autocomplete
className={classes.autoComplete}
multiple
id="tags-standard"
options={listItems}
getOptionLabel={(option) => handleGetOptionLabel(option)}
getOptionSelected={(option, value) => handleGetOptionSelected(option, value)}
onChange={(event, selectedValues) => {
formik.setFieldValue(
"tag_ids",
getCollectionColumn(selectedValues, 'id'),
false
);
}}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder="select tags"
name="tag_ids"
error={formik.touched.tag_ids && Boolean(formik.errors.tag_ids)}
helperText={formik.touched.tag_ids && formik.errors.tag_ids}
/>
)}
/>
);
For an input to be controlled, its value must correspond to that of a state variable.
That condition is not initially met in your example because your state is not initially set before onChange. Therefore, the input is initially uncontrolled. Once the onChange handler is triggered for the first time, your formik data state gets set. At that point, the above condition is satisfied and the input is considered to be controlled. This transition from uncontrolled to controlled produces the error seen above.
By initializing formik data structure in the constructor to an empty string
the input will be controlled from the start, fixing the issue.
e.g If you want to set name inside formik then initialize it as empty string then change it
on onChange according to need. Like this:
constructor(props) {
super(props);
this.state = { name: '' }
}
I don't know your data structure so you can try this on your own by mimicking this.
See React Controlled Components for more examples. Also try this solution
I have an array of objects that looks like this:
const columns = [
{
key: "Source_campname",
title: "TS Camp Name",
customElement: function (row) {
return (
<FormControlLabel
control={
<Checkbox
checked={checkbox[row.id]}
key={row.id}
onChange={() =>
handleChange(row.Source_campname, row.id, checkbox)
}
name={row.id}
/>
}
label={[row.Source_campname]}
/>
);
}
},
{
key: "Tracker_campname",
title: "TR Camp Name"
}
];
You can see a "handleChange" function above, this is used to check/uncheck the component
The handleChange function looks like this:
const handleChange = (name, campid) => {
setCheckBox({ ...checkbox, [campid]: !checkbox[campid] });
};
You can also see a "customElement" function above. This function is rendered in another React component named ThanosTable. I will just write down part of the code where the rendering of customElement happens below.
return (
<> columnArray[0].customElement(row) </>
);
In the end you get 10 checkboxes, and you have a few pages that can be changed using pagination.
Do check my codesandbox link here for a working example:
https://codesandbox.io/s/magical-germain-8tclq
Now I have two problems:
Problem 1) If I select a few checkboxes, then go to second page and return, the checkbox state is empty and the original checkboxes are unselected. No idea why that is happening. How do I prevent that?
Problem 2) The value of checkbox state is always an empty object ({}) inside customElement function. You can see this by checking console.log(checkbox) inside customElement function (Check Line 76 in codesandbox). I thought it should be an array with selected checkbox items.
The useEffect hook embodies all the lifecycle events of a component. Therefore if you try to set checkbox in useEffect it'll infinitely update the component because updating state calls useEffect. This is probably why you see your state constantly being reset.
Instead, initialize your state with the rows before rendering.
const rows = [
...
];
let checkboxObj = {};
// if (rows) {
rows.forEach((e) => {
checkboxObj[e.id] = false;
});
const [checkbox, setCheckBox] = useState(checkboxObj);
I'm having issues getting the Detail Panel of Material Table to re-render when there is a change to the tab selection of a Material-UI tab component. What I'm expecting to happen is when I select the second tab in the tab list, the styling and component should re-render to reflect that in the DOM. As of right now that isn't happening. The value property is being updated, but the DOM is never being re-rendered from the value change. The value property I'm passing to the handleChange function is an index. So for 3 tabs, there would be 3 different values (0, 1, 2)
You can see from this example , when you click a subsequent tab in the AppBar, the state is updated and changed automatically. I'm able to effectively change the 'value' property by clicking a different tab, but the Detail Panel is never re-rendered and the first tab is always selected.
This PR had a similar issue but I wasn't able to get any of the answers to work for my need.
import AppBar from '#material-ui/core/AppBar'
import Tabs from '#material-ui/core/Tabs'
import Tab from '#material-ui/core/Tab'
function TableComponent(props) {
const [value, setValue] = React.useState(0)
const handleChange = (event, newValue) => {
setValue(newValue)
}
function getVersionsTabs (rowData) {
const versions = Object.keys(rowData.versions)
var versionList = versions.map(function (name, index) {
const version = rowData.versions[name]
return <Tab key={index} label={version.versionAlias} />
})
return versionList
}
return (
<MaterialTable
...otherProps
detailPanel={
rowData => {
return (
<div>
<AppBar position='static' color='default'>
<Tabs value={value} onChange={handleChange} indicatorColor='primary' textColor='primary'>
{getVersionsTabs(rowData)}
</Tabs>
</AppBar>
</div>
)
}
/>
)
}
Any help is greatly appreciated!
I'm trying to connect material-ui ToggleButtonGroup with redux form and getting issues with this.
Here is my code:
<Field
name='operator'
component={FormToggleButtonGroup}
>
<ToggleButton value='equals'>Equal</ToggleButton>
<ToggleButton value='not_equals'>Not equal</ToggleButton>
</Field>
.. and my component, passed to Field:
const FormToggleButtonGroup = (props) => {
const {
input,
meta,
children
} = props;
return (
<ToggleButtonGroup
{...input}
touched={meta.touched.toString()}
>
{children}
</ToggleButtonGroup>
);
};
export default FormToggleButtonGroup;
the problem is, when I select value (toggle option), selected value is not passed to redux store, it passed only after loosing focus and then throws error 'newValue.splice is not a function'
Please help to deal with this issue
Sandbox with sample code
Playing with the component I finally found the solution.
I need manually assign new value got from ToggleButtonGroup component and put this value to redux store. Here is how working code looks:
const FormToggleButtonGroup = (props) => {
const {
input,
meta,
children,
...custom
} = props;
const { value, onChange } = input;
return (
<ToggleButtonGroup
{...custom}
value={value}
onChange={(_, newValue) => {
onChange(newValue);
}}
touched={meta.touched.toString()}
>
{children}
</ToggleButtonGroup>
);
};
Main change is getting redux's function onChange and call it with new value, selected when value toggled. There is onChange related to ToggleButtonGroup component and another onChange related to Redux. You need to call latter when ToggleButtonGroup's onChange occurs.