I have a function that will update my object value depending on the app name using useState hook. But I am not able to use the hook, it gives error invalid hook call.
Any other way to achieve the same will also work.
var myfun = (function() {
const APP_NAME= "sample app"
const [object, setObject] = useState({obj: 'value'})
if(APP_NAME =="sample app") {
setObject({obj:'new value'})
console.log('here')
}
return object
});
myfun();
Create a custom Hook instead of that Function:
const useMyFun = () => {
const [object, setObject] = useState({obj: 'value'});
const APP_NAME = 'sample name';
useEffect(() => {
if (APP_NAME === 'sample name') setObject({obj: APP_NAME});
}, [APP_NAME]);
return object;
}
// In the component you need it:
const SomeComponent = () => {
// will update if state changes in the useMyFun Hook;
const appname = useMyFun();
// rest of your component code
...
}
make sure you use functional components to use this.
NOTE: but this will only be useful if APP_NAME will ever change.
otherwhise you could just use a simple ternary operator:
const object = APP_NAME === 'sample name' ? {obj: APP_NAME} : {obj: 'value'}
Try to move the hook call outside the function like this and the invalid hook call error will disapear i think :
import React from "react";
import { useState } from "react";
function App() {
const [object, setObject] = useState({ obj: "value" });
const myfun = () => {
const APP_NAME = "sample app";
if (APP_NAME === "sample app") {
setObject({ obj: "new value" });
console.log("here");
}
return object;
};
myfun();
return (
....
);
}
export default App;
i hope this helps you .
If all the above answers don't work look for an infinite react hook loop (usually with useEffect);
Related
I wrote a demo here:
import React, { useRef, useEffect, useState } from "react";
import "./style.css";
export default function App() {
// let arrRef = [useRef(), useRef()];
let _data = [
{
title: A,
ref: null
},
{
title: B,
ref: null
}
];
const [data, setData] = useState(null);
useEffect(() => {
getDataFromServer();
}, []);
const getDataFromServer = () => {
//assume we get data from server
let dataFromServer = _data;
dataFromServer.forEach((e, i) => {
e.ref = useRef(null)
});
};
return (
<div>
{
//will trigger some function in child component by ref
data.map((e)=>(<div title={e.title} ref={e.ref}/>))
}
</div>
);
}
I need to preprocess after I got some data from server, to give them a ref property. the error says 'Hooks can only be called inside of the body of a function component' . so I checked the document, it says I can't use hooks inside a handle or useEffect. so is there a way to achieve what I need?
update:
I need to create component base on DB data, so when I create a component I need to give them a ref , I need trigger some function written in child component from their parent component and I use ref to achieve that. that is why I need to pass a ref to child component.
I'm using syncfusion react controls to add some functionality to my app. I want to call a method on the control in my functional component, but I haven't been able to get the ref set properly:
import React, {createRef, useEffect, useState} from "react";
import {AutoCompleteComponent} from "#syncfusion/ej2-react-dropdowns";
import "#syncfusion/ej2-base/styles/bootstrap.css";
import "#syncfusion/ej2-react-inputs/styles/bootstrap.css";
import "#syncfusion/ej2-react-dropdowns/styles/bootstrap.css";
const UserLookup = ({userSelected}) => {
const [searchString, setSearchString] = useState('');
const [items, setItems] = useState([]);
const helper = new QueryHelper();
let listObj = createRef();
const searchStringChanged = (args) => {
console.log(args.text);
if (args.text.length > 3) {
setSearchString(args.text);
}
}
const optionSelected = (event) => {
memberSelected(event.item.id);
}
useEffect(() => {
fetch('http://example.com/myendpoint')
.then(response.map((result) => {
listObj.current.showPopup(); // <-- this method should be called on the autocomplete component
return {
id: result.contactId,
label: result.firstName + ' ' + result.lastName
}
}))
.then(data => console.log(data));
}, [searchString]);
return (
<AutoCompleteComponent
id="user_search"
autofill={true}
dataSource={items}
fields={
{
value: 'label'
}
}
filtering={searchStringChanged}
select={optionSelected}
popupHeight="250px"
popupWidth="300px"
placeholder="Find a contact (optional)"
ref={listObj}
/>
);
};
export default UserLookup;
this always throws an error that Cannot read property 'showPopup' of null -- how do you set the ref for the instance of the AutoCompleteComponent so that you can call methods on it?
We can get the reference for the AutoComplete when it's rendered as a functional component with help of using useRef method instead of createRef method. Please find the modified sample from below.
Sample Link: https://codesandbox.io/s/throbbing-shadow-ddsmf
I have got code for usePrevious hook from somewhere on internet. The code for usePrevious looks like:
export const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
Now, I am learing testing react with jest and enzyme. So, I tried to test usePrevious and got some problems. Here is my test case:
import React from 'react';
import { render } from 'enzyme';
import { usePrevious } from './customHooks';
const Component = ({ children, value }) => children(usePrevious(value));
const setup = (value) => {
let returnVal = '';
render(
<Component value={value}>
{
(val) => {
returnVal = val;
return null;
}
}
</Component>,
);
return returnVal;
};
describe('usePrevious', () => {
it('returns something', () => {
const test1 = setup('test');
const test2 = setup(test1);
expect(test2).toBe('test');
});
});
When the test execution completes, I get this error:
Expected: 'test', Received: undefined
Can anyone please let me know why am I getting undefined and is this the correct way to test custom hoooks in react?
After suggestion from comments from #Dmitrii G, I have changed my code to re-render the component (Previously I was re-mounting the component).
Here is the updated code:
import React from 'react';
import PropTypes from 'prop-types';
import { shallow } from 'enzyme';
import { usePrevious } from './customHooks';
const Component = ({ value }) => {
const hookResult = usePrevious(value);
return (
<div>
<span>{hookResult}</span>
<span>{value}</span>
</div>
);
};
Component.propTypes = {
value: PropTypes.string,
};
Component.defaultProps = {
value: '',
};
describe('usePrevious', () => {
it('returns something', () => {
const wrapper = shallow(<Component value="test" />);
console.log('>>>>> first time', wrapper.find('div').childAt(1).text());
expect(wrapper.find('div').childAt(0).text()).toBe('');
// Test second render and effect
wrapper.setProps({ value: 'test2' });
console.log('>>>>> second time', wrapper.find('div').childAt(1).text());
expect(wrapper.find('div').childAt(0).text()).toBe('test');
});
});
But still I am getting the same error
Expected: "test", Received: ""
Tests Passes when settimeout is introduced:
import React from 'react';
import PropTypes from 'prop-types';
import { shallow } from 'enzyme';
import { usePrevious } from './customHooks';
const Component = ({ value }) => {
const hookResult = usePrevious(value);
return <span>{hookResult}</span>;
};
Component.propTypes = {
value: PropTypes.string,
};
Component.defaultProps = {
value: '',
};
describe('usePrevious', () => {
it('returns empty string when component is rendered first time', () => {
const wrapper = shallow(<Component value="test" />);
setTimeout(() => {
expect(wrapper.find('span').text()).toBe('');
}, 0);
});
it('returns previous value when component is re-rendered', () => {
const wrapper = shallow(<Component value="test" />);
wrapper.setProps({ value: 'test2' });
setTimeout(() => {
expect(wrapper.find('span').text()).toBe('test');
}, 0);
});
});
I am not a big fan of using settimeout, so I feel that probably i am doing some mistake. If anyone knows a solution that does not use settimeout, feel free to post here. Thank you.
Enzyme by the hood utilizes React's shallow renderer. And it has issue with running effects. Not sure if it's going to be fixed soon.
Workaround with setTimeout was a surprise to me, did not know it works. Unfortunately, it is not universal approach since you'd need to use that on any change that cases re-render. Really fragile.
As a solution you can use mount() instead.
Also you may mimic shallow rendering with mount() with mocking every nested component:
jest.mock("../MySomeComponent.jsx", () =>
(props) => <span {...props}></span>
);
I'm trying to create a custom hook and I have problems with an infinite loop.
There is the piece of code that implements the custom hook on my page:
const handleOnFinish = response => {
const {data} = response
setIsLoading(false)
setTableData(data)
setPage(page)
}
const handleOnInit = () => setIsLoading(true)
useEffectUseCaseTokenValidation({
onFinish: handleOnFinish,
onInit: handleOnInit,
params: {nameToFilter: nameFilter, page},
useCase: 'get_clients_use_case'
})
And this is my custom hook:
import {useContext, useEffect} from 'react'
import Context from '#s-ui/react-context'
const noop = () => {}
export function useEffectUseCaseTokenValidation({
onFinish = noop,
onInit = noop,
params = {},
useCase = ''
}) {
const {domain} = useContext(Context)
const config = domain.get('config')
useEffect(() => {
onInit()
domain
.get(useCase)
.execute(params)
.then(response => {
const {error} = response
if (error && error.message === 'INVALID_TOKEN') {
window.location.replace(config.get('LOGIN_PAGE_URL'))
}
onFinish(response)
})
}, [params]) // eslint-disable-line
}
With this, the useEffect is released again and again, instead of taking params into account. I add a console.log for params and is always receiving the same.
I was using this useCase correctly without the custom hook, so that is not the problem.
I want to use this custom hook to avoid to copy and paste the redirection on all UseEffects for all project pages.
Thank you!
The problem is the object ref, that means that you are passing {nameToFilter: nameFilter, page} as params but each time the components renders a new object ref is creating so, react compare both with the ===, so if you run this code in your console
var params1 = { name: 'mike', age: 29 };
var params2 = { name: 'mike', age: 29 };
console.log(params1 === params2); // it will console false
that's because object declaration are not the same event when its key/value pairs are the same.
So to avoid infinite loop into your hook, you should use useMemo to avoid that, so try this
import { useMemo } from 'react';
const params = useMemo(() => ({ nameToFilter: nameFilter, page }), [nameFilter, page])
useEffectUseCaseTokenValidation({
onFinish: handleOnFinish,
onInit: handleOnInit,
params: params,
useCase: 'get_clients_use_case'
})
useMemo will avoid recreating object reft in each render phase of your component
Please read the useMemo react official docs
Please read this post to know the differences between values VS references comparison
I have an issue with jest manual mocking after upgraing to 15.1.1 (react is 15.3.1)
When I set a mock result inside the test, when the mocked method is called the actual result is not the one expected, but the initial one when the variable was created.
It was working just fine before I upgared react and jest.
Here's my mock :
'use strict';
const psMock = jest.genMockFromModule('../ProcessService');
import clone from 'lodash/clone'
var _resultDeOuf = [];
function __setMockResult(result) {
_resultDeOuf = result;
}
psMock.getRelatedProcessesByGroupingId = jest.fn(() => {
return {
then: (callback) => callback(_resultDeOuf);
}
});
psMock.__setMockResult = __setMockResult;
export default psMock`
Here's my test :
jest.unmock('../SuperProcessRow');
jest.unmock('../ProcessRow');
import React from "react";
import ReactDom from "react-dom";
import TestUtils from "react-addons-test-utils";
import processService from 'ProcessService'
import SuperProcessRow from '../SuperProcessRow'
const defaultSuperProcess = {
"processId": "97816",
"executionId": null,
"cancelExecutionId": null
}
describe('SuperProcessRow', () => {
beforeEach(() => {
processService.getRelatedProcessesByGroupingId.mockClear()
});
it('load sub processes on super process click ', () => {
let responseSubProcesses = {
processes : subProcesses,
totalCount : 5
};
processService.__setMockResult(responseSubProcesses);
let superProcessRow = TestUtils.renderIntoDocument(
<table><SuperProcessRow process={defaultSuperProcess}/></table>);
superProcessRow = ReactDom.findDOMNode(superProcessRow);
let icon = superProcessRow.querySelector('i');
TestUtils.Simulate.click(icon);
expect(processService.getRelatedProcessesByGroupingId.mock.calls.length).toEqual(1);
})
});
And In the actual production code, I have a call to getRelatedProcessGroupingId and I handle the response inside a .then method. And instead of retrieving data set on the test, I got the intial value: [].
does someone have an idea ?
Thank you
Vincent
I fixed it by setting _resultDeOuf inside the window object. It's ugly but it works