React Hook useEffect has a missing dependency: 'callDayList'. Either include it or remove the dependency array - use-effect

React useState keep giving me undefined
this is my last questions, and it helped me a lot.
I had a problem of updating of state, and I understood the cause. but it caused me another problem.
const [urlList, setUrlList] = useState(0);
const callDayList = () =>{
Axios.post('http://localhost:3001/api/get',{
passNum :urlList,
});
};
useEffect(() => {callDayList();},[urlList]);
this is my code, and when urlList changes, I want to execute callDayList and pass the value of urlList. when I study about useEffect, I found the problem, if I include urlList inside callDayList, it will cause infinite re-rendering error. but I want to pass the value of urlList when it changes. how can I solve it?

Warning is thrown since you are not using urlList in the useEffect hook. Make use of the variable or update callDayList method.
const [urlList, setUrlList] = useState(0);
const callDayList = (list) =>{
Axios.post('http://localhost:3001/api/get',{
passNum :list,
});
};
useEffect(() => {callDayList(urlList);},[urlList]);
or
const [urlList, setUrlList] = useState(0);
const callDayList = () =>{
Axios.post('http://localhost:3001/api/get',{
passNum :urlList,
});
};
useEffect(callDayList,[urlList]);

Another solution is to use React.useCallback.
Right now, callDayList changes on every render which is one of the reasons why you can't add it to the dependency list. To prevent this, wrap the function in a useCallback:
const callDayList = React.useCallback(() => {
Axios.post('http://localhost:3001/api/get', {
passNum: urlList,
});
}, [urlList]);
Now, callDayList will change only when urlList changes. Now you can rewrite your useEffect:
React.useEffect(callDayList, [callDayList]);
Now callDayList is called only when it changes, and it changes only when urlList changes - which is what you want.

Related

Is there any way to call useeffect until the data had been fetched properly?

I want to fetch my data from backend mongodb but the useeffect is rendering only once and at that moment data is not fetched quickly resulting in lots of undefined errors.
Here is my code:
const [xnaLogData,setXnaLogData]=useState([]);
const [xnaLogMember,setXnaLogMember]=useState("");
const [balance,setBalance]=useState(0);
useEffect(() => {
setXnaLogMember(Cookies.get('member_userr'));
alert(xnaLogMember);
const calBal = () => {
var sum = xnaLogData.reduce(function(prev, current) {
return prev + +current.balance
}, 0);
setBalance(sum);
}
const fetchXna = async () => {
const response = await fetch('/xnaloginforoute');
const json = await response.json();
setXnaLogData(json.xnacomp);
calBal();
console.log(json.xnacomp)
console.log(xnaLogData);
}
fetchXna();
},[]);
If i put },[xnaLogData]); inside the useeffect line },[]); it will start a continous chain of useeffect and the data is fetching in 3rd or 4th attempt of effect. I am unable to solve this issue
Ok, this is a typical useEffect question.
Let's revisit your logic a bit.
useEffect(() => {
setAbc(value)
}, [abc])
The problem of the above is an endless loop, unless the value can reach a stable value after couple of runs. Thus most of time, we have a short circuit line installed to break it off.
useEffect(() => {
if (count > 2) return
setCount(v => v + 1)
}, [count])
Either way i think you get the idea, either loop or not, it's entirely controlled by YOU, react is not going to make a decision what to do. You can rethink why you want to re-run the effect upon the value change.

Why does my async code not work properly unless I log the promise?

