react.js how to display multiple error messages - javascript

I just got started to react so please bear with me. I don't know exactly what I am doing, I'm just picking those things as I go so I'll do my best to walk you through my mental process when building this.
My intentions are to create a registration component, where the backend returns the validation errors in case there are any in form of an object which has following structure.
{
"username": [
"A user with that username already exists."
],
"email": [
"A user is already registered with this e-mail address."
]
}
The state manager that I chose to be using is redux, so this comes back every time when the register function is dispatched.
Since it has this structure I wrote a function to help me decompose it and pick up only on the actual errors (the strings).
const walkNestedObject = (obj, fn) => {
const values = Object.values(obj)
values.forEach(val =>
val && typeof val === "object" ? walkNestedObject(val, fn) : fn(val))
}
now I want to display them in the view, so I wrote another function which is supposed to do that
const writeError = (value) => {
return <Alert message={value} type="error" showIcon />
}
Down in the actual component I am calling it as this:
{(props.error) ? walkNestedObject(props.error, writeError) : null}
To my surprise if I console.log the value above return in writeError it works flawlessly, every single error gets printed, but none of them gets rendered.
To debug this I've tried multiple variations and none of them seemed to work, I even called the writeError function in the component as
{writeError('test')}
and it worked for some reason.
At this stage I'm just assuming there's some react knowledge required to fulfil this task that Im just now aware of.
EDIT:
A mock example can be found over here
Also, I've tried using the first two answers and when mapping through the errors I get this
Unhandled Rejection (TypeError): props.error.map is not a function
with other variations, it mentions the promise from so I'd include how I manage the API request
export const authSignup = (username, email, password1, password2) => dispatch => {
dispatch(authStart());
axios.post('http://127.0.0.1:8000/rest-auth/registration/', {
username: username,
email: email,
password1: password1,
password2: password2
})
.then(res => {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
})
.catch(err => {
dispatch(authFail(err.response.data))
})
}

