How to export functions from a custom hook - javascript

The basic structure of my hook:
const useMyHook = () => {
const func1 = (): boolean => { ... }
const func2 = (type: "type1" | "type2"): boolean => { ... }
const func3 = (): boolean => { ... }
return {
func1, func2, func3
}
}
export default useMyHook
I'm currently using it like this, which works in dev, but breaks my build.
import useMyHook from "../hooks/useMyHook";
const { func1 } = useMyHook()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const isValid = func1();
if (isValid) {
// do more things
}
}
return (
<form>...</form>
)
When running yarn build, I am faced with this error:
Objects are not valid as a React child (found: object with keys {func1, func2, func3}). If you meant to render a collection of children, use an array instead.
My attempt at solving this was to export my custom hook functions in an array, as the error seemed to suggest.
return [
func1, func2, func3
]
And using it like this:
const [ func1 ] = useMyHook();
const isValid = func1();
TypeScript is not happy with this:
An argument for 'type' was not provided.
But this is confusing, because func1 does not accept a type argument as func2 does.
Questions
Is there some way I work around this build-time error and export the { } of functions? This feels like the cleanest approach.
If there is no way to achieve 1, how can I properly use the exported array of functions without my types being misread?

Related

React/JavaScript Refactor Functions to a Separate File Without Losing Scope

I have a React component with many chained functions for a specific task. Those functions need access to the component's states. I want to separate some of those functions into a new .js file to keep my component file clean, but doing so, the functions lose access to the states.
This is what I have (works fine):
// SomeComponent.js
import React, { useState, useEffect } from 'react';
export default function SomeComponent() {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
return(
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
)
}
One idea is to send the states and state-setters as parameters all the way down to func3, but there are like 10 chained functions in my actual code and that's too messy.
Another idea is to make a class in the new file and instantiate the class with the states and state-setters as attributes. Maybe a new React Component?
Is there a cleaner way I can define func1, func2, func3 in a separate file while keeping access to the states? Either sending the states and state-setters to the scope of NewFile.js somehow, or bringing the functions to the scope of my component. I just want them in the same scope.
My only problem with the functions is the amount of space that they take in my component file.
I want something like this:
// SomeComponent.js
import React, { useState, useEffect } from 'react';
import {func1, func2, func3} from './newFile';
export default function SomeComponent() {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
return(
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
)
}
// NewFile.js
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
export {func1, func2, func3}
In this sort of situation I sometimes like to use a custom hook to separate functions from the rendered JSX. Here, you could do:
export const useSomeComponent = () => {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
return { name, func3 }; // return only the values used externally
};
import { useSomeComponent } from './useSomeComponent';
export default function SomeComponent() {
const { name, func3 } = useSomeComponent();
// note that if you're returning more than one element, a fragment must surround it
return (<>
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
</>);
}
It makes a big difference when both the function definitions/hooks and the JSX is long, by focusing on only one thing at a time in a file - the layout/markup, or the logic.

React Native Arrow Syntax Explanation

