Assigning apollo mutations with a ternary in an onClick? - reactjs

I am new to typescript and migrating a react frontend to learn. I am currently having trouble with my login/registration component - a ternary operator is assigned to an onClick handler which dictates whether the login or signup mutation are executed. I implemented a workaround where I wrapped the mutations in functions, but it looks dirty.
Here is a barebones example, which returns a type assignment error because the values in the ternary do not share attributes with the onClick handler. I can provide a more robust, reproduceable example if necessary.
const Auth = () => {
const history = useHistory();
const [formState, setFormState] = useState({
login: true,
firstName: '',
lastName: '',
username: '',
password: '',
email: ''
});
const [loginMutation] = useMutation(LOGIN_MUTATION, {
onCompleted: (response) => {
//...
}
});
const [signupMutation] = useMutation(SIGNUP_MUTATION, {
variables: {
//...
},
onCompleted: (response) => {
// ...
}
});
return (
<>
{!formState.login && (
display extra inputs for registration form
)}
username and password fields
<button onClick={formState.login ? login : signup}>
{formstate.login ? login: signup}
</button>
</>
)
}

As you correctly guessed onClick has signature (e:MouseEvent<HTMLButtonElement, MouseEvent>) => void while mutation has a different signature (something along the lines of (options?: MutationFunctionOptions<T>) => Promise<Result>). So, when you say, onClick={login} you are essentially doing this: onClick={e => login(e)} which is wrong.
One way to fix it is to write an inline function
<button onClick={() => formState.login ? login() : signup()}>
{formstate.login ? login: signup}
</button>

Related

In React app, converting class components to functional components, how to re-work this setState instance?

In a React app, I have a class component that acts as a form, and each different element of the form calls the same reusable function to set state in order to save data to state until the form is finished, then a callback to pass the data around elsewhere, and when that form is finished, it writes the data in state to a MongoDB. In calling the function, I pass a parameter into it that determines which property of state is set.
I'm trying to convert this component to a functional component, but can't come up with as elegant of a way to handle this using useState.
The function:
handleChange = (e, t, l) => {
if (l) {
var lang = this.state[l]
lang[t] = e.target.value
this.setState({[l] : lang}, () => { this.setData() })
} else {
this.setState({[t]:e.target.value}, () => { this.setData() })
}
}
And the implementation:
onChange={(e) => this.handleChange(e, 'eng')}
onChange={(e) => this.handleChange(e, 'span')}
onChange={(e) => this.handleChange(e, 'type')}
Those occur on multiple inputs and there are a few other places, those are just 3 to give example.
I'd guess the best way to handle this is to use a switch, and each case is setEng, setSpan, setType, but I wanted to see if there were any other ways to replicate this that people know of? This seems like a bit of a shortcoming of useState that setState handled really nicely.
Edit:
As requested, state as it was in class component:
this.state = {
id: this.props.count,
collapsed: false,
type: '',
answer: null,
english: {
title: '',
options: [],
},
spanish: {
title: '',
options: [],
}
}
And in functional component:
const [id, setId] = useState(props.count)
const [collapsed, setCollapsed] = useState(false)
const [type, setType] = useState('')
const [answer, setAnswer] = useState(null)
const [english, setEnglish] = useState({ title: '', options: [] })
const [spanish, setSpanish] = useState({ title: '', options: [] })

Where to place dispatch function for useReducer and why?

