React Redux state conditional bug and useEffect dependency bugs out - javascript

I have a component that gets a detail of a single post via params.id the component works when I remove all the dependencies on the useEffect but doesnt input the value from the redux state to the component states, when I put the dependencies back it gives me an error of title is undefined, but the redux action returns successfully as I have the data, I think the component loads first before receiving the redux action payload?
const UpdatePost = ({ match, history }) => {
const postId = match.params.id;
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [image, setImage] = useState('');
const dispatch = useDispatch();
const postDetails = useSelector(state => state.postDetails);
const { loading, error, post } = postDetails;
const postUpdate = useSelector(state => state.postUpdate);
const {
loading: loadingUpdate,
error: errorUpdate,
success: successUpdate,
} = postUpdate;
useEffect(() => {
if (successUpdate) {
dispatch({ type: POST_UPDATE_RESET });
dispatch({ type: POST_DETAILS_RESET });
history.push('/edit/post');
} else {
console.log(post, postId);
if (!post.title || post._id !== postId) {
dispatch(listPostDetails(postId));
} else {
setTitle(post.title);
setDescription(post.description);
setImage(post.image);
}
}
}, [post, history, dispatch, postId, successUpdate]);
const submitHandler = e => {
e.preventDefault();
dispatch(
updatePost({
_id: postId,
title,
description,
image,
})
);
};
return (
<MotionBox
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1 }}
bgColor={'#eaeaea'}
my="1rem"
border="2px"
borderColor="#eaeaea"
borderRadius="25px"
p={{ base: '2rem 2rem' }}
>
<Heading color={'#435943'} size="lg" pb={'1.5rem'}>
Edit User
</Heading>
{loadingUpdate && <Loader />}
{errorUpdate && <Message status="error">{errorUpdate}</Message>}
{loading && loadingUpdate ? (
<Loader />
) : error ? (
<Message status="error">{error}</Message>
) : (
<form onSubmit={submitHandler}>
<FormControl pb={'1rem'}>
<FormLabel size="md">Edit Post Title</FormLabel>
<Input
borderColor="#d4d6d5"
placeholder="Edit Post Title"
value={title}
onChange={e => setTitle(e.target.value)}
/>
</FormControl>
<FormControl pb={'1rem'}>
<FormLabel size="md">Edit Post Description</FormLabel>
<Textarea
size="md"
value={description}
onChange={e => setDescription(e.target.checked)}
placeholder="Edit Post Descrription"
/>
</FormControl>
<FormControl pb={'1rem'}>
<FormLabel size="md">Edit Post Photo</FormLabel>
<Input
size="md"
placeholder="Edit Post Photo"
value={image}
onChange={e => setImage(e.target.checked)}
/>
</FormControl>
<Button
type="submit"
my={'2rem'}
fontSize={'md'}
fontWeight={600}
color="white"
bg={'green.800'}
_hover={{
background: 'green.600',
}}
_focus={{
outline: 'none',
border: 'none',
}}
>
Update
</Button>
</form>
)}
</MotionBox>
);
};

Related

Displaying loading indicator screen accroding to state

