useCallback function is not a function while using unit test Jest - javascript

This is my test code snippet but it throws an exception, TypeError: componentInstance.loadLoanApplication is not a function :
it('should render the SubmittedLoan', () => {
const loanData = {
data: {
id: 1,
};
const div = document.createElement('div');
const wrapper = mount(
<AppProviders>
<MemoryRouter initialEntries={['/review/153']}>
<SubmittedLoan
match={{ params: { loanId: 1, step: 1 } }}
history={{
location: { state: { from: 'register' } },
push() {},
}}
/>
</MemoryRouter>
</AppProviders>,
div,
);
const componentInstance = wrapper
.find(SubmittedLoan)
.children()
.first()
.children()
.first()
.instance();
const loanApplication = {
id: 1,
steps_data: [
{ slug: 'step_1', title: 'Step 1' },
{ slug: 'step_2', title: 'Step 2' },
],
status: ApiCaptiq.STATUS_SUBMITTED,
};
expect(wrapper.find(SubmittedLoan).length).toBe(1);
componentInstance.loadLoanApplication(1, 1);
componentInstance.onLoadLoanApplication(loanData);
componentInstance.onLoadFail();
componentInstance.setState({
formData: [{ item: 'value' }, { item2: 'value2' }],
activeStep: 1,
loanApplication,
});
componentInstance.handleSnackbarClose(new Event('click'), '');
componentInstance.setState({ activeStep: 3 });
});
Then my Component which uses memo is as follows :
export const SubmittedLoan = memo(() => {
const [loanApplication, setLoanApplication] = useState<LoanApplication | null>(null);
const [message, setMessage] = useState({
message: '',
open: false,
messageType: '',
});
const authContext = useContext(AuthContext);
const customerContext = useCustomerData();
const params = useParams();
const history = useHistory();
const classes = useStyles();
const { loanId } = params;
const onLoadFail = useCallback(() => {
setMessage({
message: 'Die verfügbaren Darlehensarten können nicht aufgelistet werden',
open: true,
messageType: 'error',
});
}, []);
const onLoadLoanApplication = useCallback(
(response: AxiosResponse) => {
setTemplateSettings(response, authContext);
if (
response.data.status === ApiCaptiq.STATUS_STARTING ||
response.data.status === ApiCaptiq.STATUS_IN_PROGRESS ||
response.data.status === ApiCaptiq.STATUS_PRE_WAITING
) {
history.push(`/view/${loanId}`);
} else {
setLoanApplication(response.data);
}
},
[loanId, authContext, history],
);
const loadLoanApplication = useCallback(
async (loan_id: number) => {
try {
const response = await request.get(`${ApiCaptiq.LOAN_APPLICATION_URL}${loan_id}/`);
const { fetchCustomerProfile } = customerContext;
await fetchCustomerProfile(response.data.customer_profile_id);
onLoadLoanApplication(response);
} catch (err) {
onLoadFail();
}
},
[customerContext, onLoadLoanApplication, onLoadFail],
);
...
What could be the possible reason for this

The functions you are defining inside the component, are not just available on the component instance. In fact, there is not way to call them. You can test only by mocking the fetch calls they are doing.
If you really need callable functions in your component (you should try to avoid these..), you could use this: https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Perhaps better would be to extract this data loading logic elsewhere and test it separately.

Related

Graphql doesn't return the data

I'm trying to find a specific data based on the id in graphql.
But it is returning null .
I have also tried the mutation. Here is also it is returning null.
What's wrong with this below code.
const { ApolloServer } = require("#apollo/server");
const { startStandaloneServer } = require("#apollo/server/standalone");
const students = [
{
name: "langesh",
roll: 131,
},
{
name: "ram",
roll: 134,
},
];
const typeDefs = `#graphql
type Student {
name: String,
roll: Int,
}
type Query {
students: [Student]
student(roll: Int) : Student
}
`;
const resolvers = {
Query: {
students: () => students,
student: (parent, roll) => {
return students.find((s) => s.roll === roll);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
async function startServer() {
const { url } = await startStandaloneServer(server, {
listen: {
port: 8000,
},
});
console.log(`url : ${url}`);
}
startServer();
You need to destructure the args in your resolver.
Instead of:
student: (parent, roll) => {
return students.find((s) => s.roll === roll);
}
do:
student: (parent, { roll }) => {
return students.find((s) => s.roll === roll);
}

tried every solution why this error occurs ? body: CastError: Cast to string failed for value mongoose

tried every solution why this error occurs ? can not understand at all
i was trying to remove cloudinary upload section from website .
it means users can post in my blog without uploading image .
image was necessary
so i tried to change source code but this error not going away at any cost
this.$__.validationError = new ValidationError(this);
[0] ^
[0]
[0] ValidationError: Post validation failed: body: Cast to string failed for value "{
[0] _immutable: {
[0] allowUndo: true,
[0] currentContent: {
[0] entityMap: {},
[0] blockMap: [Object],
[0] selectionBefore: [Object],
[0] selectionAfter: [Object]
[0] },
[0] decorator: { _decorators: [Array] },
[0] directionMap: { idih: 'LTR' },
[0] forceSelection: false,
[0] inCompositionMode: false,
[0] inlineStyleOverride: null,
[0] lastChangeType: 'insert-characters',
[0] nativelyRenderedContent: null,
[0] redoStack: [],
[0] selection: {
[0] anchorKey: 'idih',
[0] anchorOffset: 5,
[0] focusKey: 'idih',
[0] focusOffset: 5,
[0] isBackward: false,
[0] hasFocus: false
[0] },
[0] treeMap: { idih: [Array] },
[0] undoStack: [ [Object] ]
[0] }
[0] }" (type Object) at path "body", image.publicId: Path `image.publicId` is required., image.url: Path `image.url` is required.
this is my postmodel.js that i remove required:true . but error is same
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema(
{
title: { type: String, required: true },
image: { url: { type: String, required: true }, publicId: { type: String, required: true } },
body: { type: String, required: true },
likes: [{ type: mongoose.Types.ObjectId, ref: 'User' }],
unicorns: [{ type: mongoose.Types.ObjectId, ref: 'User' }],
bookmarks: [{ type: mongoose.Types.ObjectId, ref: 'User' }],
tags: [{ type: mongoose.Types.ObjectId, required: true, ref: 'Tag' }],
comments: [{ type: mongoose.Types.ObjectId, required: true, ref: 'Comment' }],
author: { type: mongoose.Types.ObjectId, required: true, ref: 'User' },
},
{
timestamps: true,
}
);
module.exports = mongoose.model('Post', PostSchema);
and this is my post controller that i was trying to change and remove the upload part from website but i could not after 3 days .
postcontroller .js
const Post = require('../model/Post');
const User = require('../model/User');
const Tag = require('../model/Tag');
const Comment = require('../model/Comment');
const cloudinary = require('../config/cloudinary');
const { uploadToCloudinary } = require('../utils/cloudinary');
const { getPostParams, unCapitalizeFirstLetter } = require('../helpers/string');
const { createTags, updateTags, deleteTags } = require('./tagsController');
const {
likeNotification,
removeLikeNotification,
postNotification,
removePostNotification,
} = require('./notificationsController');
const createPost = async (req, res) => {
const { title, file, body, tags, authorUsername } = req.body;
const { url, public_id: publicId } = await uploadToCloudinary(file, 'Posts');
const author = await User.findOne({ username: authorUsername }).exec();
const formattedTags = tags
.trim()
.split(',')
.map(w => w.trim().replace(/ /g, '-'));
const createdPost = await Post.create({
title,
image: { url, publicId },
body,
author: author._id,
});
author.followers.map(followerId => {
(async () => {
await postNotification(author._id, createdPost._id, followerId);
})();
});
await createTags(formattedTags, createdPost);
author.posts.push(createdPost._id);
await author.save();
res.status(200).json(createdPost.toObject({ getters: true }));
};
const getPost = async (req, res) => {
const author = await User.findOne({ username: req.params.username }).exec();
const authorId = await author?.toObject({ getters: true }).id;
const { postTitle, postId } = getPostParams(req.params.postUrl);
const foundPost = await Post.findOne({
author: authorId,
title: postTitle,
_id: postId,
})
.populate('author')
.populate('comments')
.populate('tags')
.exec();
res.status(200).json(foundPost.toObject({ getters: true }));
};
const getPosts = async (req, res) => {
const { userId } = req.params;
const posts = await Post.find(userId ? { bookmarks: userId } : {})
.sort({ createdAt: -1 })
.populate('author')
.populate('tags');
if (!posts) res.status(204).json('No posts found');
res.status(200).json(posts.map(post => post.toObject({ getters: true })));
};
const updatePost = async (req, res) => {
const authorId = await User.findOne({ username: req.params.username }).exec();
const { postTitle, postId } = getPostParams(req.params.postUrl);
const { url, public_id: publicId } = await uploadToCloudinary(req.body.image.url, 'Posts');
await cloudinary.uploader.destroy(req.body.image.publicId);
req.body.image = { url, publicId };
const formattedTags = req.body.tags
.trim()
.split(',')
.map(w => w.trim().replace(/ /g, '-'));
const post = await Post.findOne({
author: authorId,
title: postTitle,
_id: postId,
})
.populate('author')
.populate('tags');
Object.keys(req.body).map(key => {
if (key !== 'tags') post[key] = req.body[key];
});
await updateTags(formattedTags, post);
await post.save();
res.status(200).json(post.toObject({ getters: true }));
};
const deletePostsByUserId = async user => {
const { _id: userId } = user;
user.comments.forEach(commentId => {
(async () => {
await Post.updateMany({ comments: commentId }, { $pull: { comments: commentId } });
})();
});
const posts = await Post.find({ author: userId }).populate('tags');
['likes', 'unicorns', 'bookmarks'].forEach(k => {
(async () => {
await Post.updateMany({ [k]: userId }, { $pull: { [k]: userId } });
})();
});
posts.forEach(post => {
(async () => {
await deleteTags(
post.tags.map(({ name }) => name),
post,
true
);
await cloudinary.uploader.destroy(post.image.publicId);
await Post.deleteOne({ _id: post._id });
})();
});
await Comment.deleteMany({ author: userId });
};
const deletePost = async (req, res) => {
const author = await User.findOne({ username: req.params.username }).exec();
const { postTitle, postId } = getPostParams(req.params.postUrl);
await cloudinary.uploader.destroy(req.body.publicId);
const foundPost = await Post.findOne({
author: author._id,
title: postTitle,
_id: postId,
})
.populate('tags')
.exec();
if (!foundPost) return res.sendStatus(204);
const comments = await Comment.find({ parentPost: postId }).populate({
path: 'author',
populate: 'followers',
});
comments.forEach(({ author }) =>
(async () => {
author.comments.forEach(comment => author.comments.pull(comment));
})()
);
author.posts.pull(postId);
await author.save();
await Comment.deleteMany({ parentPost: postId });
await deleteTags(
foundPost.tags.map(({ name }) => name),
foundPost,
true
);
removePostNotification(author._id, foundPost._id, author.followers);
await Post.deleteOne({ _id: foundPost._id });
res.status(200).json(foundPost.toObject({ getters: true }));
};
const postReaction = async (req, res) => {
const { userId } = req.body;
const { action, postUrl } = req.params;
const { postTitle, postId } = getPostParams(postUrl);
const isUndoing = action.includes('remove');
const actionKey = isUndoing
? unCapitalizeFirstLetter(action.replace('remove', '')) + 's'
: action + 's';
const author = await User.findOne({ username: req.params.username }).exec();
const authorId = await author.toObject({ getters: true }).id;
const updatedPost = await Post.findOneAndUpdate(
{ author: authorId, title: postTitle, _id: postId },
isUndoing ? { $pull: { [actionKey]: userId } } : { $addToSet: { [actionKey]: userId } },
{ new: true, timestamps: false }
);
if (isUndoing) await removeLikeNotification(userId, updatedPost._id, authorId);
else await likeNotification(userId, updatedPost._id, authorId);
res.status(200).json(updatedPost.toObject({ getters: true }));
};
module.exports = {
createPost,
getPosts,
getPost,
updatePost,
deletePost,
deletePostsByUserId,
postReaction,
};
and this is my front end part new-post.jsx
import 'easymde/dist/easymde.min.css';
import { useContext, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import SimpleMDE from 'react-simplemde-editor';
import tw from 'twin.macro';
import Error from '../../common/Error';
import LoadingSpinner from '../../common/LoadingSpinner';
import RouteWrapper from '../../common/RouteWrapper';
import socketContext from '../../context/SocketContext';
import { selectCurrentUser } from '../../core/features/auth/authSlice';
import { useCreatePostMutation } from '../../core/features/posts/postsApiSlice';
import { useGetUserDashboardQuery } from '../../core/features/users/usersApiSlice';
import useBase64 from '../../hooks/useBase64';
import useRequireAuth from '../../hooks/useRequireAuth';
import { Editor } from "react-draft-wysiwyg";
// import "../../../node_modules/react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
// import React, { Component } from 'react';
import "../NewPost/react-draft-wysiwyg.css"
// import "react-draft-wysiwyg/dist/react-draft-wysiwyg.cs";
// import LiveMarkdown from '../markdowneditor/markdowneditor';
// import EasyMDE from 'easymde';
const NewPost = () => {
const [title, setTitle] = useState('');
const [file, setFile] = useState('');
const [body, setBody] = useState('');
const [tags, setTags] = useState('');
const [isTagsFocused, setIsTagsFocused] = useState(false);
const [inputsFilled, setInputsFilled] = useState(false);
const filePickerRef = useRef();
const titleRef = useRef();
const [createPost, { isLoading, isError }] = useCreatePostMutation();
const navigate = useNavigate();
const currentUser = useSelector(selectCurrentUser);
const dispatch = useDispatch();
const previewURL = useBase64(file);
const { isAuthed, handleAuth } = useRequireAuth();
const { socket } = useContext(socketContext);
const { data: user } = useGetUserDashboardQuery(currentUser.username);
useEffect(() => titleRef.current.focus(), []);
useEffect(() => {
if (title && body && tags) setInputsFilled(true);
else setInputsFilled(false);
}, [title, body, tags]);
const handleSubmit = async () => {
if (inputsFilled) {
if (isAuthed) {
try {
const { id } = await createPost({
title,
file: previewURL,
body,
tags,
authorUsername: currentUser.username,
}).unwrap();
socket.emit('post', {
sender: currentUser,
receivers: user?.followers,
post: { title, id },
});
setTitle('');
setFile('');
setBody('');
setTags('');
navigate('/');
} catch (err) {
console.log(err);
}
} else handleAuth();
}
};
return (
<RouteWrapper>
<Wrapper>
{isLoading && <LoadingSpinner />}
{!isLoading && (
<NewPostWrapper>
<Heading>ایجاد پست جدید</Heading>
<InputWrapper>
<Label dir='rtl' htmlFor='title'>موضوع</Label>
<Input
dir='rtl'
ref={titleRef}
id='title'
value={title}
onBlur={e => setTitle(prev => prev.trim())}
onChange={e => setTitle(e.target.value)}
required
/>
</InputWrapper>
<InputWrapper>
// <Input
type='file'
ref={filePickerRef}
onChange={e => setFile(e.target.files[0])}
style={{ display: 'none' }}
/>
<ImagePreview src={previewURL.toString()} alt='عکس انتخاب کنید' />
<Button onClick={() => filePickerRef.current.click()}>انتخاب آواتار</Button>
</InputWrapper>
<InputWrapper2>
{/* <SimpleMDE value={body} onChange={setBody} required /> */}
<Editor
editorState={body}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={setBody}
textAlignment="right"
placeholder="اینجا تایپ کنید"
/>;
</InputWrapper2>
<InputWrapper>
<Label htmlFor='tags'>
تگ ها
{isTagsFocused && (
<Span>تگ ها با کاما جدا شده هست</Span>
)}
</Label>
<Input
id='tags'
value={tags}
onFocus={() => setIsTagsFocused(true)}
onBlur={() => setIsTagsFocused(false)}
onChange={e => setTags(e.target.value.replace(/ /g, ''))}
required
/>
</InputWrapper>
<Submit onClick={handleSubmit}>تایید</Submit>
{isError && <Error>خطاا در انجام عملیات . دوباره امتحان کنید</Error>}
{!inputsFilled && <Error>تمام فیلدها اجباری هست</Error>}
</NewPostWrapper>
)}
</Wrapper>
</RouteWrapper>
);
};
const Submit = tw.button`bg-lighter-gray hover:bg-light-gray rounded-md text-center py-2 px-1 w-full text-sm`;
const ImagePreview = tw.img`w-32 h-32 mx-auto border border-gray flex justify-center items-center text-center object-cover`;
const Input = tw.input`py-1 px-2 rounded-md outline-none border-2 border-solid border-gray focus:border-blue`;
const Label = tw.label`font-bold text-dark-gray`;
const Span = tw.p`inline ml-sm`;
const InputWrapper = tw.div`flex flex-col gap-2 `;
const Button = tw.button`bg-lighter-gray hover:bg-light-gray rounded-md text-center py-2 px-1 w-28 text-sm mx-auto`;
const Heading = tw.h1`text-dark-gray text-center`;
const NewPostWrapper = tw.div`bg-white w-3/5 mob:(w-full px-4) mx-auto py-20 px-8 [&>*:not(:last-child)]:mb-md`;
const Wrapper = tw.div`flex items-center`;
const InputWrapper2 = tw.div`border border-gray`;
export default NewPost;
i tried to remove file change and change . but this error not going away. please if some one know this answer help me
This error is occuring as you are trying to create a Post with no values but in your schema you have the values of title, body etc set as required and thus this error, check if you are calling the create Post from your frontend with no values.
Try to check this useEffect where you set your inputsfilled, I bet it sets the inputsfilled true even if the values are empty.
useEffect(() => {
if (title && body && tags) setInputsFilled(true);
else setInputsFilled(false);
}, [title, body, tags]);
The backend error is because the you are trying to create Post with required params but with empty values.
Hopefully this helps. This might even not be the problem. My bad if I got it wrong.

How to add more objects to Array Object state using setState?

This is my initial data
const data = [
{ id: '1', name: '1' },
{ id: '2', name: '1' },
{ id: '3', name: '2' },
]
I want to loop over and:
Where it has name 1 add that object to stateOne
Where it has name 2 add that object to stateTwo
End goal both states needs to have Array of Objects inside:
stateOne needs to look like
[
{ id: '1', name: '1' },
{ id: '2', name: '1' }
]
stateTwo needs to look like
[
{ id: '3', name: '2' },
]
This is what i've tried:
const data = [
{ id: '1', name: '1' },
{ id: '2', name: '1' },
{ id: '3', name: '2' },
]
const Testing = () => {
const [stateOne, setStateOne] = useState([])
const [stateTwo, setStateTwo] = useState([])
useEffect(() => {
data.forEach((e) => {
if (e.name === '1') {
console.log('e', e)
setStateOne((prevSate) => ({ ...prevSate, e }))
}
// if (e.name === '2') {
// setStateTwo(e)
// }
})
}, [])
console.log('stateOne', stateOne)
}
I'd prefer sending data as a prop to that component
You can achieve what you need by
const data = [
{ id: '1', name: '1' },
{ id: '2', name: '1' },
{ id: '3', name: '2' },
]
export default function Testing() {
const [stateOne, setStateOne] = useState([])
const [stateTwo, setStateTwo] = useState([])
useEffect(() => {
setStateOne(data.filter(e => e.name === "1"))
setStateTwo(data.filter(e => e.name === "2"))
console.log('stateOne', stateOne)
console.log('stateTwo', stateTwo)
}, [])
}
setState functions as an assignment. Like you would normally assign a variable. That means if you want to add something to an array, you need to include that array in the assignment.
Something like this:
if (e.name === '1') {
console.log('e', e)
setStateOne([...stateOne, e])
}
if (e.name === '2') {
setStateTwo([...stateTwo, e])
}
If you don't want to use filter twice for whatever reason, You can create temporary array for each one and manipulate them then update each state respectively like so:
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
useEffect(() => {
const tempArr1 = [];
const tempArr2 = [];
data.forEach((item) => {
if (item.name === "1") {
tempArr1.push(item);
} else if (item.name === "2") {
tempArr2.push(item);
}
});
setStateOne(tempArr1);
setStateTwo(tempArr2);
}, []);
console.log(stateOne);
console.log(stateTwo);
The problem with what you're doing is you're updating the state each time you find a match which will cause a lot of unnecessary re-renders.
You've said that data comes from some API you're querying. If so, filter the data once you get it. You can do that in a couple of ways.
With two calls to filter:
const Testing = () => {
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
useEffect(() => {
let cancelled = false;
getTheData(data => {
if (cancelled) {
return;
}
setStateOne(data.filter(({name}) => name === "1"));
setStateTwo(data.filter(({name}) => name === "2"));
};
return () => {
// So we don't try to set setate on an unmounted component
cancelled = true;
};
}, []);
// ...use `dataOne` and `dataTwo` here...
};
Or if you don't want to make two passes through the data, a single loop:
const Testing = () => {
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
useEffect(() => {
let cancelled = false;
getTheData(data => {
if (cancelled) {
return;
}
const stateOne = [];
const stateTwo = [];
for (const entry of data) {
switch (entry.name) {
case "1":
stateOne.push(entry);
break;
case "2": // or default if you want all non-1s in `stateTwo`
stateTwo.push(entry);
break;
}
}
setStateOne(stateOne);
setStateTwo(stateTwo);
};
return () => {
// So we don't try to set setate on an unmounted component
cancelled = true;
};
}, []);
// ...use `dataOne` and `dataTwo` here...
};
const data = [
{ id: "1", name: "1" },
{ id: "2", name: "1" },
{ id: "3", name: "2" }
];
const App = () => {
const newdata = useState(data);
const [stateOne, setStateOne] = useState([]);
const [stateTwo, setStateTwo] = useState([]);
const Filter = () => {
let listOne = [];
let listTwo = [];
newdata[0].map((it) => {
if (it.name === "1"){
listOne.push(it);
}
else if(it.name === "2"){
listTwo.push(it)
}
});
setStateOne(listOne);
setStateTwo(listTwo);
};
useEffect(() => {
Filter();
}, []);
console.log("stateOne", stateOne)
console.log("stateTwo", stateTwo)
return (
// your code
)
};

Fetching data with hook takes too long time

I am trying to fetch and map an array with 350 object's elements. I decided to use Hook and useEffect, to re render my dataTable component since mapping is done. Unfortunately, the whole process takes enormous amount of time, and it makes page unresponsive. After 1-2 minutes, table shows up and after few seconds it disappears. After that page is still unresponsive. Could someone explain why it happens, and give me some workaround? I would be grateful.
Code below:
const Employees = (props) => {
const [developers, setDevelopers] = useState([]);
useEffect(() => {
fetchData();
});
const columns = [
{
name: "Emloyee",
selector: "name",
sortable: true,
},
{
name: "Team ",
selector: "team",
sortable: true,
},
{
name: "Email ",
selector: "email",
sortable: true,
},
];
const fetchData = () => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
};
const mapData = (jsonData) => {
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#mail.com",
};
setDevelopers((developers) => [...developers, newDeveloper]);
});
};
return <DataTable title="Employees" columns={columns} data={developers}/>;
};
useEffect without dependency array will run on every render, so in your case, you are stuck in an infinite loop which cause page to become unresponsive
solution:
const fetchData = useCallback(() => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
},[]);
const mapData = useCallback((jsonData) => {
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#kuehne-nagel.com",
};
setDevelopers((developers) => [...developers, newDeveloper]);
});
},[]);
useEffect(() => {
fetchData();
},[fetchData]); // pass dependency array here in useEffect
Thanks to #DrewReese and #SarthakAggarwal , I've got a solution:
const Employees = (props) => {
const [developers, setDevelopers] = useState([]);
const columns = [
{
name: "Emloyee",
selector: "name",
sortable: true,
},
{
name: "Team ",
selector: "team",
sortable: true,
},
{
name: "Email ",
selector: "email",
sortable: true,
},
];
const fetchData = useCallback(() => {
axios.get("http://localhost:3128/employees", {
headers: {
'Access-Control-Allow-Origin': '*',
}
})
.then((response) => {
mapData(response.data.developers);
console.log("I am here!");
})
.catch((e) => console.log(e));
}, []);
const mapData = (jsonData) => {
let table = [];
jsonData.forEach((x) => {
let newDeveloper = {
name: x.userId,
team: x.team,
email: x.userId + "#mail.com",
};
table = [...table,newDeveloper];
//setDevelopers((developers) => [...developers, newDeveloper]);
});
setDevelopers((developers) => table);
};
useEffect(() => {
fetchData();
}, [fetchData]);
return <DataTable title="Employees" columns={columns} data={developers}/>;
};
Thanks a lot !