I'm new to React and I'm currently learning about useReducer.
I've created a simple login feature that verifies if the user inputted email includes '#' and the password length is greater than 5.
If these two conditions are met, I want my program to display an alert with success or fail message when pressing on the submit button.
What I'm curious about is that the application displays "Success" on submit when I add dispatch({type: 'isCredValid')} in useEffect(commented out in the code below), but the application displays "fail" when I add the dispatch({type: 'isCredValid'}) in the onSubmit handler without using useEffect. I was expecting the application to display "Success" when adding the dispatch({type: 'isCredValid')} in the onSubmit handler without the help of useEffect. Why is it not displaying "Success"? And why does my application display "Success" when the dispatch function is in the useEffect?
Reducer function :
const credReducer = (state, action) => {
switch(action.type) {
case 'email' :
return {...state, email: action.value, isEmailValid: action.value.includes('#')};
case 'password' :
return {...state, password: action.value, isPasswordValid: action.value.length > 5 ? true : false};
case 'isCredValid' :
return {...state, isCredValid: state.isEmailValid && state.isPasswordValid ? true : false};
default :
return state;
}
}
Component and input handlers
const Login = () => {
const [credentials, dispatch] = useReducer(credReducer, {
email: '',
password: '',
isEmailValid: false,
isPasswordValid: false,
isCredValid: false
})
// useEffect(() => {
// dispatch({type: 'isCredValid'})
// }, [credentials.isEmailValid, credentials.isPasswordValid])
const handleSubmit = (e) => {
e.preventDefault()
dispatch({ type: "isCredValid" })
if (credentials.isCredValid === true) {
alert ("Success!")
} else {
alert ('failed')
}
}
const handleEmail = (e) => {
dispatch({ type: "email", value: e.target.value })
}
const handlePassword = (e) => {
dispatch({ type: "password", value: e.target.value })
}
return (
<Card className={classes.card}>
<h1> Login </h1>
<form onSubmit={handleSubmit}>
<label>Email</label>
<input type="text" value={credentials.email} onChange={handleEmail}/>
<label>Password</label>
<input type="text" value={credentials.password} onChange={handlePassword}/>
<button type="submit"> Submit </button>
</form>
</Card>
)
}
if (credentials.isCredValid === true) {
alert ("Success!")
} else {
alert ('failed')
}
You are probably referring to above alert that you didn't immediately see "Success". That doesn't happen like that, just like with updating state, when you dispatch something, you will see the update on the next render.
This useEffect may work, but you're kind of abusing the dependency array here. You're not actually depending on credentials.isEmailValid or credentials.isPasswordValid. You should use these dependencies to decide which action to dispatch, and maybe that's your plan already.
The reason your handleSubmit doesn't seem to work, is what others point out. You won't be able to see the result until next render, so not inside the handleSubmit function.
// useEffect(() => {
// dispatch({type: 'isCredValid'})
// }, [credentials.isEmailValid, credentials.isPasswordValid])
const handleSubmit = (e) => {
e.preventDefault()
dispatch({ type: "isCredValid" })
if (credentials.isCredValid === true) {
alert ("Success!")
} else {
alert ('failed')
}
}
To see the results, add another useEffect and trigger the alert from there:
useEffect(() => {
if(credentials.isCredValid){
alert('Success!')
}
}, [credentials])

How do I add some state to redux state when try to write unit test in jest/RTL?