I am reviewing some code and was unable to find a breakdown of this arrow function syntax. Could someone help explain what the parameters ({ match, onOpen }: MatchListItemProps) mean?
import React from 'react';
import { ListItem } from 'react-native-elements';
import { StyleSheet } from 'react-native';
type MatchListItemProps = {
match: User,
onOpen: Function
};
const styles = StyleSheet.create({});
const TestScreen = ({ match, onOpen }: MatchListItemProps) => {
const { name, image, message } = match;
return (....
Could someone help explain what the parameters ({ match, onOpen }: MatchListItemProps)mean?
This code is using typescript and destructuring. Let me get rid of both for a second, then add them back in. Here it is in pure javascript without destructuring:
const TestScreen = (props) => {
let match = props.match;
let onOpen = props.onOpen;
Now i'll add back in the typescript. A variable can be followed by a colon and then a type. This information is used to catch type errors at compile time.
const TestScreen = (props: MatchListItemProps) => {
let match = props.match;
let onOpen = props.onOpen;
And then adding in the destructuring. This is a shorthand to pluck values off an object and assign them to local variables:
const TestScreen = ({ match, onOpen }: MatchListItemProps) => {
Since the parameter is an object, you can deconstruct it inside the parameter.
For instance, take a look at this code
let person = {
name: 'Felipe',
age: '23'
}
You could take the values in this form
let name = person.name
let age = person.age
Or you could use a shortcut with destructuring assignment
let { name, age } = person
Finally, if the variable person inside a parameter, you can deconstruct it inside the very parameter
logPersonNameAndAge = ({ name, age }) => {
console.log(name)
console.log(age)
}
So that you could call it passing the entire object
logPersonNameAndAge(person)
Your code is TypeScript, not just JavaScript. : MatchListItemProps is a type annotation that's used by TypeScript, which is used to catch many common errors at compile time instead of at runtime. ({ match, onOpen }) => { ... is a destructuring, which means the function takes an object, and brings into scope variables called match and onOpen containing whatever was in the object with those names. It's roughly equivalent to obj => { let match = obj.match, onOpen = obj.onOpen; .... In turn, const TestScreen = obj => { ... is a lambda, which is roughly equivalent to function TestScreen(obj) { ....

Flow Array Generic is Incompatible with Itself?

I'm trying to create an es6 class with flow type checking:
/* #flow */
export default class ListHolder<Tv> {
getList = (): Array<Tv> => {
return [];
};
iterateOverList = (): mixed => {
return this.getList().map((item: mixed, index: number) => {
// no-op
return null;
});
};
}
The problem is that I keep getting a flow error that Tv [1] is incompatible with Tv [1]. referencing the line getList = (): Array<Tv> => {. Can someone help me understand this error?
It resolves if I comment out the map invocation, but I haven't been able to make much progress beyond that and the error messages aren't particularly helpful.
Thanks,
Looks like your creating a class, but assigning methods to a class is not done using assignment operator. = and arrow functions =>.
I think this is more what your after. ->
/* #flow */
export default class ListHolder<Tv> {
getList (): Array<Tv> {
return [];
};
iterateOverList (): mixed {
return this.getList().map((item: mixed, index: number) => {
// no-op
return null;
});
};
}

How to mock an exported const in jest

I have a file that relies on an exported const variable. This variable is set to true but if ever needed can be set to false manually to prevent some behavior if downstream services request it.
I am not sure how to mock a const variable in Jest so that I can change it's value for testing the true and false conditions.
Example:
//constants module
export const ENABLED = true;
//allowThrough module
import { ENABLED } from './constants';
export function allowThrough(data) {
return (data && ENABLED === true)
}
// jest test
import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';
describe('allowThrough', () => {
test('success', () => {
expect(ENABLED).toBE(true);
expect(allowThrough({value: 1})).toBe(true);
});
test('fail, ENABLED === false', () => {
//how do I override the value of ENABLED here?
expect(ENABLED).toBe(false) // won't work because enabled is a const
expect(allowThrough({value: 1})).toBe(true); //fails because ENABLED is still true
});
});
This example will work if you compile ES6 modules syntax into ES5, because in the end, all module exports belong to the same object, which can be modified.
import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';
import * as constants from './constants';
describe('allowThrough', () => {
test('success', () => {
constants.ENABLED = true;
expect(ENABLED).toBe(true);
expect(allowThrough({ value: 1 })).toBe(true);
});
test('fail, ENABLED === false', () => {
constants.ENABLED = false;
expect(ENABLED).toBe(false);
expect(allowThrough({ value: 1 })).toBe(false);
});
});
Alternatively, you can switch to raw commonjs require function, and do it like this with the help of jest.mock(...):
const mockTrue = { ENABLED: true };
const mockFalse = { ENABLED: false };
describe('allowThrough', () => {
beforeEach(() => {
jest.resetModules();
});
test('success', () => {
jest.mock('./constants', () => mockTrue)
const { ENABLED } = require('./constants');
const { allowThrough } = require('./allowThrough');
expect(ENABLED).toBe(true);
expect(allowThrough({ value: 1 })).toBe(true);
});
test('fail, ENABLED === false', () => {
jest.mock('./constants', () => mockFalse)
const { ENABLED } = require('./constants');
const { allowThrough } = require('./allowThrough');
expect(ENABLED).toBe(false);
expect(allowThrough({ value: 1 })).toBe(false);
});
});
Unfortunately none of the posted solutions worked for me or to be more precise some did work but threw linting, TypeScript or compilation errors, so I will post my solution that both works for me and is compliant with current coding standards:
// constants.ts
// configuration file with defined constant(s)
export const someConstantValue = true;
// module.ts
// this module uses the defined constants
import { someConstantValue } from './constants';
export const someCheck = () => someConstantValue ? 'true' : 'false';
// module.test.ts
// this is the test file for module.ts
import { someCheck } from './module';
// Jest specifies that the variable must start with `mock`
const mockSomeConstantValueGetter = jest.fn();
jest.mock('./constants', () => ({
get someConstantValue() {
return mockSomeConstantValueGetter();
},
}));
describe('someCheck', () => {
it('returns "true" if someConstantValue is true', () => {
mockSomeConstantValueGetter.mockReturnValue(true);
expect(someCheck()).toEqual('true');
});
it('returns "false" if someConstantValue is false', () => {
mockSomeConstantValueGetter.mockReturnValue(false);
expect(someCheck()).toEqual('false');
});
});
There is another way to do it in ES6+ and jest 22.1.0+ thanks to getters and spyOn.
By default, you cannot spy on primitive types like boolean or number. You can though replace an imported file with your own mock. A getter method still acts like a primitive member but allows us to spy on it. Having a spy on our target member you can basically do with it whatever you want, just like with a jest.fn() mock.
Below an example
// foo.js
export const foo = true; // could be expression as well
// subject.js
import { foo } from './foo'
export default () => foo
// subject.spec.js
import subject from './subject'
jest.mock('./foo', () => ({
get foo () {
return true // set some default value
}
}))
describe('subject', () => {
const mySpy = jest.spyOn(subject.default, 'foo', 'get')
it('foo returns true', () => {
expect(subject.foo).toBe(true)
})
it('foo returns false', () => {
mySpy.mockReturnValueOnce(false)
expect(subject.foo).toBe(false)
})
})
Read more in the docs.
Thanks to #Luke I was able to expand on his answer for my needs. I had the requirements of:
Only mocking certain values in the file - not all
Running the mock only inside a single test.
Turns out that doMock() is like mock() but doesn't get hoisted. In addition requireActual() can be used to grab original data.
My config.js file - I need to mock only part of it
export const SOMETHING = 'blah'
export const OTHER = 'meh'
My test file
// import { someFunc } from 'some/file' // This won't work with doMock - see below
describe('My test', () => {
test('someFunc() does stuff', async () => {
// Here I mock the config file which gets imported somewhere deep in my code
jest.doMock('config.js', () => {
// Grab original
const originalModule = jest.requireActual('config')
// Return original but override some values
return {
__esModule: true, // Depends on your setup
...originalModule,
SOMETHING: 'boom!'
}
})
// Because `doMock` doesn't get hoisted we need to import the function after
const { someFunc } = await import(
'some/file'
)
// Now someFunc will use the original config values but overridden with SOMETHING=boom!
const res = await someFunc()
})
})
Depending on other tests you may also need to use resetModules() somewhere such as beforeAll or afterAll.
Docs:
doMock
requireActual
resetModules
Since we can't override/mock the value directly. we can use the below hack
// foo.js
export const foo = true; // could be expression as well
// spec file
import * as constants from './foo'
Object.defineProperty(constant, 'foo', {value: 1})
For functions:
Object.defineProperty(store, 'doOneThing', {value: jest.fn()})
For me the simplest solution was to redefine the imported object property, as decribed here:
https://flutterq.com/how-to-mock-an-exported-const-in-jest/
// foo.js
export const foo = true; // could be expression as well
// spec file
import * as constants from './foo'
Object.defineProperty(constant, 'foo', {value: 1, writable: true})
Facing the same issue, I found this blog post very useful, and much simpler than #cyberwombat use case :
https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/
// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));
import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function
The most common scenario I needed was to mock a constant used by a class (in my case, a React component but it could be any ES6 class really).
#Luke's answer worked great for this, it just took a minute to wrap my head around it so I thought I'd rephrase it into a more explicit example.
The key is that your constants need to be in a separate file that you import, so that this import itself can be stubbed/mocked by jest.
The following worked perfectly for me.
First, define your constants:
// src/my-component/constants.js
const MY_CONSTANT = 100;
export { MY_CONSTANT };
Next, we have the class that actually uses the constants:
// src/my-component/index.jsx
import { MY_CONSTANT } from './constants';
// This could be any class (e.g. a React component)
class MyComponent {
constructor() {
// Use the constant inside this class
this.secret = MY_CONSTANT;
console.log(`Current value is ${this.secret}`);
}
}
export default MyComponent
Lastly, we have the tests. There's 2 use cases we want to handle here:
Mock the generate value of MY_CONSTANT for all tests inside this file
Allow the ability for a specific test to further override the value of MY_CONSTANT for that single test
The first part is acheived by using jest.mock at the top of your test file.
The second is acheived by using jest.spyOn to further spy on the exported list of constants. It's almost like a mock on top of a mock.
// test/components/my-component/index.js
import MyComponent from 'src/my-component';
import allConstants from 'src/my-component/constants';
jest.mock('src/my-component/constants', () => ({
get MY_CONSTANT () {
return 30;
}
}));
it('mocks the value of MY_CONSTANT', () => {
// Initialize the component, or in the case of React, render the component
new MyComponent();
// The above should cause the `console.log` line to print out the
// new mocked value of 30
});
it('mocks the value of MY_CONSTANT for this test,', () => {
// Set up the spy. You can then use any jest mocking method
// (e.g. `mockReturnValue()`) on it
const mySpy = jest.spyOn(allConstants, 'MY_CONSTANT', 'get')
mySpy.mockReturnValue(15);
new MyComponent();
// The above should cause the `console.log` line to print out the
// new mocked value of 15
});
One of the way for mock variables is the follow solution:
For example exists file ./constants.js with constants:
export const CONSTATN_1 = 'value 1';
export const CONSTATN_2 = 'value 2';
There is also a file of tests ./file-with-tests.spec.js in which you need to do mock variables.
If you need to mock several variables you need to use jest.requireActual to use the real values of the remaining variables.
jest.mock('./constants', () => ({
...jest.requireActual('./constants'),
CONSTATN_1: 'mock value 1',
}));
If you need to mock all variables using jest.requireActual is optional.
jest.mock('./constants', () => ({
CONSTATN_1: 'mock value 1',
CONSTATN_2: 'mock value 2'
}));
Instead of Jest and having trouble with hoisting etc. you can also just redefine your property using "Object.defineProperty"
It can easily be redefined for each test case.
This is a pseudo code example based on some files I have:
From localization file:
export const locale = 'en-US';
In another file we are using the locale:
import { locale } from 'src/common/localization';
import { format } from 'someDateLibrary';
// 'MMM' will be formatted based on locale
const dateFormat = 'dd-MMM-yyyy';
export const formatDate = (date: Number) => format(date, dateFormat, locale)
How to mock in a test file
import * as Localization from 'src/common/localization';
import { formatDate } from 'src/utils/dateUtils';
describe('format date', () => {
test('should be in Danish format', () => {
Object.defineProperty(Localization, 'locale', {
value: 'da-DK'
});
expect(formatDate(1589500800000)).toEqual('15-maj-2020');
});
test('should be in US format', () => {
Object.defineProperty(Localization, 'locale', {
value: 'en-US'
});
expect(formatDate(1589500800000)).toEqual('15-May-2020');
});
});
in typescript, you can not overwrite constant value but; you can overwrite the getter function for it.
const mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter = jest.fn();
jest.mock('lib/constants', () => ({
...jest.requireActual('lib/constants'),
get NEXT_PUBLIC_ENABLE_HCAPTCHA() {
return mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter();
},
}));
and in the test use as
beforeEach(() => {
mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter.mockReturnValue('true');
});
Thank you all for the answers.
In my case this was a lot simpler than all the suggestions here
// foo.ts
export const foo = { bar: "baz" };
// use-foo.ts
// this is just here for the example to have a function that consumes foo
import { foo } from "./foo";
export const getFoo = () => foo;
// foo.spec.ts
import "jest";
import { foo } from "./foo";
import { getFoo } from "./use-foo";
test("foo.bar should be 'other value'", () => {
const mockedFoo = foo as jest.Mocked<foo>;
mockedFoo.bar = "other value";
const { bar } = getFoo();
expect(bar).toBe("other value"); // success
expect(bar).toBe("baz"); // fail
};
Hope this helps someone.
../../../common/constant/file (constants file path)
export const Init = {
name: "",
basePath: "",
description: "",
thumbnail: "",
createdAt: "",
endDate: "",
earnings: 0,
isRecurring: false,
status: 0,
};
jest file
jest.mock('../../../common/constant/file',()=>({
get Init(){
return {isRecurring: true}
}
}))
it('showActionbutton testing',()=>{
const {result} = renderHook(() => useUnsubscribe())
expect(result.current.showActionButton).toBe(true)
})
index file
import {Init} from ../../../common/constant/file
const useUsubscribe(){
const showActionButton = Init.isRecurring
return showActionButton
}
I solved this by initializing constants from ContstantsFile.js in reducers. And placed it in redux store. As jest.mock was not able to mock the contstantsFile.js
constantsFile.js
-----------------
const MY_CONSTANTS = {
MY_CONSTANT1: "TEST",
MY_CONSTANT2: "BEST",
};
export defualt MY_CONSTANTS;
reducers/index.js
-----------------
import MY_CONST from "./constantsFile";
const initialState = {
...MY_CONST
}
export const AbcReducer = (state = initialState, action) => {.....}
ABC.jsx
------------
import { useSelector } from 'react-redux';
const ABC = () => {
const const1 = useSelector(state) => state. AbcReducer. MY_CONSTANT1:
const const2 = useSelector(state) => state. AbcReducer. MY_CONSTANT2:
.......
Now we can easily mock the store in test.jsx and provide the values to constant that we want.
Abc.text.jsx
-------------
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
describe('Abc mock constants in jest', () => {
const mockStore = configureMockStore([thunk]);
let store = mockStore({
AbcReducer: {
MY_CONSTANT1 ="MOCKTEST",
MY_CONSTANT2 = "MOCKBEST",
}
});
test('your test here', () => { .....
Now when the test runs it will always pick the constant value form mock store.

ES2015 function definition with implicit argument

I am trying to translate the following from ES2015 to vanilla javascript:
fileA.js
export const checkout = ({ dispatch }) => {
dispatch(types.CHECKOUT_REQUEST)
}
fileB.js
import checkout;
checkout();
So far (using https://babeljs.io/repl/) I have:
fileA2015.js:
module.exports = {
checkout: function (_ref) {
dispatch = _ref.dispatch;
dispatch(types.CHECKOUT_REQUEST)
}
};
But I cannot figure out what to pass to checkout in fileB to have access to the correct _ref. What is this and where does it come from?
export const checkout = ({ dispatch }) => {
dispatch(types.CHECKOUT_REQUEST)
}
In the above function you are destructuring an object, which is expected to have a dispatch property. Where in dispatch is a callback function.
when you pass in the data object as a parameter, because of { dispatch }, which destructures the object that is passed in, it is expected to have dispatch property.
var data = {
dispatch: (type) => {
.......
}
};
So when checkout method is referenced and invoked in fileB.js, then you will have to pass in the data object at this point.
fileB.js
var data = {
dispatch: (type) => {
.......
}
};
import checkout;
checkout(data);
Let's note a few things here:
export const checkout = ({ dispatch }) => {
dispatch(types.CHECKOUT_REQUEST)
}
In the code above, checkout() is a function that expects a single parameter -- specifically an object with the property dispatch. dispatch is also expected to be a function.
If you don't pass any parameters to checkout() (or an object that doesn't have a dispatch property), dispatch will be undefined.
Therefore you need to pass something like:
checkout({ dispatch: function(){} })

Categories