Testing a login component with Jest - javascript

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>
);
}

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 <>...</>;
}

Uncaught Error: Actions must be plain objects. Use custom middleware

The error seems common and when I made some research I saw that it's mostly due to omission of middlewere (like thunk) or failing to call the dispatch function. Even after trying to put those things in check I keep getting the error
Redux Action
export const signup = (username, email, password, re_password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ username, email, password, re_password });
try {
const {data} = await axios.post(`${process.env.REACT_APP_API_URL}/api/users/`, body, config);
console.log(body)
dispatch({
type: SIGNUP_SUCCESS,
payload: data
});
} catch (error) {
dispatch({
type: SIGNUP_FAIL,
payload: error.response && error.response.data.detail
? error.response.data.detail
: error.message,
})
}
};
function RegisterScreen({ signup, isAuthenticated }) {
const [accountCreated, setAccountCreated] = useState(false);
const [username, setUsername] = useState([])
const [email, setEmail] = useState([])
const [password, setPassword] = useState([])
const [re_password, setRe_password] = useState([])
const [message, setMessage] = useState('')
const dispatch = useDispatch()
const auth = useSelector(state => state.auth)
const { error, loading } = auth
const submitHandler = (e) => {
e.preventDefault();
if (password !== re_password) {
setMessage('Both passwords must be the same')
} else {
dispatch(signup(username, email, password, re_password));
setAccountCreated(true);
}
}
return (
<Container className='content auth-container'>
<div className="auth-header text-center mb-4">
<h2 className="auth-header">Sign Up</h2>
<p>Add your deatils to sign up</p>
</div>
{message && <Message variant='danger'>{message}</Message>}
{error && <Message variant='danger'>{error}</Message>}
{loading && <Loader />}
<Form className="auth-form" onSubmit={submitHandler}>
<Form.Group className="mb-3" controlId='name'>
<Form.Control
className="auth-input search-ppty"
required
minLength='6'
type="name"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId='email'>
<Form.Control
required
className="auth-input search-ppty"
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="password">
<Form.Control
className="auth-input search-ppty"
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="passwordConfirm">
<Form.Control
className="auth-input search-ppty"
type="password"
placeholder="Confirm Password"
value={re_password}
onChange={(e) => setRe_password(e.target.value)}
/>
</Form.Group>
<Button type="submit" className="auth-button">Sign Up</Button>
</Form>
<Row className="p-2">
<Col>
<div className=""> Already have an account? <Link to="/login">Login</Link></div>
</Col>
<Col>
</Col>
</Row>
</Container>
)
}
export default connect(null, {signup}) (RegisterScreen)
Redux store
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
const middleware = [thunk]
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(...middleware))
)
export default store
I have spent several hours still can't find where the error is coming from in the code. How do I fix it?
You seem to be mixing two different APIs for Redux: the hooks API and the connect API.
When using function components, you don't need connect at all, hooks are enough, so try to remove the last line with connect (export RegisterScreen directly instead), and remove the signup prop (use the imported signup action creator directly).

Syntax error: Unexpected token by using firebase