I wanted to start testing with Redux-toolkit, according to the article I found.
https://redux.js.org/usage/writing-tests#setting-up
The right practice is to write integration test. But right now I want to test a sign out button which is controlled by authstate,in order to set it's value I have to sign in first. What I want to do is I can give some state to the authstate in the test file instead of having to login. So I can actually write unit test on my sign out button.
Here's the code and the test
const Navbar = () => {
const cart = useAppSelector((state) => state.cart);
const user = useAppSelector((state) => state.auth);
const dispatch = useAppDispatch();
const handleLogout = () => {
localStorage.removeItem("persist:root");
window.location.reload();
};
return(
{user.user !== null ? (
<>
<MenuItem>Hello {user.user?.username} </MenuItem>{" "}
<ExitToApp
style={{ cursor: "pointer", marginLeft: "10px" }}
onClick={() => handleLogout()}
/>
<Link to="/order">
<MenuItem>Order</MenuItem>
</Link>
</>
) : (
<>
<Link to="/register">
<MenuItem>REGISTER</MenuItem>
</Link>
<Link to="/login">
<MenuItem>SIGN IN</MenuItem>
</Link>
</>
)}
)
Authslice
const slice = createSlice({
name: "auth",
initialState: { user: null, token: null } as {
user: null | UserDataInterface;
token: null | string;
},
reducers: {
setCredentials: (state,{ payload: { user, token } }: PayloadAction<{ user: UserDataInterface; token: string }>) => {
state.user = user;
state.token = token;
}
},
extraReducers: (builder) => {}
});
test file
test("When click on logoutk,it will trigger handle logout", () => {
//TODO: should let the user state to not be empty first
await store.dispatch(setCredentials())
//TODO: then we can track if logout label exist
//TODO: click on logout button and mock localstorage maybe ?
});
What should I do with this kind of unit test, if it involves prerequisites for redux-state ?
After some research, I found out how to do this. It might not be the best practice. But I think it could be useful in a lot of scenario if you don't want to write integration test.
test("When click on logout,it will trigger handle logout", async () => {
//TODO: should let the user state to not be empty first
store.dispatch(
setCredentials({
user: {
username: "Kai",
_id: "efekfjefke",
email: "dfdkdfkdf@gmail.com",
createdAt: new Date(2013, 13, 1),
updatedAt: new Date(2013, 13, 1),
img: "223232",
},
token: "test12345",
})
);
//TODO: then we can track if logout label exist
await waitFor(() =>
expect(screen.queryByRole("userTitle")).toBeInTheDocument()
);
await waitFor(() =>
expect(screen.queryByTestId("logout")).toBeInTheDocument()
);
//TODO: click on logout button and mock localstorage maybe ?
const userLogout = screen.getByTestId("logout");
fireEvent.click(userLogout);
// should not be able to query the userTitle since it's logout
await waitFor(() =>
expect(screen.queryByRole("userTitle")).not.toBeInTheDocument()
);
});
I found out you can directly dispatch state through the store. which is really convenient.
Cause for me. What's tricky is I don't know how to write integration test across two components or pages
Right now if I can directly dispatch some state first. I can unit-test the function in single page or components to see if it does what I needed.
I am pretty new to testing, so if there's any better approach at this, please let me know!
Hope this can help someone who is struggling!!

How to display comments coming from Redux store on individual component

I have created a basic single page app, on initial page there is some dummy data and on click of each item I direct user to individual details page of that item. I wanted to implement comment and delete comment functionality which I successfully did but now when I comment or delete the comment it doesn't only happen at that individual page but in every other page too. Please see the sandbox example for better clarify.
https://codesandbox.io/s/objective-feistel-g62g0?file=/src/components/ProductDetails.js
So once you add some comments in individual page, go back and then click to another products, apparently you will see that the comments you've done in other pages are also available there. What do you think causing this problem ?
The same state being reused by all the different pages.
Try to load dynamically load reducers for each page/router differently to use distinct state values.
You can start from here
Redux modules and code splitting
I found my own logical solution. You probably might find a better solution but this works pretty well too. I thought of passing another property in the object with the params I get from url and then filter the comments by their url params. So that I could do filtering based on the url parameters and display the comments only made on that specific page.
So ProductDetails.js page should be looking like this:
import React, { useState, useEffect } from 'react';
import { Input, Button } from 'semantic-ui-react'
import { connect } from 'react-redux';
const ProductDetails = (props) => {
const [commentObject, setCommentObject] = useState({
text: "",
date: "",
id: ""
});
const clickHandler = () => {
if (!commentObject.text.trim()) {
return
}
props.addNewComment(commentObject)
setCommentObject({
...commentObject,
text: ""
})
console.log(commentObject.id);
}
useEffect(() => {
}, []);
return (
<div>
{props.posts ? props.posts.text : null}
{props.comments.filter(comment => {
return comment.postId === props.match.params.slug
}).map(({ text, id }) => {
return (<div key={id}>
<p>{text}</p>
<Button onClick={() => props.deleteComment(id)} >Delete comment</Button></div>)
})}
<Input value={commentObject.text}
onChange={comment => setCommentObject({ text: comment.target.value, date: new Date(), id: Date.now(), postId: props.match.params.slug })}
/>
<Button onClick={clickHandler} >Add comment</Button>
</div>
);
}
const mapStateToProps = (state, ownProps) => {
let slug = ownProps.match.params.slug;
return {
...state,
posts: state.posts.find(post => post.slug === slug),
}
}
const mapDispatchToProps = (dispatch) => {
return {
addNewComment: (object) => { dispatch({ type: "ADD_COMMENT", payload: { comment: { text: object.text, date: object.date, id: object.id, postId: object.postId } } }) },
deleteComment: (id) => { dispatch({ type: "DELETE_COMMENT", id: id }) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductDetails);

Is there way to improve a functional component using hooks?

So I decided to give React Hooks a try.
I have a component which contains a bunch of logics like form validation and form submission as shown below:
const MyAwesomeComponent: React.FC = ()=> {
const [firstName, setFirstName]=useState('')
const [lastName, setLastName]=useState('')
const [formValidation, setFormValidation] = useState({
firstName: true,
lastName: true
})
const handleSubmitForm = (evt: React.FormEvent)=> {
evt.preventDefault();
handleSubmitButton()
}
const handleSubmitButton = ()=> {
let formBody = {}
if(!firstName) {
setFormValidation({...formValidation, firstName: false})
} else {
formBody = {...formBody, {firstName}}
}
if(!lastName) {
setFormValidation({...formValidation, lastName: false})
} else {
formBody = {...formBody, {lastName}}
}
// Nice proceed and submit the form to the backend
}
return (
<form onSubmit={(evt)=>handleSubmitForm(evt)}>
{/* form inputs go here */}
<button onClick={()=>handleSubmitButton()}></button>
</form>
)
}
export default MyAwesomeComponent
The code above feels a bit bloated and a bit difficult to maintain in my opinion. Is there a way to improve the handleSubmitButton function in order to abstract some of its code into a separate function out of the MyAwesomeComponent component?
For one thing, you could do
const handleSubmitButton = ()=> {
let formBody = {firstName, lastName}
setFormValidation({firstName: !!firstName, lastName: !!lastName});
// Nice proceed and submit the form to the backend
}
In other words, why do it in two separate steps? BTW if you haven't seen it, the !! is "not not" which converts a truthy or falsy value into an actual true or false.

Resources