Importing functions using Typescript - javascript

TLDR - what's the difference between these imports in ReactJs using Typescript:
setState1: (numbers: number[]) => void,
setState2: Function
Hi peeps, I've recently came across weird(for me) behaviour of importing functions using Typescript on React. To explain it better I'll just put sample code:
const FuncComp: React.FC = () => {
const [state1, setState1] = useState<number[]>([]);
const [state2, setState2] = useState<number[]>([]);
const handleSettingState1 = () => {
console.log("state1-",state1);
setState1([...state1, 1]);
}
console.log("state1-",state1);
const handleSettingState2 = () => {
console.log("state2-",state2);
setState1([...state2, 2]);
}
console.log("state2-",state2);
const saveChanges = () => {
saveHandler(
saveFunc,
setState1,
setState2
)
}
....
}
both handleSettingState1 and handleSettingState2 are functions passed to and set from child components, saveChanges is onClick function of button inside FuncComp and saveHandler is imported from helper file and it looks like this:
export const saveHandler = (
saveFunc: GqlMutationType,
setState1: (numbers: number[]) => void,
setState2: Function
) => {
saveFunc()
.then(() => {
setState1([]);
setState2([]);
})
.catch((err: AxiosError) => {
console.log(err);
});
};
As you can see setState1 and setState2 are imported differently.
Now the problem is that when I set both states through handlers -> perform save(it should clear both arrays in then callback) -> set another value, state1 will be empty both inside and outside handleSettingState1 scope, while state2 will have old value inside handleSettingState2 but empty inside the scope of the class.
I skipped implementation of handleSettingState2 and handleSettingState2 inside child component because I thought it's irrelevant but if it affects I can provide it too.

You can update the save handler like so.
export const saveHandler = (
saveFunc: GqlMutationType,
cb: (data: any) => void
) => {
saveFunc()
.then(data => cb(data))
.catch((err: AxiosError) => {
console.log(err);
});
};
Importing and calling the function
import { saveHandler } from './filename';
...
...
function saveFn() {}
saveHandler(saveFn, (data) => {
setState([...data]);
})

Related

I'm confused about the callback function in react js

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/

Pass a value to a react hook

Got a Component that is using a separate hook created but I'm unable to pass any value to that hook which I require in my use case.
const onEdit = (index: number) => useMediaLinkImage(img => {
setNodeImages(imgData => {
imgData[index] = {
...imgData[index],
image: {
...imgData[index].image,
image: img
}
}
return imgData
})
})
return --------------
{
nodeImages.map(({ image, mode }, index) => <Image
key={index}
mode={mode}
image={image}
onEmpty={onEmpty}
onChange={onValueChange}
onSubmit={onSubmit}
onToggle={onToggle}
onEdit={() => onEdit(index)}
onAdd={addImage}
/>)
}
unable to get use it as a function to get the index for the onEdit throws error
React Hook "useMediaLinkImage" is called in function "onEdit" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
It expects that onEdit is a component that should return a JSX element or null or undefined but you return useMediaLinkImage hook, because with this arrow function syntax (index: number) => useMediaLinkImage it's a shortcut for
(index: number) => {
return useMediaLinkImage((img) => {
// code ..
})
}
You just need to use brackets without return.
const onEdit = (index: number) => {
useMediaLinkImage(img => {
setNodeImages(imgData => {
imgData[index] = {
...imgData[index],
image: {
...imgData[index].image,
image: img
}
}
return imgData
})
})
}
You can just call hooks inside other hooks, or inside the main body of a React component.
In you case, you have to return the function you want to call from the hook itself:
function useMediaLinkImage(...) {
// set your custom state here
function update(index, ...) {
...
}
return {
... // return state variables from here if needed
update
};
}
Then in your component:
const { update } = useMediaLinkImage(...);
const onEdit = (index: number) => {
update(index, ...);
}

Fixing hook call outside of the body of a function component