I have some async code that makes calls to a mongo database and inserts/fetches items. When I am developing locally, the code below works fine. However, when I make the mongoose instance connect to MongoDb Atlas, issues arise. In particular, it seems that my code does not work properly unless I console.log the promise, which makes no sense to me. For example, with the console.log statement, all my tests pass as expected. Without it, 35 tests fail... This is because the promise I am expecting returns null, when it should return some JSON object from the database. Is my code not blocking properly?
It feels like I'm dealing with Schrodinger's cat... Any help would be appreciated. Thanks in advance.
Below is an example promise/function call. I then pass it into _executeQuery. I have await on relevant functions, so I don't think it's because I'm missing the word await somewhere.
async _inSomeAsyncFunction = () => {
const dbQueryPromise = this._dbModel.findById(_id, modelView).lean();
await this._executeQuery({ dbQueryPromise, isAccessPermitted: true })
}
_executeQuery basically gets the result of the promise if the user has access.
private _executeQuery = async (props: {
isAccessPermitted: boolean;
dbQueryPromise: Promise<any>;
}): Promise<any> => {
const { isAccessPermitted, dbQueryPromise } = props;
if (!isAccessPermitted) {
throw new Error('Access denied.');
}
console.log(dbQueryPromise, 'promise'); // without this line, dbQueryResult would be null...
const dbQueryResult = await dbQueryPromise;
return dbQueryResult;
};
After some more testing, I found out that the first API call works but any calls after that returns null...
EDIT:
this._dbModel is some mongoose schema. For example,
const dbSchema= new Schema({
name: String,
});
const dbModel = mongoose.model('DbSchema', dbSchema);
Try replacing your dbQueryPromise as follows:
const dbQueryPromise = this._dbModel.findById(_id, modelView).lean().exec();
Mongoose queries do not get executed unless you pass a callBack function or use exec()
For anyone else having similar problems, here's how I solved it:
I changed
const dbQueryResult = await dbQueryPromise;
to
const dbQueryResult = await dbQueryPromise.then((doc) => {
return doc;
});

how to properly handle firebase realtime database listeners with useEffect?

const Messages = (props) => {
const [messages, setMessages] = useState([]);
const { currentChannel, currentUser } = props;
const messagesRef = firebase.database().ref("messages");
useEffect(() => {
if (currentChannel && currentUser) {
const channel = currentChannel.currentChannel;
let loadedMessages = [];
messagesRef.child(channel.id).on("child_added", (snap) => {
loadedMessages.push(snap.val());
});
setMessages(loadedMessages);
}
return () => messagesRef.off();
}, [currentChannel, currentUser, messagesRef]);
const displayMessages = (messages) =>
messages.length > 0 &&
messages.map((message) => (
<Message key={message.timestamp} message={message} user={currentUser} />
));
return (
<>
<MessagesHeader />
<Segment>
<Comment.Group className="messages">
{displayMessages(messages)}
</Comment.Group>
</Segment>
<MessageForm
messagesRef={messagesRef}
currentChannel={currentChannel}
currentUser={currentUser}
/>
</>
);
};
Warning: Maximum update depth exceeded. This can happen when a
component calls setState inside useEffect, but useEffect either
doesn't have a dependency array, or one of the dependencies changes on
every render.
How to prevent this infinite loop?
Every time you render, you're creating a new messagesRef object. It may be dealing with the same spot in the database, but it's a new object. Since messageRef changed, your effect will rerun and that effect will call setMessages with a brand new array each time. Since you set state, the component rerenders, and the loop repeats.
Most of the time, i would recommend having the database ref only exist inside your useEffect, and then removing it from the dependency array. However, it looks like you need to pass the ref as a prop to MessageForm, so that won't work here. Instead, you need to make sure that you only create the database ref once. useMemo is one way to do this:
const messagesRef = useMemo(() => {
return firebase.database().ref("messages");
}, []);

React-testing-library mock function not called

i'm trying to test my component:
in my componet when i click to button i call some function and pass there some id.
my test:
it("should call function on submit click", async () => {
const wrapper = render(<Component />);
const id = "1";
const handleSubmit = jest.fn();
const SubmitBtn = wrapper.getByTestId("submit-btn");
fireEvent.click(SubmitBtn);
await waitFor(() => wrapper);
expect(handleSubmit).toHaveBeenCalled();
});
here i tried:
expect(handleSubmit).toHaveBeenCalled();
expect(handleSubmit).toHaveBeenCalledWith(id);
expect(handleSubmit).toBeCalled();
expect(handleSubmit).toBeCalledWith(id)
tried everything but it's not works..
expect(jest.fn()).toBeCalled()
Expected number of calls: >= 1
Received number of calls: 0
Your mock cannot be called this, because you declare it after having rendered your component. I assume you have a handleSubmit function in your component that gets called on submit click, but when you call :
const handleSubmit = jest.fn();
...the component is already renderer and there is no way you can mock its handleSubmit function.
What I suggest :
Do not try to spy on handleSubmit function, test like a real user
You should not try to test internal component logic, but test what a real user would see : a loading spinner, a confirmation message...This is what promotes RTL, because it will give you confidence in your test, decouple your test from internal implementation (which is a good thing when it comes to React components unit testing), and force you as a developper to think about user first when writing code (thinking how you will test your code using RTL forces you to think about user experience).
In your test you could do this :
// in component
const handleSubmit = () => {
// do things
// say that this boolean controls a message display
setSubmittedMessageDisplay(true);
}
In test
it('should call function on submit click', async () => {
const wrapper = render(<Component />);
const submitBtn = wrapper.getByTestId('submit-btn');
fireEvent.click(submitBtn);
expect(wrapper.getByText('form submitted - controlled by submittedMessageDisplay')).toBeTruthy();
});
Pass the form submit handler down from parent component, mock it
This implies to lift things up to the parent component :
// in parent
const handleSubmit = () => {...}
<ChildWithForm onSubmit={handleSubmit} />
// in child
<Form onSubmit={props.onSubmit}>...</Form>
Then in your test you can do this :
test('submits form correctly', () => {
const mockedHandler = jest.fn();
const wrapper = render(<Child onSubmit={mockedHandler} />);
const submitBtn = wrapper.getByTestId('submit-btn');
fireEvent.click(submitBtn);
expect(mockedHandler).toHaveBeenCalled();
});