I'm attempting to show a simple loading screen component when the data is connecting to firebase (creating a user, or login in). I have set all the indicators with a useState, although when the loading occurs, the screen doesn't pop up.
My register screen:
export function Register({ navigation }: any) {
const [showModal, setShowModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (isLoading) return <Loading />;
}, [isLoading]);
return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<>
<Modal
visible={showModal}
text={i18n.t('registerModal.title')}
state="success"
description={i18n.t('registerModal.description')}
buttonText={i18n.t('registerModal.goToLogin')}
navigation={undefined}
setShowModal={setShowModal}
onPress={() => {
navigation.navigate('SignIn');
setShowModal(false);
}}
/>
<Container>
<ArrowContainer>
<ArrowTouchable onPress={() => navigation.goBack(null)}>
<ArrowBack width={24} height={24} />
</ArrowTouchable>
</ArrowContainer>
<TitleContainer>
<Title>{i18n.t('signup.title')}</Title>
</TitleContainer>
<Form setShowModal={setShowModal} setIsLoading={setIsLoading} />
<TextContainer>
<Text>{i18n.t('signup.alreadyHaveAccount')}</Text>
<TouchableText onPress={() => navigation.navigate('SignIn')}>
<SignUpText>{i18n.t('signup.singIn')}</SignUpText>
</TouchableText>
</TextContainer>
</Container>
</>
</TouchableWithoutFeedback>
);
}
My Form with sets the loading state:
export function Form({ setShowModal, setIsLoading }: any) {
const {
control,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
async function handleUserRegister(data: FormData) {
setIsLoading(true);
const incomingData = await registerWithEmailAndPassword(data);
if (incomingData) {
setIsLoading(false);
setShowModal(true);
}
setIsLoading(false);
}
useEffect(() => {
ToastShowManagement(i18n.t('signup.error'), errors);
}, [errors]);
return (
<Container>
<ControlledInput
name="username"
control={control}
icon="at-sign"
placeholder={i18n.t('signup.username')}
error={errors.username}
/>
<ControlledInput
name="name"
control={control}
icon="user"
placeholder={i18n.t('signup.name')}
error={errors.name}
/>
<ControlledInput
control={control}
name="email"
icon="mail"
placeholder={i18n.t('signup.email')}
keyboardType="email-address"
autoCapitalize="none"
error={errors.email}
/>
<ControlledInput
control={control}
name="password"
icon="lock"
placeholder={i18n.t('signup.password')}
secureTextEntry
error={errors.password}
/>
<ControlledInput
control={control}
name="passwordConfirmation"
icon="lock"
placeholder={i18n.t('signup.confirmPassword')}
secureTextEntry
error={errors.passwordConfirmation}
/>
<PrimaryButton
text={i18n.t('signup.button')}
onPress={handleSubmit(handleUserRegister as any)}
style={{ marginTop: 24 }}
/>
</Container>
);
}
The way to apply useEffect is incorrect
useEffect(() => {
if (isLoading) return <Loading />;
}, [isLoading]);
Does not return the element to main thread, the app.js.
You should take away the useEffect hook
export function Register({ navigation }: any) {
const [showModal, setShowModal] = useState(false);
const [isLoading, setIsLoading] = useState(false);
if (isLoading) return <Loading />
return <>...</>;
}

Cannot make element disappear/appear properly inside of my React App

So I have built a movie search app.
On the 4th page we have the ability to search for a specific movie or TV show.
Now I have built a logic that will display "Movies(Tv Shows) not found" when there are no search results.
Here is the code of the entire "Search" Component:
const Search = () => {
const [type, setType] = useState(0);
const [page, setPage] = useState(1);
const [searchText, setSearchText] = useState("");
const [content, setContent] = useState([]);
const [numOfPages, setNumOfPages] = useState();
const [noSearchResults, setNoSearchResults] = useState(false);
const fetchSearch = async () => {
try {
const { data } = await axios.get(`https://api.themoviedb.org/3/search/${type ? "tv" : "movie"}?api_key=${process.env.REACT_APP_API_KEY}&language=en-US&query=${searchText}&page=${page}&include_adult=false`);
setContent(data.results);
setNumOfPages(data.total_pages);
} catch (error) {
console.error(error);
}
};
const buttonClick = () => {
fetchSearch().then(() => {
if (searchText && content.length < 1) {
setNoSearchResults(true);
} else {
setNoSearchResults(false);
}
});
};
useEffect(() => {
window.scroll(0, 0);
fetchSearch();
// eslint-disable-next-line
}, [page, type]);
return (
<div>
<div style={{ display: "flex", margin: "25px 0" }}>
<TextField className="textBox" label="Search" variant="outlined" style={{ flex: 1 }} color="secondary" onChange={e => setSearchText(e.target.value)} />
<Button variant="contained" style={{ marginLeft: "10px" }} size="large" onClick={buttonClick}>
<SearchIcon color="secondary" fontSize="large" />
</Button>
</div>
<Tabs
value={type}
indicatorColor="secondary"
onChange={(event, newValue) => {
setPage(1);
setType(newValue);
}}
style={{
marginBottom: "20px",
}}
>
<Tab style={{ width: "50%" }} label="Search Movies" />
<Tab style={{ width: "50%" }} label="Search TV Shows" />
</Tabs>
<div className="trending">
{content && content.map(c => <SingleContent key={c.id} id={c.id} poster={c.poster_path} title={c.title || c.name} date={c.first_air_date || c.release_date} media_type={type ? "tv" : "movie"} vote_average={c.vote_average} />)}
{noSearchResults && (type ? <h2>Tv Shows not found</h2> : <h2>Movies not found</h2>)}
</div>
{numOfPages > 1 && <CustomPagination setpage={setPage} numOfPages={numOfPages} />}
</div>
);
};
You can see this in action here.
The problem that happens is that even when I have something in my search results, it still shows the Movies(Tv Shows) not found message.
And then if you click the search button again it will disappear.
A similar thing happens when there are no search results.
Then the Movies(Tv Shows) not found message will not appear the first time, only when you press search again.
I don't understand what is going on. I have used .then after my async function and still it does not execute in that order.
Try adding noSearchResults to your useEffect hook. That hook is what tells React when to re-render, and right now it's essentially not listening to noSearchResult whenever it changes because it's not included in the array.

How to get my Autocomplete input field to reset DefaultValue after submit?

How can I get my <TextField> inside Autocomplete to reset it's default value after form submit?
Currently, the state of formValues during submit remains as the default value?
I've tried to fix the onSubmit function of my form to clear the state of the values, but not able to do so.
How can I clear the value after a user submits?
const { control, handleSubmit } = useForm();
const [formValues, SetFormValues] = useState()
const onSubmit = (data, e) =>
{
console.log(data);
axiosInstance
.patch(URL + slug + '/', {
stock_list: data.stock_list.map(list=>list.symbol),
})
.then((res) =>
{
getFinData(dispatch)(slug);
SetFormValues([''])
console.log(formValues)
});
};
console.log(formValues)
return (
<Container component="main" maxWidth="md">
<div className={classes.container}>
<Grid>
<form noValidate onSubmit = { handleSubmit(onSubmit) }>
<Controller
render={({ onChange ,...props }) => (
<Autocomplete
{...props}
className={classes.inputBar}
id="stock_list"
key={formValues}
name="stock_list"
multiple
options={options}
ListboxComponent={ListboxComponent}
renderGroup={renderGroup}
filterOptions={filterOptions}
filterSelectedOptions
// onChange={(e) => onChange(e.target.value)}
onChange={(e, data) => { onChange(data); SetFormValues(data) }}
getOptionLabel={(option) => option.symbol}
getOptionSelected={(option, value) => option.symbol === value.symbol}
renderOption={(option) =>
{
return (
<>
<span style={{ fontWeight: 500, fontSize: "20px", paddingRight: "1rem" }}>{option.symbol}</span><span style={{ color: "#C6C6C6", fontSize: "24px" }}> | </span><span style={{ paddingLeft: "1rem" }}>{option.company}</span>
</>
)
}}
renderInput={(params) => (
<Zoom in={tabSwitch === 0}>
<TextField
{...params}
style={{ alignItems: 'center' }}
id="stock_list"
name="stock_list"
variant="outlined"
label="Companies"
className={classes.inputBar}
defaultValue={formValues}
/>
</Zoom>
)}
/>
)}
name="stock_list"
control={control}
defaultValue={formValues}
// onChange={([, data]) => data}
/>
{formValues && formValues.length > 0 &&
<Button
variant="contained"
color="primary"
type="submit"
style={{display:"flex",alignItems: 'center',justifyContent:"center"}}
>
Add Stocks
</Button>
}
</form>
</Grid>
</div>
</Container>
);
})
UPDATE:
I have tried implementing your codes but still no success in removing the data from original state?
const [formValues, SetFormValues] = useState([])
const onSubmit = (data, e) =>
{
console.log(data);
axiosInstance
.patch(URL + slug + '/', {
stock_list: data.stock_list.map(list=>list.symbol),
})
.then((res) =>
{
getFinData(dispatch)(slug);
SetFormValues([]);
});
};
return (
<Controller
render={({ onChange ,...props }) => (
<Autocomplete
{...props}
className={classes.inputBar}
id="stock_list"
key={formValues}
name="stock_list"
multiple
options={options}
ListboxComponent={ListboxComponent}
renderGroup={renderGroup}
filterOptions={filterOptions}
filterSelectedOptions
onChange={(e, data) => { onChange(data); SetFormValues(data) }}
getOptionLabel={(option) => option.symbol}
getOptionSelected={(option, value) => option.symbol === value.symbol}
renderOption={(option) =>
{
return (
<>
<span style={{ fontWeight: 500, fontSize: "20px", paddingRight: "1rem" }}>{option.symbol}</span><span style={{ color: "#C6C6C6", fontSize: "24px" }}> | </span><span style={{ paddingLeft: "1rem" }}>{option.company}</span>
</>
)
}}
renderInput={(params) => (
<Zoom in={tabSwitch === 0}>
<TextField
{...params}
style={{ alignItems: 'center' }}
id="stock_list"
name="stock_list"
variant="outlined"
label="Companies"
className={classes.inputBar}
defaultValue={formValues}
value={formValues}
/>
</Zoom>
)}
/>
)}
name="stock_list"
control={control}
defaultValue={[]}
/>
It might be worth noting that my Mui AutoComplete and textfield is wrapped around by React Hook Form controller.
There is error in your code.
When using state, specify the data type to be stored.
E.g
if you are storing array of data, your state should be const [formValues, SetFormValues] = useState([]); not const [formValues, SetFormValues] = useState();
if you are storing string of data, your state should be const [formValues, SetFormValues] = useState(""); not const [formValues, SetFormValues] = useState();
if you are storing integer of data, your state should be const [formValues, SetFormValues] = useState(0); not const [formValues, SetFormValues] = useState();
To clear the state of each of these data
For Array
SetFormValues([]);
For String
SetFormValues("");
For Int
SetFormValues(0);
So correct your code and ty again. It will work for you.
put this code in the then callback of axios SetFormValues([]);
Because defaultValue is default value..
Try looks like this; (add value)
<TextField
style={{ alignItems: 'center' }}
id="stock_list"
name="stock_list"
variant="outlined"
label="Companies"
defaultValue={formValues}
value={formValues}
/>
https://material-ui.com/api/input/#main-content
defaultValue: The default input element value. Use when the component is not controlled.
value: The value of the input element, required for a controlled component.
You can do it this way
const [formValues, SetFormValues] = useState(null)
Put this code in the then callback of axios SetFormValues(null);
OR
const [formValues, SetFormValues] = useState([])
Put this code in the then callback of axios SetFormValues(null);
If any of those two above does not work for you, try storing the values as string in state
E.g const [formValues, SetFormValues] = useState("")
and clear items using SetFormValues(null) or SetFormValues("");

UseEffect() keeps giving error for Redirection

If login is successful, a token is returned and is stored in the local storage. In this case, I should redirect to a private route /panel. If login is unsuccessful, I should be able to show the error message from ShowError(). The error message functionality was working properly before I added the redirection.
This is my LoginPage.tsx
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
return (
<ThemeProvider theme={colortheme}>
<Typography color='primary'>
Login Unsuccessful
</Typography>
</ThemeProvider>)
}
}
function FormSubmitted(){
setSubmitted(true);
console.log('Form submitted');
}
function RedirectionToPanel(){
if(submitted && localStorage.getItem('token')){
return <Redirect to='/panel'/>
}
}
// useEffect(() => {
// if(localStorage.getItem('token')){
// return <Redirect to='/panel'/>
// }
// },[] );
function submitForm(LoginMutation: any) {
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);FormSubmitted();RedirectionToPanel()}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
<br></br>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2">
Forgot password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
)
}}
</Formik>
</div>
{submitted && <Redirect to='/panel'/>}
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
Currently, if the login is unsuccessful, it automatically redirects to page /404. However, I want it to direct to /404 only if an invalid/private link is entered. In case of unsuccessful login, I want to show the error message.
I also tried to use UseEffect(), which is commented out in the code above but it keeps giving me this error on the arrow:
Argument of type '() => JSX.Element | undefined' is not assignable to parameter of type 'EffectCallback'.
Type 'Element | undefined' is not assignable to type 'void | (() => void | undefined)'.
Type 'Element' is not assignable to type 'void | (() => void | undefined)'.
Type 'Element' is not assignable to type '() => void | undefined'.
Type 'Element' provides no match for the signature '(): void | undefined'.ts(2345)
here is how my private routing is implemented in the App.tsx
const token = localStorage.getItem('token');
const PrivateRoute = ({component, isAuthenticated, ...rest}: any) => {
const routeComponent = (props: any) => (
isAuthenticated
? React.createElement(component, props)
: <Redirect to={{pathname: '/404'}}/>
);
return <Route {...rest} render={routeComponent}/>;
};
If I comment out the {submitted && <Redirect to='/panel'/>}, I get the error message on unsuccessful login but then there's no redirection even when the login is successful.
Edited Code:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [submitted, setSubmitted] = useState(false);
const [shouldRedirect, setShouldRedirect] = useState(false);
function ShowError(){
if (!localStorage.getItem('token'))
{
console.log('Login Not Successful');
return (
<ThemeProvider theme={colortheme}>
<Typography color='primary'>
Login Unsuccessful
</Typography>
</ThemeProvider>)
}
}
// function FormSubmitted(){
// setSubmitted(true);
// console.log('Form submitted');
// }
function RedirectionToPanel(){
console.log('check');
if(submitted && localStorage.getItem('token')){
console.log('Finall');
return <Redirect to='/panel'/>
}
}
useEffect(() => {
if(localStorage.getItem('token')){
setShouldRedirect(true);
}
},[] );
function submitForm(LoginMutation: any) {
setSubmitted(true);
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
})
.catch(console.log)
}
}
return (
<Mutation mutation={LoginMutation}>
{(LoginMutation: any) => (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}>
<Avatar>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in
</Typography>
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
validationSchema={schema}
>
{props => {
const {
values: { email, password },
errors,
touched,
handleChange,
isValid,
setFieldTouched
} = props;
const change = (name: string, e: any) => {
e.persist();
handleChange(e);
setFieldTouched(name, true, false);
setState( prevState => ({ ...prevState, [name]: e.target.value }));
};
return (
<form style={{ width: '100%' }}
onSubmit={e => {e.preventDefault();
submitForm(LoginMutation);RedirectionToPanel()}}>
<TextField
variant="outlined"
margin="normal"
id="email"
fullWidth
name="email"
helperText={touched.email ? errors.email : ""}
error={touched.email && Boolean(errors.email)}
label="Email"
value={email}
onChange={change.bind(null, "email")}
/>
<TextField
variant="outlined"
margin="normal"
fullWidth
id="password"
name="password"
helperText={touched.password ? errors.password : ""}
error={touched.password && Boolean(errors.password)}
label="Password"
type="password"
value={password}
onChange={change.bind(null, "password")}
/>
{submitted && ShowError()}
<FormControlLabel
control={<Checkbox value="remember" color="primary" />}
label="Remember me"
/>
<br />
<Button className='button-center'
type="submit"
disabled={!isValid || !email || !password}
// onClick={handleOpen}
style={{
background: '#6c74cc',
borderRadius: 3,
border: 0,
color: 'white',
height: 48,
padding: '0 30px'
}}
>
Submit</Button>
</form>
)
}}
</Formik>
</div>
{/* {submitted && <Redirect to='/panel'/>} */}
</Container>
)
}
</Mutation>
);
}
export default LoginPage;
I believe that returning Redirect component shouldn't be called in useEffect hook or inside any function.
You should have something like that:
function LoginPage (){
const [state, setState] = useState({
email: '',
password: '',
});
const [shouldRedirect, setShouldRedirect] = useState(false);
useEffect(() => {
if(localStorage.getItem("token")) {
setShouldRedirect(true);
}
}, []);
function submitForm(LoginMutation: any) {
setSubmitted(true);
const { email, password } = state;
if(email && password){
LoginMutation({
variables: {
email: email,
password: password,
},
}).then(({ data }: any) => {
localStorage.setItem('token', data.loginEmail.accessToken);
setShouldRedirect(true);
})
.catch(console.log)
}
}
if(shouldRedirect) return <Redirect to="/panel" />;
return (
... rest of code
);
}