I am now following this youtube video to develop an Instagram clone app by using React with firebase.
The error occurred at the point below, on the video at 2:36:29.
It says "Syntax error: Unexpected token (93:12)"
{user?.displayName ? (
<ImageUpload username={user.displayName} />
) : (
<h3>sorry you need to login to upload</h3>
)}
what I tried
・Just put <ImageUpload username={user.displayName} />
=> I got 「firebase is not defined」 error on the other js file.
・put "user.displayName" instead of "user?.displayName" (? is erased)
=> more complicated error occur that "TypeError: Cannot read properties of null (reading 'displayName')"
・Put {user ? (<ImageUpload username={user.displayName} />) : (<h3>Sorry you need to login !!</h3>)} eraced displayName
=> only visual works(surely, the username is not given in this case)
Adobe three tries, I guess displayName is the main point to solve the issue...
If you are good at React.js or you have seen the same error, please give me advice.
Whole App.js codes↓
App.js
import React, { useState, useEffect } from 'react'
import './App.css';
import Post from './Post';
import { db, auth } from './firebase';
import Modal from '#mui/material/Modal';
import Box from '#mui/material/Box';
import Button from '#mui/material/Button';
import Input from '#mui/material/Input';
//import Typography from '#mui/material/Typography';
import ImageUpload from './ImageUpload';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
function App() {
const [posts, setPosts] = useState([]);
const [open, setOpen] = React.useState(false);
const [openSignIn, setOpenSignIn] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((authUser) => {
if (authUser) {
//User has logged IN
console.log(authUser);
setUser(authUser);
//↑ is keep user logged in after refresh
} else {
//User has logged OUT
setUser(null);
}
})
return () => {
//perform some cleanup actions
unsubscribe(null);
}
}, [user, username]);
//useEffect => where the code runs, last []) means the code runs only once
useEffect(() => {
db.collection('posts').onSnapshot(snapshot => {
setPosts(snapshot.docs.map(doc => ({
id: doc.id,
post: doc.data()
})));
})
}, []);
//signup func
const signup = (event) => {
event.preventDefault();
auth
.createUserWithEmailAndPassword(email, password)
.then((authUser) => {
return authUser.user.updateProfile({
displayName: username
})
})
.catch((error) => alert(error.message));
setOpen(false);
}
//signin func
const signin = (event) => {
event.preventDefault();
auth
.signInWithEmailAndPassword(email, password)
.catch((error) => alert(error.message));
setOpenSignIn(false);
}
return (
<div className="app">
{user.displayName ? (
<ImageUpload username={user.displayName} />
) : (
<h3>sorry you need to login to upload</h3>
)}
<div className="app__header">
<img
className="app__headerImage"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo.png/735145cfe0a4.png"
alt="HeaderImage"
/>
{user ? (
<Button onClick={() => auth.signOut()}>Logout</Button>
) : (
<div className="app__loginContainer">
<Button onClick={() => setOpenSignIn(true)}>SignIn</Button>
<Button onClick={() => setOpen(true)}>SignUp</Button>
</div>
)}
</div>
<Modal
open={open}
onClose={() => setOpen(false)}
>
<Box sx={style}>
<form className="app__signup">
<center>
<img
className="app__headerImage"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo.png/735145cfe0a4.png"
alt="HeaderImage"
/>
</center>
<Input
type="text"
placeholder="username"
//at first, ↓ was value={username}, but typing cannot be shown on the screen in that case,
//https://stackoverflow.com/questions/34006333/cant-type-in-react-input-text-field
//↑is the trouble shooting, which says try defaultValue instead of value(V is capital).
defaultValue={username}
onChange={(e) => setUsername(e.target.value)}
/>
<Input
type="text"
placeholder="email"
defaultValue={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input
type="text"
placeholder="password"
defaultValue={password}
onChange={(e) => setPassword(e.target.value)}
/>
<center>
<Button type="submit" onClick={signup}>SignUp</Button>
</center>
</form>
</Box>
</Modal>
<Modal
//signupModal
open={openSignIn}
onClose={() => setOpenSignIn(false)}
>
<Box sx={style}>
<form className="app__signup">
<center>
<img
className="app__headerImage"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo.png/735145cfe0a4.png"
alt="HeaderImage"
/>
</center>
<Input
type="text"
placeholder="email"
defaultValue={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input
type="text"
placeholder="password"
defaultValue={password}
onChange={(e) => setPassword(e.target.value)}
/>
<center>
<Button type="submit" onClick={signin}>SignIn</Button>
</center>
</form>
</Box>
</Modal>
<h1>TEST</h1>
{/* header */}
{
posts.map(({ id, post }) => (
<Post username={post.username} caption={post.caption} imageUrl={post.imageUrl} />
))
}
{/* post */}
{/* post */}
</div >
);
}
export default App;
Here↓ is ImageUpload.js which I got firebase is not defined when I erase the? mark from ImageUpload tag,,,,
firebase is not defined from this line↓
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
ImageUpload.js
import React, { useState } from 'react';
import Button from '#mui/material/Button';
import { storage, db } from "./firebase";
import firebase from 'firebase/app';
function ImageUpload(username) {
const [image, setImage] = useState(null);
const [url, setUrl] = useState("");
const [progress, setProgress] = useState(0);
const [caption, setCaption] = useState('');
const handleChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0])
}
};
const handleUpload = () => {
const uploadTask = storage.ref('images/${image.name}').put(image);
uploadTask.on(
"state_change",
(snapshot) => {
//progress function
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(progress);
},
(error) => {
//error function
console.log(error);
alert(error.message);
},
() => {
//complete func
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then(url => {
//post image inside DB
db.collection("posts").add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
caption: caption,
imageUrl: url,
username: username
});
setProgress(0);
setCaption("");
setImage(null);
})
}
)
}
return (
<div>
{/* I want to have .... */}
{/* caption input */}
{/* file picker */}
{/* Post button */}
<progress value={progress} max="100" />
<input type="text" placeholder="enter a caption.." onChange={event => setCaption(event.target.value)} />
<input type="file" onChange={handleChange} />
<Button onClick={handleUpload}>
upload
</Button>
</div >
)
}
export default ImageUpload
To be safe here ↓ is firebase.js(privacy info are shown as ***)
firebase.js
import firebase from "firebase";
const firebaseApp = firebase.initializeApp({
apiKey: "***",
authDomain: "***.firebaseapp.com",
projectId: "***",
storageBucket: "***.appspot.com",
messagingSenderId: "*****",
appId: "****",
measurementId: "****"
})
const db = firebase.firestore();
const auth = firebase.auth();
const storage = firebase.storage();
export { db, auth, storage };
And index.js
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Moreover, firebase version is 8.10.0, using MacBook air intel one OS BigSur