I made a custom ReactJS hook to handle a couple of specific mouse events, as below:
const HealthcareServices = ({
filterToRemove,
filters,
onChange,
onClear,
selectedAmbulatoryCareFilterValue,
shouldClear,
}: Props): JSX.Element => {
const classes = useStyles();
...
useEffect(() => {
shouldClear && clearFilters();
}, [shouldClear]);
const useSingleAndDoubleClick = (actionSimpleClick: () => void, actionDoubleClick: () => void, delay = 250) => {
const [click, setClick] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (click === 1) actionSimpleClick();
setClick(0);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (click === 2) actionDoubleClick();
return () => clearTimeout(timer);
}, [click]);
return () => setClick((prev) => prev + 1);
};
const handleSelectedItem = (service: Filter) => {
service.selected = !service.selected;
setHealthcareServices([...healthcareServices]);
onChange(healthcareServices);
};
const handleSingleClick = (service: Filter) => {
console.log('single-click');
if (service.isRequired) {
service.checkedIcon = <Icons.CheckboxSingleClick />;
}
handleSelectedItem(service);
};
const handleDoubleClick = (service: Filter) => {
console.log('double-click');
if (service.isRequired) {
service.checkedIcon = <Icons.CheckboxDoubleClick />;
}
handleSelectedItem(service);
};
const handleClick = (service: Filter) =>
useSingleAndDoubleClick(
() => handleSingleClick(service),
() => handleDoubleClick(service)
);
...
return (
<div className={classes.filter_container}>
...
<div className={classes.filter_subgroup}>
{filters.map((filter) => (
<div key={`${filter.label}-${filter.value}`} className={classes.filter}>
<Checkbox
label={filter.label}
className={classes.checkbox}
checked={filter.selected}
onChange={() => handleClick(filter)}
checkedIcon={filter.checkedIcon}
/>
</div>
))}
</div>
...
</div>
);
};
When I click on my <Checkbox />, the whole thing crashes. The error is:
The top of my stacktrace points to useState inside my hook. If I move it outside, so the hook looks as:
const [click, setClick] = useState(0);
const useSingleAndDoubleClick = (actionSimpleClick: () => void, actionDoubleClick: () => void, delay = 250) => {
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (click === 1) actionSimpleClick();
setClick(0);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (click === 2) actionDoubleClick();
return () => clearTimeout(timer);
}, [click]);
return () => setClick((prev) => prev + 1);
};
The problem still happens, only the stacktrace points to the useEffect hook. The code is based on another answer here.
Any suggestions?
You've defined your useSingleAndDoubleClick hook inside of a component. That's not what you want to do. The idea of custom hooks is that you can move logic outside of your components that could otherwise only happen inside of them. This helps with code reuse.
There is no use for a hook being defined inside a function, as the magic of hooks is that they give you access to state variables and such things that are usually only allowed to be interacted with inside function components.
You either need to define your hook outside the component and call it inside the component, or remove the definition of useSingleAndDoubleClick and just do everything inside the component.
EDIT: One more note to help clarify: the rule that you've really broken here is that you've called other hooks (ie, useState, useEffect) inside your useSingleAndDoubleClick function. Even though it's called useSingleAndDoubleClick, it's not actually a hook, because it's not being created or called like a hook. Therefore, you are not allowed to call other hooks inside of it.
EDIT: I mentioned this earlier, but here's an example that could work of moving the hook definition outside the function:
EDIT: Also had to change where you call the hook: you can't call the hook in a nested function, but I don't think you need to.
const useSingleAndDoubleClick = (actionSimpleClick: () => void, actionDoubleClick: () => void, delay = 250) => {
const [click, setClick] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
// simple click
if (click === 1) actionSimpleClick();
setClick(0);
}, delay);
// the duration between this click and the previous one
// is less than the value of delay = double-click
if (click === 2) actionDoubleClick();
return () => clearTimeout(timer);
}, [click]);
return () => setClick((prev) => prev + 1);
};
const HealthcareServices = ({
filterToRemove,
filters,
onChange,
onClear,
selectedAmbulatoryCareFilterValue,
shouldClear,
}: Props): JSX.Element => {
const classes = useStyles();
...
useEffect(() => {
shouldClear && clearFilters();
}, [shouldClear]);
// your other handlers
// changed this - don't call the hook inside the function.
// your hook is returning the handler you want anyways, I think
const handleClick = useSingleAndDoubleClick(handleSingleClick, handleDoubleClick)

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

Why does the ordering of it functions matter in this jest test?

I have the following component...
export default class TextInput extends PureComponent<TextInputProps> {
private handleOnChange = (event: OnChangeEvent): void => {
if (!this.props.disabled && this.props.onChange) {
this.props.onChange(event)
}
}
private handleOnBlur = (event: OnBlurEvent): void => {
if (!this.props.disabled && this.props.onBlur) {
this.props.onBlur(event)
}
}
public render(): ReactNode {
return (
<Styled.TextInput
id={this.props.id}
type={this.props.type}
onChange={this.handleOnChange}
onBlur={this.handleOnBlur}
disabled={this.props.disabled}
/>
)
}
}
And am trying to test the handleOnChange function using the following test...
const mockOnChange = jest.fn((): void => { })
const mockOnBlur = jest.fn((): void => { })
const minHandlerProps ={
id: 'test',
type: 'text',
onChange: mockOnChange,
onBlur: mockOnBlur,
}
describe('handleOnChange', () => {
it('Should not call the onChange prop when it\'s been passed and TextInput is disabled', () => {
const wrapper = shallow(<TextInput {...minHandlerProps} disabled={true} />)
const instance = wrapper.instance()
instance.handleOnChange()
expect(minHandlerProps.onChange).not.toHaveBeenCalled()
})
it('Should call the onChange prop when it\'s been passed and TextInput is not disabled', () => {
const wrapper = shallow(<TextInput {...minHandlerProps} />)
const instance = wrapper.instance()
instance.handleOnChange()
expect(minHandlerProps.onChange).toHaveBeenCalled()
})
})
The tests pass when they are in this order, but if I swap the order around the should not call the onChange prop test fails.
Is this because in this instance, the onChange prop has already been called in the first it()?
Am I supposed to write a separate describe for this?
I've console logged that the props are passing correctly and it looks as though they are, so I'm at a loss as to this behaviour. Thanks anyone who can shed some light on it.
By defining the mock functions outside the describe block, you share a single instance of each between all tests. This means you see calls from other tests, so your test are order-dependent (a very bad thing for unit tests).
There are various solutions to this:
Create a new instance for each test, e.g. in a beforeEach:
describe('handleOnChange', () => {
let minHandlerProps;
let mockOnBlur;
let mockOnChange;
beforeEach(() => {
mockOnChange = jest.fn((): void => { })
mockOnBlur = jest.fn((): void => { })
minHandlerProps = {
id: 'test',
type: 'text',
onChange: mockOnChange,
onBlur: mockOnBlur,
}
});
...
});
Reset each one explicitly between tests with mockClear:
describe('handleOnChange', () => {
beforeEach(() => {
mockOnBlur.mockClear();
mockOnChange.mockClear();
});
...
});
Reset all mocks explicitly between tests with clearAllMocks:
describe('handleOnChange', () => {
beforeEach(() => {
jest.clearAllMocks();
});
...
});
Get Jest to reset all mocks between tests, by setting clearMocks to true in your configuration.
As a side note, I would not recommend testing that invoking handleOnChange on the instance invokes the mocked function prop. That's the implementation - the behaviour of the component is that the callback is invoked when the Styled.TextInput changes, or better yet when some interaction with that component occurs. Simulating these events leaves you less coupled to your current implementation.

Categories

Resources