I've been out of the React game for awhile. Come back and I'm trying to implement the Material UI library which has been rewritten with Hooks.
It seems to be extremely confusing + spagetti code in my eyes.
I simply want to reference a function so I can toggle the drawer, how can I do this?
// Old class
export default class DefaultContainer extends Component<ViewProps, any> {
render() {
return (
<View>
<MainAppBar
onPress={() => this.onMenuPressed()}
/>
{this.props.children}
<MainDrawer
ref={'drawer'}
/>
</View>
);
}
onMenuPressed = () => {
// TODO The bit that isn't working
(this.refs['drawer'] as Drawer).handleToggle()
}
}
Now the new material UI drawer
// New Drawer (3x more code now..)
const useStyles = makeStyles({
list: {
width: 280,
},
fullList: {
width: 'auto',
},
})
type Props = {
}
function MainDrawer(props: Props, ref: any) {
const classes = useStyles();
const [state, setState] = React.useState({
left: false,
});
const toggleDrawer = () => (
event: React.KeyboardEvent | React.MouseEvent,
) => {
if (
event.type === 'keydown' &&
((event as React.KeyboardEvent).key === 'Tab' ||
(event as React.KeyboardEvent).key === 'Shift')
) {
return;
}
setState({ ...state, left: true });
};
const inputRef = useRef();
useImperativeHandle(ref, () => {
toggleDrawer()
});
const sideList = () => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer()}
onKeyDown={toggleDrawer()}
>
<List>
<ListItem button key={'drawer_item'}>
<ListItemIcon><GroupIcon /></ListItemIcon>
<ListItemText primary={'Test Item'} />
</ListItem>
</List>
</div>
);
return (
<div>
<Button onClick={toggleDrawer()}>Open Left</Button>
<Drawer open={state.left} onClose={toggleDrawer()}>
{sideList()}
</Drawer>
</div>
);
}
export default forwardRef(MainDrawer);
I'm struggling to understand why you need to invoke a function from inside MainDrawer rather than just leveraging the use of props e.g.
Container
export default function DefaultContainer(props: ViewProps) {
const [drawerOpen, setDrawerOpen] = React.useState(false);
// assuming it's a toggle?
const toggleMenu = React.useCallback(() => setDrawerOpen(open => !open));
return (
<View>
<MainAppBar onPress={toggleMenu} />
{this.props.children}
<MainDrawer open={drawerOpen} />
</View>
)
}
MainDrawer
function MainDrawer(props: Props) {
const [open, setOpen] = React.useState(props.open);
...
const toggleDrawer = React.useCallback(() => setOpen(open => !open));
return (
<div>
<Button onClick={toggleDrawer}>Open Left</Button>
// use prop to determine whether drawer is open or closed
<Drawer open={open} onClose={toggleDrawer}>
{sideList()}
</Drawer>
</div>
);
}
Related
Im using Redux-toolkit (already using it for a while). When I reset my redux store, and load my website for the first time the SharedList component does not update if I update the store.
But the strange thing... after refreshing the page, everything works perfect!
Im using the [...array] and Object.assign({}) methods. Also console logged, and the objects are NOT the same.
this is the reducer: (Only updates the ReactJS component after I refresh my page)
CREATE_SHARED_LIST_ITEM_LOCAL: (
proxyState,
action: PayloadAction<{
active: boolean;
checked: boolean;
id: string;
text: string;
}>
) => {
const state = current(proxyState);
const [groupIndex, group] = getCurrentGroupIndex(state);
const { id, text, active, checked } = action.payload;
const listItemIndex = group.shared_list.findIndex((item) => {
return item.id === id;
});
const group_id =
proxyState.groups[groupIndex].shared_list[listItemIndex].group_id;
const sharedItem: SharedListItem = {
text,
id,
checked,
active,
group_id,
};
proxyState.groups[groupIndex].shared_list[listItemIndex] = Object.assign(
{},
sharedItem
);
},
This is the ReactJS component
const GroceryList: React.FC = () => {
const [update, setUpdate] = useState(false);
const handleUpdate = () => {};
const dispatch = useDispatch();
const listItems = useSelector(selectSharedList);
const { setTitle } = useContext(HeaderContext);
const editItem = async (
id: string,
checked: boolean,
text: string,
active: boolean
) => {
dispatch(MUTATE_SHARED_LIST_LOCAL({ id, text, active, checked }));
};
const fetchList = async () => {
await dispatch(QUERY_SHARED_LIST({}));
};
useEffect(() => {
setTitle("Lijst");
fetchList();
}, []);
const list = listItems.filter((item) => {
return item.active;
});
return (
<>
<Card
// style={{
// paddingBottom: 56,
// }}
>
<CardContent>
<List>
{list.length === 0 ? (
<Box textAlign="center" justifyContent="center">
{/* <Center> */}
<Box>
<EmptyCartIcon style={{ width: "45%" }} />
</Box>
{/* </Center> */}
<Typography>De gedeelte lijst is leeg</Typography>
</Box>
) : null}
{list.map((item, index) => {
const { checked, text, id, active } = item;
return (
<ListItem dense disablePadding key={"item-" + index}>
<Checkbox
checked={checked}
onClick={() => {
editItem(id, !checked, text, active);
}}
/>
<EditTextSharedListItem item={item} />
<Box>
<IconButton
onClick={() => {
editItem(id, checked, text, false);
}}
>
<DeleteOutlineRoundedIcon />
</IconButton>
</Box>
</ListItem>
);
})}
</List>
</CardContent>
</Card>
<AddSharedListItem />
</>
);
};
This is the selector
export const selectSharedList = (state: RootState) => {
const group = selectCurrentGroup(state);
return group.shared_list.filter((item) => {
return item.active;
});
};
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>
</>
)
}
In my parent component I call hook useRef: const flatListRef = useRef(null); and then I want to use this flatListRef in child component. I tried to do like in documentation but without success. When I call my function toTop I get: null is not an object (evaluating 'flatListRef.current.scrollToOffset')
This is my parent component:
const BeautifulPlacesCards = ({navigation}: HomeNavigationProps<"BeautifulPlacesCards">) => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1)
}
const buttonPressed = () => {
toTop()
}
return(
<Carousel filteredData={filteredData} flatListRef={flatListRef}/>
)
}
This is my child component:
const Carousel = forwardRef((filteredData, flatListRef) => {
return (
<AnimatedFlatList
ref={flatListRef}
/>
)
}
Here is a working example: https://snack.expo.dev/#zvona/forwardref-example
Key takes:
you need to use prop ref when passing it down, not flatListRef
you need to destructure filteredData from props
Here is the relevant code:
const Child = forwardRef(({ filteredData }, ref) => {
return (
<FlatList
ref={ref}
style={styles.flatList}
data={filteredData}
renderItem={({ item }) => (
<Text style={styles.item} key={`foo-${item}`}>
{item}
</Text>
)}
/>
);
});
const App = () => {
const flatListRef = useRef(null);
const toTop = () => {
flatListRef.current.scrollToOffset(1);
};
return (
<View style={styles.container}>
<Button title={'Scroll back'} onPress={toTop} />
<Child filteredData={[1,2,3,4,5,6]} ref={flatListRef} />
</View>
);
};
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}} />;
};
I have a screen and I need to show multiple modals in it. For example, if some request was failed then I want to show an error modal, if some request was successful then I want to show a success modal.
I currently do it as following which I think is bad practice:
...
export default function SomeSampleScreen(props) {
const [errorModalVisible, setErrorModalVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [successModalVisible, setSuccessModalVisible] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
function showError(message) {
setErrorMessage(message);
setErrorModalVisible(true);
}
function showSuccess(message) {
setSuccessMessage(message);
setSuccessModalVisible(true);
}
return (
<>
<ErrorModal
visible={errorModalVisible}
onClose={() => {
setErrorModalVisible(false);
}}>
{errorMessage}
</ErrorModal>
<SuccessModal
visible={successModalVisible}
onClose={() => {
setSuccessModalVisible(false);
}}>
{successMessage}
</SuccessModal>
<View>
...
</View>
</>
);
}
You could just condense it into one object:
export default function SomeSampleScreen(props) {
const [modalState, setModalState] = useState({state: ''});
function showError(message) {
setModalState({state: "error", message});
}
function showSuccess(message) {
setModalState({state: "success", message});
}
return (
<>
<ErrorModal
visible={modalState.state === "error"}
onClose={() => {
setModalState({state: ''});
}}>
{modalState.message}
</ErrorModal>
<SuccessModal
visible={modalState.state === "success"}
onClose={() => {
setModalState(false);
}}>
{modalState.message}
</SuccessModal>
<View>
...
</View>
</>
);
}
UDPATE
After clarifying what the actual question is, here is a good way to do it.
Create a Context:
const ModalContext = React.createContext({status: "", message: ""});
Add the context and the modals somewhere up in your tree:
constructor(props) {
super(props);
this.setModal = (modalState) => {
this.setState(state => ({...state, ...modalState}));
};
this.state = {
status: "",
message: "",
setModal: this.setModal,
};
}
return <ModalContext.Provider value={this.state.modalState}>
<RestOfApp />
<Modals/>
</ModalContext.Provider>
The Modals would be similar to what you postet:
export default function SomeSampleScreen(props) {
const modalState = useContext(ModalContext);
function showError(message) {
modalState.setModal({state: "error", message});
}
function showSuccess(message) {
modalState.setModal({state: "success", message});
}
return (
<>
<ErrorModal
visible={modalState.state === "error"}
onClose={() => {
modalState.setModal({state: ''});
}}>
{modalState.message}
</ErrorModal>
<SuccessModal
visible={modalState.state === "success"}
onClose={() => {
modalState.setModal(false);
}}>
{modalState.message}
</SuccessModal>
<View>
...
</View>
</>
);
}
You can use a conditional render to return different data on the same modal.
Like this:
export default function SomeSampleScreen(props) {
const [ModalVisible, setModalVisible] = useState(false);
const [errorMessage, setErrorMessage] = useState(false);
const [successMessage, setSuccessMessage] = useState(false);
function showError(message) {
setErrorMessage(message);
setModalVisible(true);
}
function showSuccess(message) {
setSuccessMessage(message);
setModalVisible(true);
}
return (
<>
<Modal
visible={ModalVisible}
onClose={() => {
setSuccessModalVisible(false);
setSuccessMessage(false);
setErrorMessage(false);
}}>
{errorMessage && (
{errorMessage}
)}
{ successMessage && (
{successMessage}
)}
</Modal>
<View>
...
</View>
</>
);
}