I am trying to create a custom hook with a slider ui element. My goal is to be able to access the slider value from the parent element so as to update some other ui parts.
However, it seems that the slider values do not update correctly: when the user tries to drag the slider tooltip it only moves one step. It seems like the mouse events stop being tracked after useEffect gets called.
What can I do to fix this and have a smooth dragging behaviour?
Here is my code (sandbox):
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.querySelector('#root'));
demo.js
import React, { useEffect } from "react";
import useSlider from "./slider";
function CustomizedSlider() {
const [CustomSlider, sliderValue] = useSlider("Slider", 50);
useEffect(() => {
console.log("Slider value: " + sliderValue);
}, [sliderValue]);
return <CustomSlider />;
}
export default CustomizedSlider;
slider.js
import React, { useState } from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({...
}));
const PrettoSlider = withStyles({...
})(Slider);
export default function useSlider(label, defaultState) {
const classes = useStyles();
const [state, setState] = useState(defaultState);
const CustomSlider = () => {
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={state}
onChange={(event, v) => {
setState(v);
}}
/>
</Paper>
);
};
return [CustomSlider, state];
}
Thanks a lot for your help!
The issue is that your CustomSlider is a new component type for each render due to it being a unique function each time. This causes it to unmount/remount with each render rather than just re-render which will cause all sorts of issues (as you've seen).
Rather than a custom hook, I think you really just want a custom component. Below is one way you could structure it with only minimal changes to your initial code.
demo.js
import React, { useEffect } from "react";
import CustomSlider from "./CustomSlider";
function CustomizedSlider() {
const [value, setValue] = React.useState(50);
useEffect(() => {
console.log("Slider value: " + value);
}, [value]);
return <CustomSlider label="Slider" value={value} setValue={setValue} />;
}
export default CustomizedSlider;
CustomSlider.js
import React from "react";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Paper from "#material-ui/core/Paper";
import Slider from "#material-ui/core/Slider";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles(theme => ({
root: {
width: 300 + 24 * 2,
padding: 24
},
margin: {
height: theme.spacing(1)
}
}));
const PrettoSlider = withStyles({
root: {
color: "#a2df77",
height: 8
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit"
}
},
active: {},
valueLabel: {
left: "calc(-50% + 4px)"
},
track: {
height: 8,
borderRadius: 4
},
rail: {
height: 8,
borderRadius: 4
}
})(Slider);
const CustomSlider = ({ label, value, setValue }) => {
const classes = useStyles();
return (
<Paper className={classes.root}>
<div className={classes.margin} />
<Typography gutterBottom>{label}</Typography>
<PrettoSlider
valueLabelDisplay="auto"
aria-label="pretto slider"
defaultValue={50}
value={value}
onChange={(event, v) => {
setValue(v);
}}
/>
</Paper>
);
};
export default CustomSlider;
Related
i am trying to run a unit testing for my react-native component. My component seems complex. i tired all possible ways to get it done.
My CheckBox component wrapped with HOC withTheme
import { View, Text, Pressable, StyleSheet } from 'react-native'
import React, { useState } from 'react'
import { scale } from 'react-native-size-matters'
import MaterialIcons from 'react-native-vector-icons/dist/MaterialIcons'
import withTheme from 'Theme/withTheme'
import Label from 'Components/Label'
import testID from 'Utils/common'
function CheckBox({ theme: { colors }, label }) {
const styles = style(colors)
const [checked, setChecked] = useState(false)
const onPress = () => {
setChecked(!checked)
}
return (
<Pressable onPress={onPress} style={styles.wrapper} {...testID(`CheckBox wrapper`)}>
<View style={styles.iconWrapper} {...testID(`CheckBox wrapper check`)}>
{checked && <MaterialIcons name="done" size={scale(15)} color={colors.black} />}
</View>
<View style={{ paddingHorizontal: scale(10) }} {...testID(`CheckBox Label Wrapper`)}>
<Label text={label} style={{ fontSize: scale(14) }} {...testID(`CheckBox label ${label}`)}/>
</View>
</Pressable>
)
}
export default withTheme(CheckBox)
const style = StyleSheet.create((colors) => ({
wrapper: {
flexDirection: 'row',
alignItems: 'center'
},
iconWrapper: {
backgroundColor: colors.primary,
height: scale(25),
width: scale(25), borderRadius: scale(4),
justifyContent: "center",
alignItems: 'center'
}
}))
withTheme HOC Code
withTheme has hook useTheme
import React, { forwardRef } from 'react';
import { useTheme } from './ThemeProvider';
function withTheme(WrappedComponent, props) {
return forwardRef(function (someProps, ref) {
const { theme } = useTheme();
return (
<WrappedComponent ref={ref} {...someProps} {...props} theme={theme} />
);
});
}
export default withTheme;
What i tried
Tried mockcing HOC
Tried mockcing withTheme hook
Refered this Blog
test written
/* ---------- Packages Import ---------- */
import React from 'react';
import {render, cleanup, fireEvent} from '#testing-library/react-native';
import CheckBox from '../index';
import testID from 'Utils/common';
/* ---------- Components Import ---------- */
jest.mock('#react-native-async-storage/async-storage', () =>
require('#react-native-async-storage/async-storage/jest/async-storage-mock'),
);
jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter');
jest.mock('Theme/__mock__/withTheme');
jest.mock('Theme/__mock__/useTheme');
afterEach(cleanup);
describe('<CheckBox />', () => {
it('should match the snapshot', () => {
const rendered = render(<CheckBox />).toJSON();
expect(rendered).toMatchSnapshot();
});
});
Error i am getting
Any help would be appricated, Thank in advance
I am trying to implement a dark toggle in my website. I have gotten the toggle to show up in the correct place. I am using the usestate hook to implement the toggle functionality. However, on clicking the toggle, does not change the theme. I don't understand what is going wrong over here.
Here is the code of the different components.
I have implemented a component for the toggle theme button. Here is the code for togglethemebutton.js
import React, { useState } from "react";
// ----------------------------------------------------------------------
import { Switch } from '#mui/material';
import { createTheme } from '#mui/material/styles';
// ----------------------------------------------------------------------
export default function ToggleThemeButton() {
const [darkState, setDarkState] = useState(false);
const palletType = darkState ? "dark" : "light";
const darkTheme = createTheme({
palette: {
type: palletType,
}
});
const handleThemeChange = () => {
setDarkState(!darkState);
};
return (
<Switch checked={darkState} onChange={handleThemeChange} />
);
}
I am using this component in the Navbar.js component.
import PropTypes from 'prop-types';
// material
import { alpha, styled } from '#mui/material/styles';
import { Box, Stack, AppBar, Toolbar, IconButton } from '#mui/material';
// components
import Iconify from '../../components/Iconify';
//
import Searchbar from './Searchbar';
import AccountPopover from './AccountPopover';
import LanguagePopover from './LanguagePopover';
import NotificationsPopover from './NotificationsPopover';
import ToggleThemeButton from './ToggleThemeButton';
// ----------------------------------------------------------------------
const DRAWER_WIDTH = 280;
const APPBAR_MOBILE = 64;
const APPBAR_DESKTOP = 92;
const RootStyle = styled(AppBar)(({ theme }) => ({
boxShadow: 'none',
backdropFilter: 'blur(6px)',
WebkitBackdropFilter: 'blur(6px)', // Fix on Mobile
backgroundColor: alpha(theme.palette.background.default, 0.72),
[theme.breakpoints.up('lg')]: {
width: `calc(100% - ${DRAWER_WIDTH + 1}px)`
}
}));
const ToolbarStyle = styled(Toolbar)(({ theme }) => ({
minHeight: APPBAR_MOBILE,
[theme.breakpoints.up('lg')]: {
minHeight: APPBAR_DESKTOP,
padding: theme.spacing(0, 5)
}
}));
// ----------------------------------------------------------------------
DashboardNavbar.propTypes = {
onOpenSidebar: PropTypes.func
};
export default function DashboardNavbar({ onOpenSidebar }) {
return (
<RootStyle>
<ToolbarStyle>
<IconButton
onClick={onOpenSidebar}
sx={{ mr: 1, color: 'text.primary', display: { lg: 'none' } }}
>
<Iconify icon="eva:menu-2-fill" />
</IconButton>
<Searchbar />
<Box sx={{ flexGrow: 1 }} />
<Stack direction="row" alignItems="center" spacing={{ xs: 0.5, sm: 1.5 }}>
// here is the toggle button
<ToggleThemeButton/>
<LanguagePopover />
<NotificationsPopover />
<AccountPopover />
</Stack>
</ToolbarStyle>
</RootStyle>
);
}
A posible solution may be to move the logic to change the theme from the ToggleThemButton component to the NavBar and just pass the values needed like this:
ToggleThemeButton:
export default function ToggleThemeButton({handleThemeChange, darkState}) {
return (
<Switch checked={darkState} onChange={handleThemeChange} />
);
}
Then in the NavBar component you could add a theme variable that its updated by the ToggleThemeButton, here i used a new createTheme with the pallete that has a background property just for testing (i don't know much about material ui)
Navbar:
export default function DashboardNavbar({ onOpenSidebar }) {
const [darkState, setDarkState] = useState(false);
const palletType = darkState ? "dark" : "light";
const darkTheme = createTheme({
palette: {
type: palletType,
background: {
default: "#fff"
}
}
});
const [theme, setTheme] = useState(darkTheme);
const handleThemeChange = () => {
setDarkState(!darkState);
setTheme(createTheme({
palette: {
type: palletType,
background: {
default: !darkState? "#000" : "#fff"
}
}
}))
};
return (
<RootStyle theme={theme}>
..... <ToggleThemeButton handleThemeChange={handleThemeChange} darkState={darkState} /> ....
</ToolbarStyle>
</RootStyle>
);
}
I'm moving React App components from Material Ui4 to 5 and I face some issues with few components. One of them is that for TextField component I'm getting below comment from Typescript once I hover over : const classes = outlinedPadding(props);
const outlinedPadding: never
This expression is not callable.
Type 'never' has no call signatures.ts(2349)
import { TextField } from '#mui/material';
import { TextFieldBackgroundProps } from '../../textfield/text-field';
import { FC } from 'react';
import { makeStyles } from '#mui/material/styles';
const outlinedPadding = makeStyles({
root: {
'& label.MuiFormLabel-root': {
backgroundColor: (
props: React.PropsWithChildren<TextFieldBackgroundProps>
) => props.backgroundcolor ?? '#ffff',
paddingRight: '5px',
fontSize: '11px',
},
},
});
type PropsModelType = TextFieldBackgroundProps;
export const BaseInput: FC<PropsModelType> = (props) => {
const classes = outlinedPadding(props);
return (
<TextField
{...props}
variant="outlined"
inputProps={{
'aria-label':
props.inputProps?.['aria-label'] ?? (props.label as string),
...props.inputProps,
}}
fullWidth
className={classes.root}
/>
);
};
Appreciate Your replies
Hello I'm new to stroybook and and would liek to use custom colors on my component but i keep on getting errors.
This is the code below, could sb help me to fix this so I can see how to use storybook material ui elements in storybook..
the code:
import "bootstrap/dist/css/bootstrap.min.css";
import React from "react";
import { deepOrange, deepPurple } from '#material-ui/core/colors';
import { makeStyles } from '#material-ui/core/styles';
import Avatar from '#material-ui/core/Avatar';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
'& > *': {
margin: theme.spacing(1),
},
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
},
purple: {
color: theme.palette.getContrastText(deepPurple[500]),
backgroundColor: deepPurple[500],
},
}));
And this is the error.
export default {
const classes = useStyles();
title: "Components/Avatar/Number",
component: Avatar
}
export const _Avatar = () => (
<>
<Avatar>H</Avatar>
<Avatar className={classes.orange}>N</Avatar>
<Avatar className={classes.purple}>OP</Avatar>
</>
);
ERROR in
src\stories\Avatars\avatar.stories.js
Parsing error: Unexpected keyword 'const'.
export default {
const classes = useStyles();
^
title: "Components/Avatar/Number",
component: Avatar
}
It is syntactically incorrect to use the const keyword and the = operator inside your default export object. Try the following code instead:
export default {
classes: useStyles(),
title: "Components/Avatar/Number",
component: Avatar
}
I am a react beginner. I use hooks in the class and the program reports errors
src\page\App.js
Line 41:35: React Hook "React.useState" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Line 44:39: React Hook "React.useState" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
this is my code
import './App.css';
import React from "react";
import {BottomNavigation, BottomNavigationAction} from "#material-ui/core";
import {AccountCircle, Home, Info} from "#material-ui/icons";
import makeStyles from "#material-ui/core/styles/makeStyles";
import Grid from "#material-ui/core/Grid";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import styled from "#material-ui/core/styles/styled";
class App extends React.Component {
render() {
makeStyles(() => ({
title: {
color: "#ffffff"
},
toolbar: {
background: "#3f51b5"
},
contents: {
marginBottom: "100px"
},
bottomNavigation: {
left: '0px',
right: '0px',
position: 'fixed',
bottom: '0px',
}
}));
const {classes} = this.props
const MyGrid = styled(Grid)({
marginTop: 10,
});
//切换标签选中的钩子
const [value, setValue] = React.useState('home');
//切换toolbar的钩子
const [tbValue, setTbValue] = React.useState("主页");
const handleChange = (event, newValue) => {
setValue(newValue);
console.log("switch" + newValue)
//切换tb的字
switch (newValue) {
case 'home':
setTbValue("主页");
this.props.history.push('/home/user_login');
break;
case 'info':
setTbValue("信息");
this.props.history.push('/home/info');
break;
case 'me':
setTbValue("我的");
this.props.history.push('/home/me');
break;
}
};
return (
<div>
<AppBar position="static" style={{left: "0", right: "0"}}>
<Toolbar className={classes.toolbar}>
<Typography className={classes.title}>
{tbValue}
</Typography>
</Toolbar>
</AppBar>
{/*内容区*/}
<div className={classes.contents}>
<div>
<MyGrid container direction={'column'} justify={"center"}
alignContent={"center"}>
{this.props.children}
</MyGrid>
</div>
</div>
<BottomNavigation className={classes.bottomNavigation}
value={value}
onChange={handleChange} showLabels>
<BottomNavigationAction label="主页" value='home' icon={<Home/>}/>
<BottomNavigationAction label="信息" value='info' icon={<Info/>}/>
<BottomNavigationAction label="我的" value='me' icon={<AccountCircle/>}/>
</BottomNavigation>
</div>
);
}
}
export default App;
When i use method everything is normal
import './App.css';
import React from "react";
import {BottomNavigation, BottomNavigationAction} from "#material-ui/core";
import {AccountCircle, Home, Info} from "#material-ui/icons";
import makeStyles from "#material-ui/core/styles/makeStyles";
import Grid from "#material-ui/core/Grid";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import styled from "#material-ui/core/styles/styled";
function App(props) {
makeStyles(() => ({
title: {
color: "#ffffff"
},
toolbar: {
background: "#3f51b5"
},
contents: {
marginBottom: "100px"
},
bottomNavigation: {
left: '0px',
right: '0px',
position: 'fixed',
bottom: '0px',
}
}));
const {classes} = props
const MyGrid = styled(Grid)({
marginTop: 10,
});
//切换标签选中的钩子
const [value, setValue] = React.useState('home');
//切换toolbar的钩子
const [tbValue, setTbValue] = React.useState("主页");
const handleChange = (event, newValue) => {
setValue(newValue);
console.log("switch" + newValue)
//切换tb的字
switch (newValue) {
case 'home':
setTbValue("主页");
props.history.push('/home/user_login');
break;
case 'info':
setTbValue("信息");
props.history.push('/home/info');
break;
case 'me':
setTbValue("我的");
props.history.push('/home/me');
break;
}
};
return (
<div>
<AppBar position="static" style={{left: "0", right: "0"}}>
<Toolbar className={classes.toolbar}>
<Typography className={classes.title}>
{tbValue}
</Typography>
</Toolbar>
</AppBar>
{/*内容区*/}
<div className={classes.contents}>
<div>
<MyGrid container direction={'column'} justify={"center"}
alignContent={"center"}>
{props.children}
</MyGrid>
</div>
</div>
<BottomNavigation className={classes.bottomNavigation}
value={value}
onChange={handleChange} showLabels>
<BottomNavigationAction label="主页" value='home' icon={<Home/>}/>
<BottomNavigationAction label="信息" value='info' icon={<Info/>}/>
<BottomNavigationAction label="我的" value='me' icon={<AccountCircle/>}/>
</BottomNavigation>
</div>
);
}
export default App;
Using hooks in class component is not allowed. As stated in the react docs:
You can’t use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.
Link: https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both