Objects are not valid as a React child (found: object with keys {username})

I'm starting to learn React and following the tutorial and got stuck with this error when I was trying to upload picture. When I press upload button, this error "Objects are not valid as a React child (found: object with keys {username}). If you meant to render a collection of children, use an array instead." shown up and I couldn't reload the page anymore.
Render Error
Here are the codes:
1.App.js
import React, { useEffect, useState } from "react";
import './App.css';
import Post from './Post';
import { auth, db } from "./firebase";
import Modal from '#material-ui/core/Modal';
import { makeStyles } from '#material-ui/core/styles';
import { Button, Input } from "#material-ui/core";
import ImageUpload from './ImageUpload';
function getModalStyle() {
const top = 50;
const left = 50;
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`,
};
}
const useStyles = makeStyles((theme) => ({
paper: {
position: 'absolute',
width: 400,
backgroundColor: theme.palette.background.paper,
border: '2px solid #000',
boxShadow: theme.shadows[5],
padding: theme.spacing(2, 4, 3),
},
}));
function App() {
const classes = useStyles();
const [modalStyle] = React.useState(getModalStyle);
const [posts, setPosts] = useState([]);
const [open, setOpen] = useState(false);
const [openSignIn, setOpenSignIn] = useState('');
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [user, setUser] = useState(null);
//UseEffect -> Run a piece of code based on a specific condition
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((authUser) => {
if (authUser) {
//user has logged in...
console.log(authUser);
setUser(authUser);
} else {
//user has logged out...
setUser(null);
}
return () => {
//perform some cleanup action
unsubscribe();
}
})
}, [user, username]);
useEffect(() => {
//this is where the code runs
db.collection('posts').onSnapshot(snapshot => {
//everytime a new post is added, this code fires...
setPosts(snapshot.docs.map(doc => ({
id: doc.id,
post: doc.data()
})));
})
}, []);
const signUp = (event) => {
event.preventDefault();
auth
.createUserWithEmailAndPassword(email, password)
.then((authUser) => {
return authUser.user.updateProfile({
displayName: username
})
})
.catch((error) => alert(error.message))
}
const signIn = (event) => {
event.preventDefault();
auth
.signInWithEmailAndPassword(email, password)
.catch((error) => alert(error.message))
setOpenSignIn(false);
}
return (
<div className="app">
{user?.displayName ? (
<ImageUpload username={user.displayName} />
) : (
<h3>Sorry you need to login to upload</h3>
)}
<Modal
open={open}
onClose={() => setOpen(false)}
>
<div style={modalStyle} className={classes.paper}>
<form className="app__signup">
<center>
<img
className="app__headerImage"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo.png/735145cfe0a4.png"
alt=""
/>
</center>
<Input
placeholder="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<Input
placeholder="email"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input
placeholder="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Button type="submit" onClick={signUp}>Sign Up</Button>
</form>
</div>
</Modal>
<Modal
open={openSignIn}
onClose={() => setOpenSignIn(false)}
>
<div style={modalStyle} className={classes.paper}>
<form className="app__signup">
<center>
<img
className="app__headerImage"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo.png/735145cfe0a4.png"
alt=""
/>
</center>
<Input
placeholder="email"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input
placeholder="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<Button type="submit" onClick={signIn}>Sign In</Button>
</form>
</div>
</Modal>
<div className="app__header">
<img
className="app__headerImage"
src="https://www.instagram.com/static/images/web/mobile_nav_type_logo.png/735145cfe0a4.png"
alt="" />
</div>
{user ? (
<Button onClick={() => auth.signOut()}>Logout</Button>
) : (
<div className="app__loginContainer">
{/* : is stand for OR */}
<Button onClick={() => setOpenSignIn(true)}>Sign In</Button>
<Button onClick={() => setOpen(true)}>Sign Up</Button>
</div>
)}
<h1>Hello Joes! Let's build an Instagram CLone with React</h1>
{
posts.map(({ id, post }) => (
<Post key={id} username={post.username} caption={post.caption} imgUrl={post.imgUrl} />
))
}
</div>
);
}
export default App;
Here is the ImageUpload file
2. ImageUpload.js
import { Button } from '#material-ui/core'
import React, { useState } from 'react';
import { storage, db } from './firebase';
import firebase from "firebase";
function ImageUpload(username) {
const [image, setImage] = useState(null);
const [progress, setProgress] = useState(0);
const [caption, setCaption] = useState('');
const handleChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
const handleUpload = () => {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
"state_change",
(snapshot) => {
//progress funtion...
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(progress);
},
(error) => {
//Error function...
console.log(error);
alert(error.message);
},
() => {
// Complete function...
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then(url => {
// Post image on db
db.collection("posts").add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
caption: caption,
imageUrl: url,
username: username
});
setProgress(0);
setCaption("");
setImage(null);
});
}
);
};
return (
<div>
{/* I want to have... */}
{/* Caption input */}
{/* File picker */}
{/* Post button */}
<progress value={progress} max="100" />
<input type="text" placeholder="Enter a caption..." onChange={event => setCaption(event.target.value)} value={caption} />
<input type="file" onChange={handleChange} />
<Button onClick={handleUpload}>
Upload
</Button>
</div>
)
}
export default ImageUpload
Thanks guys!
This line is wrong:
<progress value={progress} max="100" />
React components have to be upper case.
Your progress is a number, not a React component
You probably wanted to import the component Progress and write:
<Progress value={progress} max="100" />

React async timing issue, cause modal to pop up with prefilled form after state change?

I have a setCurrentProject call using useState, useEffect hooks api but the modal pops up before the state has been set and it causes the data to throw an error or be blank if i throw in a conditional to check if it's there.
How do I make sure the state changes before the modal opens with the correct prop data
const EditProjectModal = ({modalOpen, handleClose, addTask, currentProject}) => {
const [name, setName] = useState("");
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const [projectTasks, setProjectTasks] = useState([]);
useEffect(() => {
setName(currentProject.name)
setProjectTasks(currentProject.tasks)
setStartDate(currentProject.startDate)
setEndDate(currentProject.endDate)
}, [currentProject])
return(
<Modal
centered={false}
open={modalOpen}
onClose={handleClose}
>
<Modal.Header>Edit Project</Modal.Header>
<Modal.Content>
<Form onSubmit={handleSubmit}>
<Form.Group widths="equal">
<Form.Field
control={Input}
label="Project"
placeholder="Enter the name of your project..."
required
value={name}
onChange={handleName}
/>
</Form.Group>
<Form.Group widths='equal'>
<Form.Field required>
<label>Start Date</label>
<DatePicker
selected={startDate}
onChange={date => setStartDate(date)}
selectsStart
startDate={startDate}
endDate={endDate}
/>
</Form.Field>
<Form.Field required>
<label>End Date</label>
<DatePicker
selected={endDate}
onChange={date => setEndDate(date)}
selectsEnd
startDate={startDate}
endDate={endDate}
minDate={startDate}
placeholderText="Select end date.."
/>
</Form.Field>
</Form.Group>
<ProjectTasksContainer projectTasks={projectTasks} setProjectTasks={setProjectTasks}/>
<Divider hidden/>
<Button type='submit'>Submit</Button>
</Form>
</Modal.Content>
</Modal>
I've left out some of the change functions etc because that's not important to the state change.
It's being triggered by a button from another component
const handleEdit = () => {
setCurrentProject(project)
handleEditProjClick();
}
edited to add dashboard that uses the editProjectModal
const Dashboard = ({notes, setNotes, tasks, setTasks, goals, setGoals, projects, setProjects}) => {
//============== STATE VARIABLES ===================//
const [projModalOpen, setProjModalOpen] = useState(false);
const [editProjModalOpen, setEditProjModalOpen] = useState(false);
const [currentProject, setCurrentProject] = useState({});
//============== MODALS ===================//
const handleProjModalOpen = () => {
setProjModalOpen(true);
}
const handleProjModalClose = () => {
setProjModalOpen(false);
}
const handleEditProjModalOpen = () => {
setEditProjModalOpen(true);
}
const handleEditProjModalClose = () => {
setEditProjModalOpen(false);
}
//============== RENDERING FUNCTION ===================//
return (
<Fragment>
<Grid columns={2}>
<Grid.Row stretched>
<Grid.Column width={12}>
<Container style={{width: "90%"}}>
<Segment basic>
<Segment textAlign="right">
<ProjectButton handleClick={handleProjModalOpen}/>
<ProjectModal handleClose={handleProjModalClose} modalOpen={projModalOpen} addTask={addTask} addProject={addProject} />
<EditProjectModal handleClose={handleEditProjModalClose} modalOpen={editProjModalOpen} addTask={addTask} currentProject={currentProject} />
</Segment>
</Container>
</Grid.Column>
<Grid.Column width={4}>
<Segment>
<ProgressContainer projects={projects} handleProjClick={handleProjModalOpen} handleEditProjClick={handleEditProjModalOpen} setCurrentProject={setCurrentProject}/>
</Segment>
</Grid.Column>
</Grid.Row>
</Grid>
</Fragment>
)
}
the parent holds the setCurrentProject state and passes it down to that child and also passes down the handleEditProjClick();
What's the best way to handle this issue?
Cheers!
Because editProjModalOpen and currentProject are independent you could get a situation where editProjModalOpen is true and currentProject is {} - you'd need to account for that, e.g. {!isEmpty(currentProject) && <EditProjectModal ... /.>}
This would then guarantee currentProject is set in EditProjectModal and you could just set the initial values in state off the currentProject:
const [name, setName] = useState(currentProject.name);
const [startDate, setStartDate] = useState(currentProject.startDate);
Or if you have to useEffect then just check the state before rendering the modal:
if (!name ) {
return null
}

Categories