I'm confused about the callback function in react js - javascript

I followed the react js tutorial, and the code is written like this
import React, { Fragment, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { ArticleListSection } from "../../components";
import { ArticlesCard, Hero } from "../../components/molecules";
import { setDataBlog } from "../../config/redux/action";
export const Home = () => {
const { dataBlog } = useSelector((state) => state.homeReducer);
const dispatch = useDispatch();
useEffect(() => {
dispatch(setDataBlog());
}, [dispatch]);
return (
<Fragment>
<Hero
titleHero="Kemal is a place to write, read, and connect."
descHero="It's easy and free to post your thinking on any topic and connect
with millions of readers."
button="Start Writing!"
></Hero>
<ArticlesCard dataBlog={dataBlog}></ArticlesCard>;
<ArticleListSection dataBlog={dataBlog}></ArticleListSection>
</Fragment>
);
};
export default Home;
the other part is called like this (homeAction.js)
import Axios from "axios";
export const setDataBlog = () => (dispatch) => {
console.log({ dispatch });
Axios.get("http://localhost:4000/v1/blog/posts")
.then((result) => {
const responseAPI = result.data;
dispatch();
})
.catch((err) => {
console.log("error: ", err);
});
};
What I understand, is that dispatch is a function of useDispatch and it is passed as a parameter in the form of a function setDataBlog().
so if i destucturing the homeAction will be like this
export const setDataBlog = () =>{
return(dispatch)=>{
Axios.get("http://localhost:4000/v1/blog/posts")
.then((result) => {
const responseAPI = result.data;
dispatch({ type: "UPDATE_DATA_BLOG", payload: responseAPI.data });
})
.catch((err) => {
console.log("error: ", err);
});
}
}
BUT I'm confused,
what is returned from the dispatch
I know that the 2 arrow function in homeAction.js file is a function that returns another function but why is it written like that.
in the setDataBlog function there is another "dispatch" parameter, where does this parameter come from??!!
why in setDataBlog function he calls dispatch function again??? isn't it the dispatch function that calls setDataBlog, how can call parent function inside child function?!!!
Anyway thank you if you answering this question.

I have solved this problem.
So basically, dispatch is working like this .
to understand this issue we need to get closer to the material
arrow functions
curried function
callbacks
And I will answer my questions
I know that the 2 arrow function in homeAction.js file is a function that returns another function but why is it written like that.
to understand this maybe we should start from a simple syntax like this:
middleWare = callBack => {
callBack("Ini pesan dari middleware");
}
fungsi2 = pesan=>{
document.write(pesan)
}
const container = () => {
middleWare(fungsi2)
}
container()
think of middleWare as our dispatch, and fungsi2 as our setDataBlog. From the simple callback function above, we will get output on our browser screen "Ini pesan dari middle ware!".
furthermore, what if we actually have a function inside a function like this:
fungsi2 = () => pesan=>{
document.write(pesan)
}
how do we print the text?
the way we print it is by calling the function like this:
const container = () => {
middleWare(fungsi2())
}
okay we are getting closer to the answer to our question.
now what if we make the parameter in our callback a variable?
middleWare = callBack => {
const action = "ini output callback"
callBack(action);
}
fungsi2 = () => pesan =>{
document.write(pesan)
}
const container = () => {
middleWare(fungsi2())
}
container()
wow it can! means we can dispatch functions also inside this middleware. okay let's pass the function into the callback
middleWare = callBack => {
const fungsiBerupaOutput = (text) =>{
document.write(text)
}
callBack(fungsiBerupaOutput)
}
fungsi2 = () => fungsi =>{
fungsi("halo")
}
const container = () => {
middleWare(fungsi2())
}
container()
now we can print the hello text to the screen from the fungsi parameter.
In conclusion, unknown parameters are sent from the middleware so we can print them.
Hope it helps for people who are just learning Java Script!
Reference:
https://www.w3schools.com/js/js_arrow_function.asp
https://www.petanikode.com/javascript-fungsi/
https://www.petanikode.com/javascript-output/
https://www.geeksforgeeks.org/what-is-the-use-of-middleware-redux-thunk/

Related

React Hook for a POST call onClick

I have a button, onClick of that button I want to make a POST call with some data user has filled in an input field, stored in state, and then redirect the user to another page.
My current code looks like this, but I get an error:
React Hook "usePost" is called in function "onAccept" which is neither a React function component or a custom React Hook function
And the code doesn't work. I have created my own hook for POST calls.
What might a way to make the desired functionality work?
What I'm after is the ability to make a POST call and redirect.
Simplified example:
// my function
const onAccept = () => {
const { data, loading, error } = usePost(
"MY_URL",
{ DATA: MY_DATA }
);
if (data && !error) {
navigate(`/`);
}
};
// return
<button onClick={() => onAccept()}
Yes, You are calling usePost hook inside of onAccept function. You should follow react hook rule.
To solve your problem, you can do like that:
your custom hook file:
export const usePost = () => {
const [status, setStatus] = useState()
const handlePost = useCallback(async (url, data) => {
// your api request logic in here, bellow only show example
try {
const {data, status} = await apiCall(url, data)
if (data && status === 200) navigate(`/`)
} catch (error) {
console.log(error)
}
}, [])
return { handlePost }
// to return status to component, you can use bellow.
// return { status, handlePost }
}
then your component:
const YourComponent: React.FC = () => {
const { handlePost } = usePost()
// To get status: const { status, handlePost } = usePost()
// your other hooks in here
// Check status
useEffect(() => {
if (status === 200) {
// whatever you want to do
}
}, [status])
return (
<>
// Your component UI here
...
<button onClick={() => handlePost(url, data)}>
</>
)
}
You should call your custom hooks(for example: usePost) at the top level of component, not nested function body as like as you were doing in your code (onAccept function body).
I can suggest you do the following.
At first, you should create fetch function which will be returned from usePost hook.
example.
export const usePost = () => {
const [loading, setLoading] = useState(false)
const [data, setData] = useState([])
const fetch = () => {
setStatus(loading)
apiRequest({
url: 'my_url',
method: 'GET',
}).then(response => {
setStatus(false)
setData(response.data.data)
}).catch(e => {
setStatus(false)
})
}
return {
status,
data,
fetch
}
After all, you can call this hook in your component. It will return fetch function. You should call fetch inside onAccept.
Example.
const { data, loading, fetch } = usePost()
const onAccept = () => {
fetch()
}
// return
<button onClick={() => onAccept()}
PS. if you need you can return errors from usePost hook, as well.
First, call you hook from React Function. Read the docs: https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions.
Second, you should have some sort of load method in your usePost hook, e.g.: const { load } = usePost(...), in order to make POST request on click.
So your handler will look like:
const onAccept = () => {
load();
// the following block move somewhere before you render the component or better use useEffect hook for that
// if (data && !error) {
// navigate(`/`);
// }
};
I hope this will help.

Is it possible to fetch API without useEffect in React JS?

Hello i'm newbie here...
I found my friend's code when he using useState instead of using useEffect to fetch the API.
I tried it and it worked, the code didn't cause an error and infinite loops.
here is for the code
import { useState } from "react";
import { IN_THEATER, POSTER } from "../../../constant/movies";
import { GlobalGet } from "../../../utilities/fetch";
const Service = () => {
const [movieData, setMovieData] = useState({ data: null, poster: null });
const fetchMovieData = async () => {
try {
let movieRes = await GlobalGet({ url: `${IN_THEATER}` });
return movieRes;
} catch (error) {
console.log(error);
}
};
const fetchPoster = async () => {
try {
let posterRes = await GlobalGet({ url: `${POSTER}` });
return posterRes;
} catch (error) {
console.log(error);
}
};
const fetchData = async () => {
setMovieData({
data: await fetchMovieData(),
poster: await fetchPoster(),
});
};
useState(() => { //<=here it is
fetchData();
}, []);
return {
movieData,
};
};
export default Service;
And my question is, why it could be happen ? why using useState there doesn't cause an infinite loops ?
The useState() function can accept an initializer function as its first argument:
const [state, setState] = useState(initializerFunction)
When a function is passed to useState(), that function is only called once before the component initially mounts. In your case below, the initializer function is an anonymous arrow function:
useState(() => { // <-- Initializer function invoked once
fetchData();
}, []);
Here, the initializer function is () => { fetchData(); }, which is invoked once before the initial mount, so the fetchData() method is only called once. The array that is passed as the second argument [] is useless and doesn't do anything in this case as it's ignored by useState(). The above useState would behave the same if you did useState(fetchData);. Because fetchData() is only called once on the initial mount, any state updates of your component don't cause the fetchData() function to execute again as it's within the initializer function.
With that said, useState() shouldn't be used for fetching data on mount of your component, that's what useEffect() should be used for instead.
Generally it's possible to fetch data from outside of the useEffect hook.
Somewhere in the body of your component...
const [fetchedData, setFetchedData] = useState(false)
const someFetchFunc = asyunc (url) => {
setFetchedData(!fetchedData)
const res = await fetch(url)
const data = await res.json()
setMovieData(data)
}
!fetchedData && someFetchFunc()
But this is an antipattern. In this case developer lacks a whole toolset of dealing with possible issues. What if fetching fails?
So, it's generally a good idea to handle all the side effects like fetching in a place that was intended for that. It's useEffect hook)

Debouncing with multiple hooks

I am trying to implement debouncing in my app, however, the most I am able to achieve is to debounce the speed of the input. The gist of the App, is that it first takes input from the user, to generate a list of cities, according to the input and when selected, will provide a forecast for the whole week.
Here is the original code, without debouncing:
import React, { useState, useEffect } from 'react';
import * as Style from './Searchbar.styles';
import { weatherAPI } from '../API/api';
import endpoints from '../Utils/endpoints';
import { minCharacters } from '../Utils/minChars';
import MultiWeather from './MultiCard';
import useDebounce from '../Hooks/useDebounce';
export default function SearchBar() {
const [cityName, setCityName] = useState('');
const [results, setResults] = useState([]);
const [chooseCityForecast, setChooseCityForecast] = useState([]);
useEffect(() => {
if (cityName.length > minCharacters) {
loadCities();
}
}, [cityName, loadCities]);
//this is used to handle the input from the user
const cityValueHandler = value => {
setCityName(value);
};
//first API call to get list of cities according to user input
const loadCities = async () => {
try {
const res = await weatherAPI.get(endpoints.GET_CITY(cityName));
setResults(res.data.locations);
} catch (error) {
alert(error);
}
};
//second API call to get the forecast
const getCurrentCity = async city => {
try {
const res = await weatherAPI.get(endpoints.GET_DAILY_BY_ID(city.id));
console.log(res);
setChooseCityForecast(res.data.forecast);
setCityName('');
setResults([]);
} catch (error) {
alert(error);
}
};
return (
<Style.Container>
<h1>Search by City</h1>
<Style.Search>
<Style.SearchInner>
<Style.Input type="text" value={cityName} onChange={e => cityValueHandler(e.target.value)} />
</Style.SearchInner>
<Style.Dropdown>
{cityName.length > minCharacters ? (
<Style.DropdownRow results={results}>
{results.map(result => (
<div key={result.id}>
<span
onClick={() => getCurrentCity(result)}
>{`${result.name}, ${result.country}`}</span>
</div>
))}
</Style.DropdownRow>
) : null}
</Style.Dropdown>
</Style.Search>
{chooseCityForecast && (
<section>
<MultiWeather data={chooseCityForecast} />
</section>
)}
</Style.Container>
);
}
The code above works perfectly, aside from creating an API call everytime I add an additional letter. I have refered to this thread on implementing debouncing. When adjusted to my code, the implementation looks like this:
const debounce = (fn, delay) => {
let timerId;
return (...args) => {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
}
};
const debouncedHandler = useCallback(debounce(cityValueHandler, 200), []);
But, as mentioned before, this results in debouncing/delaying user input by 200ms, while still creating additional API calls which each extra letter.
If I try to debounce the loadCities function or add a setTimeout method, it will delay the function, but will still make the API calls with each additional letter.
I have a hunch, that I need to remake the logic, which is handling the input, but at this point I am out of ideas.
After some digging I found a simple solution, that will be refactored later, but the gist of it is to move the loadCities function inside the use effect and implenet useTimeout and clearTimeout methods within the useEffect and wrapping the API call function, as seen below:
useEffect(() => {
let timerID;
if (inputValue.length > minCharacters) {
timerID = setTimeout(async () => {
try {
const res = await weatherAPI.get(endpoints.GET_CITY(inputValue));
setResults(res.data.locations);
} catch (error) {
alert(error);
}
}, 900);
}
return () => {
clearTimeout(timerID);
};
}, [inputValue]);
Hope this will help someone.

Using use-State hook in react function always throws an error + How to share a variable with another component

I'm a beginner to programming in Javascript/Typescript and just about to develop my very first app in react. To get some data from the backend (FastAPI) I created the function "GetData" that is executed whenever the button is clicked.
Component 1: Button
import { GetData } from "./socket"
export default function Button() {
return (
<button onClick={GetData}>
Run
</button>
)
}
Component 2 (named socket): websocket and data logic
import {useState, createContext} from 'react';
let socket: any;
export async function ConnectWebsocket() {
socket = new WebSocket("ws://127.0.0.1:8000/");
socket.onopen = () => {
console.log('connected')
}
socket.onclose = () => {
console.log('closed')
}
socket.onerror = () => {
console.log('error')
}
}
export async function GetData() {
const [data, setData] = useState({});
socket.send("get Data");
socket.onmessage = (event: any) => {
const newData = JSON.parse(event.data);
console.log(`Data from server: ${newData}`);
setData((data) => ({ ...data, ...newData }));
}
console.log(`Overall data: ${data}`);
}
The problem I'm facing is the useState hook. Whenever I try to stream the data via the websocket by clicking the Run-Button I always get the following error:
Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
I also created a small example using useState-hook and this one worked. Can you spot what I messed up in my code above?
Plus I have another beginner question. How would you make the "data" variable available to a third component (e.g. a table)?
You can only use react hooks inside the actually react component. In your case your Button component. So I would do something like this instead:
class SocketHelper {
socket = new WebSocket("ws://127.0.0.1:8000/");
constructor() {
socket.onopen = () => {
console.log('connected')
}
socket.onclose = () => {
console.log('closed')
}
socket.onerror = () => {
console.log('error')
}
}
}
export const socketHelper = new SocketHelper();
export default function Button() {
const [data, setData] = useState({});
useEffect(() => {
socketHelper.socket.onmessage = (event: any) => {
const newData = JSON.parse(event.data);
console.log(`Data from server: ${newData}`);
setData((data) => ({ ...data, ...newData }));
}
}, []);
const getData = () => {
socketHelper.socket.emit("getdata");
}
return (
<div>
<button onClick={getData}>
Run
</button>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
Also you are using socket.send but it doesn't seems like your are using the returned socket. So instead i would use the emit function: https://socket.io/docs/v4/client-api/#socketemiteventname-args

how to get check setState function callled or not in react js using hooks?

I am trying to test custom hook. I want to know is setState function fire or not.
here is my custom hook
import React from "react";
import axios from "axios";
export default () => {
const [state, setState] = React.useState([]);
const fetchData = async () => {
const res = await axios.get("https://5os4e.csb.app/data.json");
setState(res.data);
};
React.useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
return { state };
};
now I am trying to test this custom hook. I want to know is setState function fire or not .
I tried like this
import moxios from "moxios";
import React from "react";
import { act, renderHook, cleanup } from "#testing-library/react-hooks";
import useTabData from "./useTabData";
describe("use tab data", () => {
beforeEach(() => {
moxios.install();
});
afterEach(() => {
moxios.uninstall();
});
describe("non-error response", () => {
// create mocks for callback arg
const data = [
{
name: "hello"
}
];
let mockSetCurrentGuess = jest.fn();
beforeEach(async () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: data
});
});
});
test("calls setState with data", async () => {
React.useState = jest.fn(() => ["", mockSetCurrentGuess]);
const { result, waitForNextUpdate } = renderHook(() => useTabData());
console.log(result);
//expect(mockSetCurrentGuess).toHaveBeenCalledWith(data);
});
});
});
You should not mock the React internals. This is incorrect. Either ways, this code has no effect in mocking due to closures. Even if it worked, no point in testing if you are mocking the real implementation, isn't it ? :)
I would recommend to try to get grasp of what react hook is doing in your code.
You have a state in your custom hook:
const [state, setState] = React.useState([]);
.
.
return [state]; //you are returning the state as ARRAY
#testing-library/react-hooks allows you to debug and get value of current outcome of hook.
const { result, waitForNextUpdate } = renderHook(() => useTabData());
const [foo] = result.current; // the array you returned in hook
expect(foo).toEqual('bar'); //example assertion
I would stop here and allow you to learn and debug.

Categories

Resources