How to test an async method (not explicitly using fetch) mocking its return value in React?

I want to test Stripe function createToken, and check if it is saved into the state in React but I can not find a way to mock the response.
So far I have tried to mock the function with jest but when I run the test I can see that the function is never called.
Here is my Parent component. On the child component there is a button that activate the function "sendPayment". In the child component I tested if this function was called, so now the idea is to create a wrapper with enzyme and run the function.
var stripe = require('stripe-client')(STRIPE_PUBLIC_KEY);
class StripePayment extends React.Component {
constructor(props) {
super(props);
this.state = {
number: '',
exp_year: '',
exp_month: '',
cvc: '',
error: '',
token: ''
};
}
handleOnChangeText = (text, cardAttribute) => {
this.setState({
[cardAttribute]: text,
});
};
sendPayment = () => {
//
stripe.createToken(this.createCard())
.then(resp => this.setState({token: resp.id}))
.catch(error =>
this.setState({
error,
})
);
};
createCard = () => {
let informations = {};
information.card = {};
Object.keys(this.state)
.filter(key => !['error','token'].includes(key))
.forEach(filteredKey => (information.card[filteredKey] = this.state[filteredKey]));
return informations;
};
render() {
return <StripeForm sendPayment={this.sendPayment} error={this.state.error} />;
}
}
StripePayment.navigationOptions = {
header: null,
};
export default StripePayment;
The test I wrote so far:
it('should save token to state', async () => {
const wrapper = shallow(<StripePaymentScreen />)
const instance = wrapper.instance()
const response = {id: 'token'}
jest.spyOn(stripe, 'createToken').mockImplementation(() => response);
await instance.sendPayment();
expect(stripe.createToken).toHaveBeenCalled();
expect(instance.state.data).toEqual({ token: 'token' });
});
Or alternatively
describe('StripePayment', () => {
let stripe;
beforeEach(() => {
stripe = {
createToken : jest.fn()
};
});
it('should save token to state', async () => {
const wrapper = shallow(<StripePaymentScreen />)
const instance = wrapper.instance()
const response = {id: 'token'}
jest.spyOn(stripe, 'createToken').mockImplementation(() => response);
stripe.createToken.mockResolvedValue(response);
await instance.sendPayment();
expect(stripe.createToken).toHaveBeenCalled();
expect(instance.state.data).toEqual({ token: 'token' });
});
});

Categories