Testing a login component with Jest

I'm not great at testing, and new to Jest and Enzyme. I have a Login component that consists of two TextInput components for username and password and a Button component. I am testing each component individually.
I would just like to test that a username and password was returned by onLogin.
Here is the component:
export const onLogin = (user, password) => {
console.log('User', user, 'Password', password)
return [user, password];
};
function Login() {
const [user, setUser] = useState("");
const [password, setPassword] = useState("");
return (
<LoginWrapper>
<Branding brand={brand.brandName} />
<FormWrapper onSubmit={(e) => { e.preventDefault(); onLogin(user, password) }}>
<Stack>
<TextInput
className="username"
type="text"
label="Username"
onChange={e => setUser(e.target.value)}
/>
</Stack>
<Stack>
<TextInput
className="password"
type="password"
label="Password"
onChange={e => {setPassword(e.target.value); console.log('user', user)}}
/>
</Stack>
<Stack padding="0" align="right">
<Button type="submit">Login</Button>
</Stack>
</FormWrapper>
</LoginWrapper>
);
}
export default Login;
My test:
describe("<Login />", () => {
it("renders text input correctly", () => {
const tree = renderer.create(<ThemeProvider theme={themes.default}><Login /></ThemeProvider>).toJSON();
expect(tree).toMatchSnapshot();
});
it("calls onLogin when button clicked", () => {
const onSubmitMock = jest.fn();
const component = Enzyme.mount(
<ThemeProvider theme={themes.default}><Login onSubmit={onSubmitMock} /></ThemeProvider>
);
component.find("input.username").simulate('change', { target: { value: 'myUser' } })
component.find("input.password").simulate('change', { target: { value: 'myPassword' } })
component.find("form").simulate("submit");
console.log("onClickMock.mock", onSubmitMock.mock)
expect(onSubmitMock).toBeCalled()
});
});
Results:
Expected mock function to have been called, but it was not called.
Your testing approach is correct except for:
In your test, you are mocking a callback function and passing it as a property onSubmit to your component. Then you need to call this function from your component when you submit the form.
Instead, you are calling the onLogin function on your component that does not have any
repercussion.
In order to fix this, declare the properties on your component function as a parameter, and call props.onSubmit on your form submit.
function Login(props) {
const [user, setUser] = useState("");
const [password, setPassword] = useState("");
return (
<LoginWrapper>
<Branding brand={brand.brandName} />
<FormWrapper onSubmit={(e) => { e.preventDefault(); props.onSubmit(user, password) }}>
<Stack>
<TextInput
className="username"
type="text"
label="Username"
onChange={(e) => setUser(e.target.value)}
/>
</Stack>
<Stack>
<TextInput
className="password"
type="password"
label="Password"
onChange={(e) => { setPassword(e.target.value) }}
/>
</Stack>
<Stack padding="0" align="right">
<Button type="submit">Login</Button>
</Stack>
</FormWrapper>
</LoginWrapper>
);
}

Categories