Consider changing the topology of your error messages:
"errors": [
{ "type": "username", "message": "Username already in use." },
{ "type": "email", "message": "Email address already in use."}
]
That makes your implementation a bit easier:
// MyLogin.jsx
import React from 'react'
const MyLogin = () => {
/**
* Here we're using state hooks, since it's much simpler than using Redux.
* Since we don't need this data to be made globally available in our
* application, it doesn't make sense to use Redux anyway.
*/
const [errors, setErrors] = React.useState([])
const handleLogin = (event) => {
event.preventDefault()
axios.post('/api/login', formData).then(() => successAction(), (error: any) => {
setErrors(error) // Update our local state with the server errors
})
}
return (
<>
{errors.length && ( // Conditionally render our errors
errors.map((error) => (
<Alert type={error.type} message={error.message} />
)
)}
<form onSubmit={handleLogin}>
<input type='text' name='email' />
<input type='text' name='username' />
<input type='password' name='password' />
</form>
<>
)
}
export default MyLogin

Your walkNestedFunction function checks each layer of an object, and if a given layer of the object is an object itself, it then uses that object to run your function - which in this case is writeError. writeError returns an error <Alert /> as soon as an error arises. But when you stick writeError inside the circular logic of walkNestedFunction, it will hit the first return statement, render it to the page, and then stop rendering. I think this is why you're getting the complete list of errors logged to the console. Your walkNestedFunction continues cycling down through object layers until its done. But in React, only the first return statement will actually render.
A better tactic would be to modify your writeError function to record the erors to a state variable. Then you can render this state variable. Every time the state is updated, the component will rerender with the updated state.
// Define state in your component to contain an array of errors:
state = {
errors: []
}
// Add an error into state with each iteration of writeError
const writeError = (error) => {
this.setState({
errors: [
...this.state.errors,
error
]
})
}
// inside render(), render the state variable containing your errors
<div>
{ this.state.errors.map(error => <p>error</p>) }
</div>
`

Related

Async custom validator approves the field validation requirement while the promise is still resolving, how can I change the behaviour? Vuelidate

The field is email, and it's the last field the user has to complete in order to send an invitation to an external user to join the platform.
the validation checks if the email is already being used, basically ⇒ if the email is already registered ⇒ throw error. This maintains the submit button disabled.
The issue I'm having here is that until the promise resolves (for this punctual validation), which is about 1/2sec the field turns ok validated and the submit button becomes enable.
If the user is quick (and you know, there's connection speed and other factors in the equation...) he can send an invitation and cause trouble/ terrific user excperience.
how can i make it work backwards => field not approved until promise finished?
Here's the code:
<template>
<Input
id="invite-email"
name="invite-email"
class="w-full"
:errors="v$.email.$errors"
v-model="v$.email.$model"
:invitePlayer="true"
>
{{ t('placeholder.email') }}
</Input>
</template>
<script lang="ts">
import { useVuelidate } from '#vuelidate/core'
import { required, email, minLength, helpers } from '#vuelidate/validators'
import { checkEmail } from '#/utils/validators'
const v$ = useVuelidate(rules, form)
const invite = async (): Promise<void> => {
loading.value = true
try {
const invitedUserResult: Guest = await store.dispatch('inviteUser', {
...Object.fromEntries(
Object.entries(form).filter(([_, v]) => v !== '')
),
fromUserID: store.state.user._id
})
console.log('invitedUserResult', invitedUserResult)
invitedUser.value = invitedUserResult
invitationLink.value = invitedUserResult.shortLink
showInviteSuccess.value = true
} catch (err) {
console.log(err)
}
loading.value = false
}
</script>
I tried to keep only the relevant code, tell me if you need more, thanks a lot!
I think you should use loading value to keep field disabled when promise hasn't been resolved yet. You can pass it as a prop to the component.
I also recommed to wrap loading.value = false with finally to be sure it's always executed, even if there is an error thrown.
try {...}
catch (err) {
console.log(err)
} finally {
loading.value = false
}

Rendered more hooks than during the previous render after apollo useQuery

I'm tying to find a way to correct the Hook rendering error. I have a total of 3 useQuery hooks being rendered :
const {
data: OSData,
error: OSError,
loading: OSLoading,
} = useQuery(OSData, {
variables: {
NUMBER: UniqueList,
},
})
const {
data: RamData,
error: RamERROR,
loading: RamLOADING,
} = useQuery(GET_Ram)
const {
data: Hardware,
error: HardwareERROR,
loading: HardwareLOADING,
} = useQuery(GET_Hardware)
The variable 'NUMBER' is based on a list 'UniqueList' that is made from the GET_Ram and GET_Hardware queries so the OSData query needs to be called later or there's an undefined variable. However, calling the OSData Query later in the code gives me a render error.
Any idea on how I could accomplish this?
Thank you!
an answer I found is using lazy query.
const SomeData [{
called, loading, data
}] = useLazyQuery(OSData)
})
if (called && loading) return <p>Loading ...</p>
if (HardwareLOADING || RamLOADING) return <p> loading</p>
if (HardwareERROR || RamERROR) return <p>error</p>
//perform all the needed calculations for the variable here
and in the return statement you can call the query and provide the variable. Here I use a button.
<div>
<button onClick={() => SomeData({ variables: { NUMBER: uniqueList } })}>
Load{' '}
</button>
</div>
Hope this helps someone

Button not changing state in react js based on api call

I have this page which shows a single post and I have a like button. if the post is liked, when the user clicks the button, it changes its state to unlike button, but if the post is not liked, then the like is getting registered and the id is getting pushed on to the array, but the button state is not getting updated and I have to reload the page to see the page. Can someone tell me how to resolve this issue?
This is the code:
const [liked, setLiked] = useState(false)
const [data, setData] = useState([]);
function likePosts(post, user) {
post.likes.push({ id: user });
setData(post);
axiosInstance.post('api/posts/' + post.slug + '/like/');
window.location.reload()
}
function unlikePosts(post, user) {
console.log('unliked the post');
data.likes = data.likes.filter(x => x.id !== user);
setData(data);
return (
axiosInstance.delete('api/posts/' + post.slug + '/like/')
)
}
For the button:
{data.likes && data.likes.find(x => x.id === user) ?
(<FavoriteRoundedIcon style={{ color: "red" }}
onClick={() => {
unlikePosts(data, user)
setLiked(() => liked === false)
}
}
/>)
: (<FavoriteBorderRoundedIcon
onClick={() => {
likePosts(data, user)
setLiked(() => liked === true)
}
}
/>)
}
Thanks and please do ask if more details are needed.
As #iz_ pointed out in the comments, your main problem is that you are directly mutating state rather than calling a setState function.
I'm renaming data to post for clarity since you have said that this is an object representing the data for one post.
const [post, setPost] = useState(initialPost);
You don't need to use liked as a state because we can already access this information from the post data by seeing if our user is in the post.likes array or not. This allows us to have a "single source of truth" and we only need to make updates in one place.
const isLiked = post.likes.some((like) => like.id === user.id);
I'm confused about the likes array. It seems like an array of objects which are just {id: number}, in which case you should just have an array of ids of the users who liked the post. But maybe there are other properties in the object (like a username or timestamp).
When designing a component for something complex like a blog post, you want to break out little pieces that you can use in other places of your app. We can define a LikeButton that shows our heart. This is a "presentation" component that doesn't handle any logic. All it needs to know is whether the post isLiked and what to do onClick.
export const LikeButton = ({ isLiked, onClick }) => {
const Icon = isLiked ? FavoriteRoundedIcon: FavoriteBorderRoundedIcon;
return (
<Icon
style={{ color: isLiked ? "red" : "gray" }}
onClick={onClick}
/>
);
};
A lot of our logic regarding liking and unliking could potentially be broken out into some sort of usePostLike hook, but I haven't fully optimized this because I don't know what your API is doing and how we should respond to the response that we get.
When a user clicks the like button we want the changes to be reflected in the UI immediately, so we call setPost and add or remove the current user from the likes array. We have to set the state with a new object, so we copy all of the post properties that are not changing with the spread operator ...post and then override the likes property with an edited version. filter() and concat() are both safe array functions which return a new copy of the array.
We also need to call the API to post the changes. You are using the same url in both the "like" and "unlike" scenarios, so instead of calling axios.post and axios.delete, we can call the generalized function axios.request and pass the method name 'post' or 'delete' as an argument to the config object. [axios docs] We could probably combine our two setPost calls in a similar way and change likePost() and unlikePost() into one toggleLikePost() function. But for now, here's what I've got:
export const Post = ({ initialPost, user }) => {
const [post, setPost] = useState(initialPost);
const isLiked = post.likes.some((like) => like.id === user.id);
function likePost() {
console.log("liked the post");
// immediately update local state to reflect changes
setPost({
...post,
likes: post.likes.concat({ id: user.id })
});
// push changes to API
apiUpdateLike("post");
}
function unlikePost() {
console.log("unliked the post");
// immediately update local state to reflect changes
setPost({
...post,
likes: post.likes.filter((like) => like.id !== user.id)
});
// push changes to API
apiUpdateLike("delete");
}
// generalize like and unlike actions by passing method name 'post' or 'delete'
async function apiUpdateLike(method) {
try {
// send request to API
await axiosInstance.request("api/posts/" + post.slug + "/like/", { method });
// handle API response somehow, but not with window.location.reload()
} catch (e) {
console.log(e);
}
}
function onClickLike() {
if (isLiked) {
unlikePost();
} else {
likePost();
}
}
return (
<div>
<h2>{post.title}</h2>
<div>{post.likes.length} Likes</div>
<LikeButton onClick={onClickLike} isLiked={isLiked} />
</div>
);
};
CodeSandbox Link

Jest: Mock a _HOC_ or _curried_ function

Given the following function:
./http.js
const http = {
refetch() {
return (component) => component;
}
}
I would like to mock the function in a test as follows:
./__tests__/someTest.js
import { refetch } from './http';
jest.mock('./http', () => {
return {
refetch: jest.fn();
}
}
refetch.mockImplementation((component) => {
// doing some stuff
})
But I'm receiving the error
TypeError: _http.refetch.mockImplementation is not a function
How can I mock the refetch function in the given example?
update:
When I modify the mock function slightly to:
jest.mock(
'../http',
() => ({ refetch: jest.fn() }),
);
I get a different error:
TypeError: (0 , _http.refetch)(...) is not a function
My guess it's something with the syntax where the curried function (or HOC function) is not mapped properly. But I don't know how to solve it.
Some of the real code I'm trying to test.
Note: The example is a bit sloppy. It works in the application. The example given is to give an idea of the workings.
./SettingsContainer
// ...some code
return (
<FormComponent
settingsFetch={settingsFetch}
settingsPutResponse={settingsPutResponse}
/>
);
}
const ConnectedSettingsContainer = refetch(
({
match: { params: { someId } },
}) => ({
settingsFetch: {
url: 'https://some-url.com/api/v1/f',
},
settingsPut: (data) => ({
settingsPutResponse: {
url: 'https://some-url.com/api/v1/p',
}
}),
}),
)(SettingsContainer);
export default ConnectedSettingsContainer;
Then in my component I am getting the settingsPutResponse via the props which react-refetch does.
I want to test if the user can re-submit a form after the server has responded once or twice with a 500 until a 204 is given back.
./FormComponent
// ...code
const FormComp = ({ settingsResponse }) => {
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsResponse && settingsResponse.fulfilled) {
setSuccess(true);
}
}, [settingsResponse]);
if (success) {
// state of the form wil be reset
}
return (
<form>
<label htmlFor"username">
<input type="text" id="username" />
<button type="submit">Save</button>
</form>
)
};
The first question to ask yourself about mocking is "do I really need to mock this?" The most straightforward solution here is to test "component" directly instead of trying to fake out an http HOC wrapper around it.
I generally avoid trying to unit test things related to I/O. Those things are best handled with functional or integration tests. You can accomplish that by making sure that, given same props, component always renders the same output. Then, it becomes trivial to unit test component with no mocks required.
Then use functional and/or integration tests to ensure that the actual http I/O happens correctly
To more directly answer you question though, jest.fn is not a component, but React is expecting one. If you want the mock to work, you must give it a real component.
Your sample code here doesn't make sense because every part of your example is fake code. Which real code are you trying to test? I've seen gigantic test files that never actually exercize any real code - they were just testing an elaborate system of mocks. Be careful not to fall into that trap.

Render React component using Firestore data

I'm trying to render my Guild component with data from Firestore. I put the data from Firestore into my state as an array, then when I call the component and try to render it, nothing shows. I want to believe I'm doing something very wrong here (haven't been working with React for very long), but I'm not getting any errors or warnings, so I'm not sure exactly what's happening.
Guilds.js
<Col>
<Card>
<CardBody>
<CardTitle className={this.props.guildFaction}>{this.props.guildName}</CardTitle>
<CardSubtitle>{this.props.guildServer}</CardSubtitle>
<CardText>{this.props.guildDesc}</CardText>
</CardBody>
</Card>
</Col>
Render function
renderCards() {
var guildComp = this.state.guilds.map(guild => {
console.log(guild)
return <Guilds
key={guild.id}
guildFaction={guild.guildFaction}
guildServer={guild.guildServer}
guildName={guild.guildName}
guildDesc={guild.guildDesc} />
})
return <CardDeck>{guildComp}</CardDeck>
}
Fetching Firestore Data
guildInfo() {
Fire.firestore().collection('guilds')
.get().then(snapshot => {
snapshot.forEach(doc => {
this.setState({
guilds: [{
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
}]
})
console.log(doc.data().guildName)
})
})
}
UPDATE: solved, fix is in the render function.
Well, you using state "guilds" but you update state "posts" or I miss something?
I see few things here:
your component is Guild.js, but you are rendering <Guilds />
You are setting state to posts, but using this.state.guilds to render the components
You are overriding that piece of state each time to the last object in the snapshot, with the way you are mapping the Firestore data
you are setting the ids in the list wrong using doc.id instead of doc.data().id
You aren't mapping guilds to render. guilds is an array of guild objects, so you should do something like guilds.map(guild => { return <Guild /> }
These are few things to fix, and then try to console.log(this.state.guilds) before rendering and see if you get the right data
I think your issue is that because setState is async, by the time it actually sets the state doc is no longer defined. Try creating the array first, then call setState outside of the loop ie:
guildInfo() {
Fire.firestore().collection('guilds')
.get().then(snapshot => {
let guilds = []
snapshot.forEach(doc => {
guilds.push({
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
});
})
this.setState({guilds});
})
}
Try to use a map function, and in the callback function of the setState, try to console log your state after the update:
guildInfo() {
Fire.firestore().collection('guilds')
.get()
.then(snapshot => {
const guilds = snapshot.map(doc => {
return {
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
};
this.setState({guilds}, () => console.log(this.state))
})
})
})
}
If in the console log there's a little [i] symbol near your state, it means that the state is not ready, and therefore it's am async issue. Replacing the forEach with the map function may already help though.

Categories