I am using a customized, forwarded checkbox so I can pass that as a component to a DataGrid.
const CustomCheckbox = ({
checkboxRef,
...props
}: {
checkboxRef: React.ForwardedRef<unknown>;
}) => (
<Checkbox
{...props}
ref={checkboxRef as React.RefObject<HTMLButtonElement>}
sx={{
color: "#363636",
"&.Mui-checked": {
color: "#363636"
}
}}
/>
);
const ForwardCustomCheckbox = forwardRef((props, ref) => {
return <CustomCheckbox {...props} checkboxRef={ref} />;
});
<DataGrid
...
components={{
BaseCheckbox: ForwardDarkCheckbox
}}
/>
I don't like the use of as React.RefObject<HTMLButtonElement> since I understand this as a kind of bypassing TypeScript.
I couldn't make it work without that.
Do you have any suggestions or is it considered 'okay' using as in this scenario?
Change checkboxRef type to React.ForwardRef<HTMLButtonElement>, as it seems this is the only element you wish to allow for this property
Use forwardRef<HTMLButtonElement> to explicitly define the type of reference this component accepts
const CustomCheckbox = ({
checkboxRef,
...props
}: {
checkboxRef: React.ForwardedRef<HTMLButtonElement>;
}) => (
<Checkbox
{...props}
ref={checkboxRef}
sx={{
color: '#363636',
'&.Mui-checked': {
color: '#363636',
},
}}
/>
);
const ForwardCustomCheckbox = forwardRef<HTMLButtonElement>((props, ref) => {
return <CustomCheckbox {...props} checkboxRef={ref} />;
});
Related
I have a MUI TreeView where its treeItems are rendered in a function.
The problem I have is that I want to toggle disable on every treeItem.
But when I try that, the items aren't disabled until I click somewhere.
You could see it in this sandbox
(When you use the toggle button it seems to work as I expect from the second click and forward, but using the other two you see the problem I have)
const Tree = (props: Props) => {
const handleToggle = (event: React.SyntheticEvent, nodeIds: string[]) => {
setExpanded(nodeIds);
};
const renderTreeItems = (
node: MyNode,
nodeDisabled: boolean,
) => {
return (
<StyledTreeItemRoot
key={node.id}
nodeId={'' + node.id}
label={
<Box sx={{ display: 'flex', alignItems: 'center', p: 0.5, pr: 0 }}>
<Typography
variant='body2'
>
{node.title}
</Typography>
</Box>
}
disabled={nodeDisabled}
{...props.treeItemProps}
>
{Array.isArray(node.children)
? node.children.map((node) => renderTreeItems(node, nodeDisabled))
: null}
</StyledTreeItemRoot>
);
};
return (
<TreeView
expanded={expanded}
selected={props.selectedNodeId}
onNodeSelect={(_event: React.SyntheticEvent, nodeId: string) => {
props.onSelect(nodeId);
}}
onNodeToggle={handleToggle}
{...props.treeViewProps}
>
{props.tree.map((item) =>
renderTreeItems(item, props.treeDisabled ?? false),
)}
</TreeView>
);
};
export default Tree;
Solved this by adding a useEffect listening for the change of disable prop and calling a forceUpdate function like this:
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void;
useEffect(() => {
forceUpdate();
}, [forceUpdate, props.treeDisabled]);
Edit:
Although the first solution worked, I changed to adding the prop, which tells the tree to be disabled or not, to the key prop in the tree.
<TreeView
key={String(props.treeDisabled)}
expanded={expanded}
selected={props.selectedNodeId}
onNodeSelect={(_event: React.SyntheticEvent, nodeId: string) => {
props.onSelect(nodeId);
}}
onNodeToggle={handleToggle}
{...props.treeViewProps}
>
{props.tree.map((item) =>
renderTreeItems(item, props.treeDisabled ?? false),
)}
</TreeView>
I have written page that uses Tabs and Tab Panel. I'm writing UI tests using react-testing library. The theme has been modified into custom theme that is imported at the root of the Next.js app. I'm using page that uses a component with form for the Tab. In the listening it's <MyComponent />. The component inside has Select component used from ChakraUI. Other inputs don't affect on the error that appears.
Libraries:
ChakraUI 2.8
react-hook-form
Next.js 12
The error that appears is
Cannot read properties of undefined (reading '_focus')
TypeError: Cannot read properties of undefined (reading '_focus')
at /path/to/project/node_modules/#chakra-ui/select/dist/index.cjs.js:102:22
My Page in a nutchell looks like
const MyPage () => {
const instrumentInfoTab = React.useRef() as React.MutableRefObject<HTMLInputElement>;
return (
<Tabs>
<TabList><Tab>...</Tab</TabLists>
</Tabs>
<TabPanels>
<TabPanel>
<MyComponent updateTransactionState={/*some function to handle state*/} nextTabRef={instrumentInfoTab} />
</TabPanel>
</TabPanels>
<Tab
)
}
MyComponent
interface IInstrumentInfoPanelProps {
nextTabRef: React.MutableRefObject<HTMLInputElement>;
updateTransactionState: (data: InstrumentInfoInput) => void;
}
const MyComponent = (props: IInstrumentInfoPanelProps) => {
const { nextTabRef, updateTransactionState } = props;
const textColor = useColorModeValue('secondaryGray.900', 'white');
const methods = useForm<InstrumentInfoInput>({
resolver: zodResolver(instrumentInfoSchema)
});
const { handleSubmit, register } = methods;
const onSubmit = (data: InstrumentInfoInput) => {
updateTransactionState(data);
nextTabRef.current.click();
};
return (
<TabPanel>
<CustomCard>
<Text>
Instrument Info
</Text>
<Flex>
<form onSubmit={handleSubmit(onSubmit)}>
<FormProvider {...methods}>
<SimpleGrid>
<Stack>
<Flex direction="column" mb="34px">
<FormLabel
ms="10px"
htmlFor="transactionType"
fontSize="sm"
color={textColor}
fontWeight="bold"
_hover={{ cursor: 'pointer' }}>
Transaction type*
</FormLabel>
<Select
{...register('transactionType')}
id="transactionType"
variant="main"
defaultValue="buy">
<option value="buy">BUY</option>
<option value="sell">SELL</option>
</Select>
</Flex>
</Stack>
<Stack>
<InputField
id="accountName"
name="accountName"
placeholder="eg. Coinbase"
label="Account Name*"
data-testid="instrumentInfoPanel-accountName"
/>
</Stack>
</SimpleGrid>
<Flex justify="space-between" mt="24px">
<Button
type="submit">
Next
</Button>
</Flex>
</FormProvider>
</form>
</Flex>
</CustomCard>
</TabPanel>
);
};
export default MyComponent;
My Test looks like. The error appears in render function
jest.mock('next/link', () => {
return ({ children }) => {
return children;
};
});
interface IWrapedComponent {
children?: JSX.Element;
}
const test = (ref: MutableRefObject<HTMLElement | null>): void => {
if (ref.current) ref.current.focus();
};
const WrappedComponent = (props: IWrapedComponent) => {
const { children } = props;
const ref = React.useRef() as React.MutableRefObject<HTMLInputElement>;
useEffect(() => {
test(ref);
}, []);
return (
<Tabs>
<TabList>
<Tab ref={ref}></Tab>
</TabList>
<TabPanels>
<MyComponent />
</TabPanels>
</Tabs>
);
};
describe('Instrument Info Panel', () => {
it('should render inputs for instrument info', () => {
render(
<QueryClientProvider client={new QueryClient()}>
<WrappedComponent />
</QueryClientProvider>
);
});
});
In order to debug an issue I have tried to remove other inputs from form and it worked when there were different input types than Select from ChakraUI.
This is either very simple or I am doing it completely wrong. I am a novice so please advise.
I am trying to show different components inside different tabs using Material UI using array map. The tabs are showing fine but the components do not render. Basically if the array label is 'Welcome', the tab name should be 'Welcome' and the Welcome component should show up and so on. Please help!
return (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
{fetchedCategories.map((category) => (
<Tab key={category.label} label={category.label} />
))}
</Tabs>
</Box>
{fetchedCategories.map((category, index) => {
const Component=myComponents[category.label];
})}
{fetchedCategories.map((category, index) => (
<TabPanel key={category.label} value={value} index={index}>
<Component label={category.label} />
</TabPanel>
))}
</Box>
);
Here is my props & Component function:
interface ComponentProps {
label: string;
value?: number;
}
function Component (props: ComponentProps)
{
const {label, value} = props;
return myComponents[label];
}
const myComponents = {
'Welcome': Welcome,
'Salad/Soup': Welcome
}
Try something like:
function Component({ label, value }: ComponentProps) {
const [Comp, setComponent] = useState(<div />);
React.useEffect(() => {
const LabelComp = myComponents[label];
if (label && LabelComp) {
setComponent(<LabelComp value={value} />); // <-- if you want to pass value to you component
}
}, [value, label]);
return Comp;
}
And you will use it like:
const App = () => {
return <Component label={"myComponentLabel"} value={"some value"} />;
};
UPDATED!
I'm creating a wrapper for dropdown menu and use it in several components so I need to make such a Menu generic. The problem is I do not understand how to pass external variables into such a component.
My component:
const SelectOptionsPaginated = ({
alignment, minWidth, width,
rowData,
column
}) => {
..........
const Menu = (props) => {
const {options} = props;
const dropdownContainer = useRef(null);
const [maxMenuHeight, setMaxMenuHeight] = useState(300)
const [dropDownStyle, setDropDownStyle] = useState({
position: "absolute",
minWidth: `${minWidth ? minWidth + "px" : "100%"}`,
maxWidth: `${width}px`,
maxHeight: `${maxMenuHeight}px`,
top: `32px`
})
const getDropdownPosition = (elem) => {
setDropDownStyle({
...dropDownStyle,
...getDropdownAlignment(elem, setMaxMenuHeight, gridId, options, true)
})
};
useEffect(() => {
const optionsList = dropdownContainer.current
if (!optionsList) return
getDropdownPosition(optionsList)
}, [options])
return (
<div
className="dropdown-container"
ref={dropdownContainer}
style={dropDownStyle}
>
<components.Menu {...props} >
{props.children}
</components.Menu>
</div>
)
}
............
return <AsyncPaginate
additional={defaultAdditional}
isMulti={isMulti}
value={value}
loadOptions={loadOptions}
onChange={handleChange}
escapeClearsValue
isClearable
styles={getStylesForSelectorEditor(width, minWidth, newAlignment)}
components={{Menu}}
/>
such variables as minWidth, width should be passed externally to Menu.
I tried something like:
...............
return <AsyncPaginate
additional={defaultAdditional}
isMulti={isMulti}
value={value}
loadOptions={loadOptions}
onChange={handleChange}
escapeClearsValue
isClearable
styles={getStylesForSelectorEditor(width, minWidth, newAlignment)}
// pseudocode
components={{<Menu width={100}/>}} or
components={{Menu(100)}}
/>
but it doesn't work.
I tried to google but didn't find clear information. I'm new in react so will appreciate any help.
Did you meant something like that?
const AsyncPaginate= (props) => {
const {components} = props;
return (
<>
{components}
</>
)
}
const Menu = () => {
return (
<>
something...
</>
)
}
const App = () => {
return (
<>
<AsyncPaginate components={<Menu />}></AsyncPaginate>
</>
)
}
I'm trying to dynamically generate classes on the material ui Menu this way :
const useStyles = (props) => {
return makeStyles({
paper: props.style,
});
};
const StyledMenu = (props) => {
const classes = useStyles(props)();
return <Menu {...props} className={classes.paper} />;
};
render() {
const { state, fnsMenuBtn, fieldsMenuBtn, props } = this;
const { fnsMenuIsOpen, fieldsMenuIsOpen } = state;
return (
<StyledMenu
id="fnsMenuEl"
anchorEl={fnsMenuBtn}
keepMounted
open={fnsMenuIsOpen}
style={{ border: "1px solid blue" }}
onClose={(e) => {
vm.setState((state, props) => {
return {
fnsMenuIsOpen: !state.fnsMenuIsOpen,
};
});
}}
>
{Object.keys(formulajs).map((fnName) => (
<MenuItem onClick={(e) => {}} key={fnName}>
{fnName}
</MenuItem>
))}
</StyledMenu>
)
}
But the wanted style is never added to the menu
What's wrong ?
How to do it otherwise?
Insted using className try to use classes
const StyledMenu = (props) => {
const classes = useStyles(props)();
return <Menu {...props} classes={{paper: classes.paper}} />;
};