How to mock a function and expect it to be called?

I am new to react-testing-library and I have been trying to test one function for a long time.
for example, I want to check if when a button is clicked a given function is called and it's throwing errors. so any help would be highly appreciated and if possible share with me any helpful resources.
signin.js
export default class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
};
}
handleClose = (event, reason) => { };
validate = () => { };
change = (e) => { };
onSubmit = (e) => { };
render() {
return (<div>...</div>);
}
}
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/views/SignIn.js
this is my test
it('should submit form', async () => {
const { getByLabelText, getByText, container, debug } = render(<SignIn />);
const change = jest.fn();
const onSubmit = jest.fn();
const email = getByLabelText('email');
const password = getByLabelText('password');
const submit = getByLabelText('submit');
userEvent.type(email, 'octopusbn#gmail.com');
expect(email.value).toBe('octopusbn#gmail.com');
expect(password.value).toBe('');
expect(change).toHaveBeenCalled();
console.log(password)
await userEvent.click(submit);
expect(onSubmit).toHaveBeenCalled();
});
Full: https://github.com/blaise82/react-testing-library-try/blob/master/src/test/signin.test.js
results
> Expected number of calls: >= 1
> Received number of calls: 0
please let know what I am doing wrong.
Full code on GitHub: https://github.com/blaise82/react-testing-library-try
You can test a function by mocking all that is coming from outside of the component (aka dependencies) like - a prop callback, an external library api etc.
Before starting, let's go through what all functions are in the component.
Going through the component, I can list them as below:
Event handlers on elements [like handleClose, onSubmit, change in the component]
Functions internal to the component which do not interact with the state/functions outside the component [validate]
prop functions/library apis being called [axios.post]
Let's discuss them one by one --
Event handlers &
Functions internal to component not interacting with state/functions outside of the component
==> Event handlers that are attached to elements can safely be expected to get called. You don't need to test them if they are called. Rather, what you should test is the after-effect of them being called. Also the functions like validate
Let's take example of the change function that you are trying to test. This function after being called sets the state and the state gets reflected into the form elements. We can assert values of the form elements with a helper like this.
prop functions/library apis being called [axios.post]
==> These functions can be mocked and tested for the number of calls/parameters they are called with.
https://jestjs.io/docs/en/mock-functions.html#mocking-modules
In addition to the snippet of mocking jest as given in the link above, in your case -
axios.post.toHaveBeenCalledWith(expectedParms);
Also you can make it return results/errors you want and test respective component behaviour.
Hope you find this helpful. Cheers!
I think this is because you are not actually passing in your mocked functions to the component. You're just instantiating two constants that happen to have the name of the functions you're trying to watch, but are not actually being used anywhere in your component.
It sounds like you want to spy on your component's internal functions to see that they've been called.
Here's something of an example (not tested) based on a post (linked below) that might help you.
describe('spying on "onSubmit" method', () => {
it('should call onSubmit when the button is clicked', () => {
const wrapper = shallow(<SignIn />);
const instance = wrapper.instance();
jest.spyOn(instance, 'onSubmit');
wrapper.find('button').simulate('click');
expect(instance.onSubmit).toHaveBeenCalled();
});
});
Post: https://bambielli.com/til/2018-03-04-directly-test-react-component-methods/#spying-on-incrementcounter

Resources