How to resolve dependency array warning in React when variable is conditional? - javascript

I am doing a fetch request in useEffect hook. It is how it looks like.
useEffect(() => {
// clear prev state
setUniqueValues([]);
setLoading(true);
// get unique values and set field values;
jwtAxios
.post(`${baseURL}/support/get_unique_values`, {
field: fieldName,
catalog_id: catalogID,
page,
category_values: props?.initialValues?.schema?.category,
})
.then((data) => {
setUniqueValues(data.data.unique_values);
setLoading(false);
setError(false);
})
.catch((err) => {
console.log(err);
setError(err);
});
}, [catalogID, fieldName, page]);
In the post request I want to send a value (category_values) if it is exists, if it comes with props. so I check with ?.
But if I don't put it in dependency array it complains:
warning React Hook useEffect has a missing dependency: 'props?.initialValues?.schema?.category'. Either include it or remove the dependency array react-hooks/exhaustive-deps
If I add it like [catalogID, fieldName, page,props?.initialValues?.schema?.category ]
It complains like :
Line 100:8: React Hook useEffect has a missing dependency: 'props.initialValues.schema.category'. Either include it or remove the dependency array react-hooks/exhaustive-deps
Line 100:37: React Hook useEffect has a complex expression in the dependency array. Extract it to a separate variable so it can be statically checked react-hooks/exhaustive-deps
If I don't put ?'s when props are not passed, it is collapsing because it doesn't find props.
How do I solve it? Thanks.

you can compute the category just before calling useEffect
const category = props?.initialValues?.schema?.category;
useEffect(() => {
setUniqueValues([]);
setLoading(true);
jwtAxios
.post(`${baseURL}/support/get_unique_values`, {
field: fieldName,
catalog_id: catalogID,
page,
category_values: category,
})
...
}, [catalogID, fieldName, page, category]);

Related

react useEffect async triggering eachother problem how solve you guys?

useEffect(()=>{
// console.log(`Ahanda items`,items)
setItems(JSON.parse(localStorage.getItem(`baskets`)) )
},[])
useEffect(()=>{
localStorage.setItem(`baskets`,`${JSON.stringify(items)}`)
},[items])
Hello i have classic async problem have.
problem is
there is basket for eccommerce shopping site basket simple one i want to
when site is refreshing if localstorage inside have any list of product item pull
and setItem
but the problem is the other items Useeffect works to. So if i add timeout and do like that :
useEffect(()=>{
// console.log(`Ahanda items`,items)
setItems(JSON.parse(localStorage.getItem(`baskets`)) )
},[])
useEffect(()=>{
setTimeout(() => {
localStorage.setItem(`baskets`,`${JSON.stringify(items)}`)
}, 200);
},[items])
but the problem is not solved because some times i can be use api's
here is other explanation https://www.youtube.com/watch?v=-mTvd1O-3nM
I might get the question wrong. But please correct me.
Are you looking for something like this ????
const[items, setItems] = useState([])
function handleFetchItems(){
if(localStorage.baskets){ //call this only if I have items
setItems(state =>[...state,...JSON.parse(localStorage.getItem(`baskets`))]
}
fetch(`url`).then((response) => { //async operation
setItems(state =>[...state,...response])
}).catch(() => {})
}
useEffect(useCallback(() => {handleFetchItems()},[]))
I already face This problem, and solve it by making a custom hook :
by the way, This custom hook was created by Kent creator of Remix , you can check it here : https://github.com/kentcdodds/react-hooks
usage:
const [name, setName] = useLocalStorageState({
defaultValue: 'default value',
key: 'name',
})
name is the value stored under the key 'name' in localStorage
setName is the setter of the new data in key 'name' in localStorage

useEffect/useCallback missing dependency warnings, but redux state does not let me fix it

I'm trying to clean up my warnings, but im facing those dependency warnings.
This is an example, but a lot of useEffect() is facing a similar problem.
Im trying to laod my page calling my fetch api inside useCallback (got samething inside useEffect), but the filter param there is actually a redux state
useEffect(() => {
if (checkValidRoute(env.activeSelector.menu, "indicacoes")) {
dispatch(
indicationsAction.getIndications(config.page, config.rowsPerPage, config.order, {
environmentId: env[router.query.ambiente].envId,
loginId: user.login?.id,
selectorID: env.activeSelector?.selectorID,
token: user.login.token,
details: false,
filter: {
status: config.status,
dateInit: dateFormat(beforeMonth),
dateEnd: dateFormat(today),
name: config.name,
indicatorName: config.indicatorName
}
})
)
} else {
router.push(`/${router.query.ambiente}`)
}
}, [env, config.status, config.order, dispatch, beforeMonth, config.indicatorName, config.name, config.page, config.rowsPerPage, router, today, user.login?.id, user.login.token])
Those filters has it value associated to an input, i do not want to re-fetch after change my config state, because i need to wait for the user fill all the filter fields, but i need to reload my page if my env change.
I thought about this solution, but it does not work
const filterParams = {
page: config.page,
rowsPerPage: config.rowsPerPage,
order: config.order,
details: false,
filter: {
status: config.status,
dateInit: dateFormat(beforeMonth),
dateEnd: dateFormat(today),
name: config.name,
indicatorName: config.indicatorName
}
}
const loadPage = useCallback(() => {
if (checkValidRoute(env.activeSelector.menu, "indicacoes")) {
dispatch(
indicationsAction.getIndications({
environmentId: env[router.query.ambiente].envId,
loginId: user.login?.id,
selectorID: env.activeSelector?.selectorID,
token: user.login.token,
}, filterParams)
)
} else {
router.push(`/${router.query.ambiente}`)
}
}, [dispatch, env, router, user.login?.id, user.login.token, filterParams])
useEffect(() => {
loadPage()
}, [loadPage])
Now I got the following warning:
The 'filterParams' object makes the dependencies of useCallback Hook (at line 112) change on every render. Move it inside the useCallback callback. Alternatively, wrap the initialization of 'filterParams' in its own useMemo() Hook.eslintreact-hooks/exhaustive-deps
if add filterParams to useMemo() dependencies samething will happend
// eslint-disable-next-line react-hooks/exhaustive-deps sounds not good ...
There's any solution for this ? I think that I have to change my form to useForm() to get the onChange values then after submit() i set my redux state... but i dont know yet
EDIT: In that case i did understand that we need differente states to control my input state and my request state, they cant be equals. If someone find another solution, i would appreciate (:
EDIT2: Solved by that way:
const [ filtersState ] = useState(
{
page: config.page,
rowsPerPage: config.rowsPerPage,
order: config.order,
data: {
environmentId: env[router.query.ambiente].envId,
loginId: user.login?.id,
selectorID: env.activeSelector?.selectorID,
token: user.login.token,
details: false,
filter: {
status: config.status,
dateInit: dateFormat(config.dateInit),
dateEnd: dateFormat(config.dateEnd),
name: config.name,
indicatorName: config.indicatorName
}
}
}
);
const handleLoadPage = useCallback(() => {
if (checkValidRoute(env.activeSelector.menu, "indicacoes")) {
dispatch(indicationsAction.getIndications({
...filtersState,
filters: {
...filtersState.filters,
selectorID: env.activeSelector?.selectorID,
}
}))
} else {
router.push(`/${router.query.ambiente}`)
}
}, [env.activeSelector, filtersState, dispatch, router]
)
useEffect(() => {
handleLoadPage()
}, [handleLoadPage])
Any other alternatives is appreciate
The thing here is, if you memoize something, it dependencies(if are in local scope) must be memoized too.
I recommend you read this amazing article about useMemo and useCallback hooks.
To solve your problem you need to wrap filterParams within useMemo hook. And if one of it dependencies are in local scope, for example the dateFormat function, you'll need to wrap it as well.

React Hooks: Handling Objects as dependency in useEffects

UPDATE: Yes for use case 1, if I extract search.value outside the useEffect and use it as a dependency it works.
But I have an updated Use case below
Use Case 2: I want to pass a searchHits Object to the server. The server in turn return it back to me with an updated value in response.
If I try using the searchHits Object I still get the infinite loop
state: {
visible: true,
loading: false,
search: {
value: “”,
searchHits: {....},
highlight: false,
}
}
let val = search.value
let hits = search.searchHits
useEffect( () => {
axios.post(`/search=${state.search.value}`, {hits: hits}).then( resp => {
…do something or ..do nothing
state.setState( prevState => {
return {
…prevState,
search: {... prevState.search, hits: resp.hit}
}
})
})
}, [val, hits])
Use Case 1: I want to search for a string and then highlight when I get results
e.g.
state: {
visible: true,
loading: false,
search: {
value: “”,
highlight: false,
}
}
useEffect( () => {
axios.get(`/search=${state.search.value}`).then( resp => {
…do something or ..do nothing
state.setState( prevState => {
return {
…prevState,
search: {... prevState.search, highlight: true}
}
})
})
}, [state.search])
In useEffect I make the API call using search.value.
eslint complains that there is a dependency on state.search , it does not recognize state.search.value. Even if you pass state.search.value it complains about state.search
Now if you pass state.search as dependecy it goes in an infinite loop because after the api call we are updating the highlights flag inside search.
Which will trigger another state update and a recursive loop.
One way to avoid this is to not have nested Objects in state or move the highlights flag outside search, but I am trying to not go that route give the sheer dependecies I have.
I would rather have an Object in state called search the way it is. Is there any way to better approach this.
If I want to keep my state Object as above how do I handle the infinite loop
Just a eslint stuff bug may be. You have retracted some code by saying //do something and have hidden he code. Are you sure that it doesn't have anything to do with search object?
Also, try to extract the variable out before useEffect().
const searchValue = state.search.value;
useEffect(()=>{// axios call here},[searchValue])
If your search value is an object, react does shallow comparison and it might not give desired result. Re-rendering on a set of object dependencies isn't ideal. Extract the variables.
React does shallow comparison of dependencies specified in useEffect
eg.,
const {searchParam1, searchParam2} = search.value;
useEffect(() => {
//logic goes here
}, [searchParam1, searchParam2]);
Additionally, you can add dev dependency for eslint-plugin-react-hooks, to identify common errors with hooks

Render React component using Firestore data

I'm trying to render my Guild component with data from Firestore. I put the data from Firestore into my state as an array, then when I call the component and try to render it, nothing shows. I want to believe I'm doing something very wrong here (haven't been working with React for very long), but I'm not getting any errors or warnings, so I'm not sure exactly what's happening.
Guilds.js
<Col>
<Card>
<CardBody>
<CardTitle className={this.props.guildFaction}>{this.props.guildName}</CardTitle>
<CardSubtitle>{this.props.guildServer}</CardSubtitle>
<CardText>{this.props.guildDesc}</CardText>
</CardBody>
</Card>
</Col>
Render function
renderCards() {
var guildComp = this.state.guilds.map(guild => {
console.log(guild)
return <Guilds
key={guild.id}
guildFaction={guild.guildFaction}
guildServer={guild.guildServer}
guildName={guild.guildName}
guildDesc={guild.guildDesc} />
})
return <CardDeck>{guildComp}</CardDeck>
}
Fetching Firestore Data
guildInfo() {
Fire.firestore().collection('guilds')
.get().then(snapshot => {
snapshot.forEach(doc => {
this.setState({
guilds: [{
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
}]
})
console.log(doc.data().guildName)
})
})
}
UPDATE: solved, fix is in the render function.
Well, you using state "guilds" but you update state "posts" or I miss something?
I see few things here:
your component is Guild.js, but you are rendering <Guilds />
You are setting state to posts, but using this.state.guilds to render the components
You are overriding that piece of state each time to the last object in the snapshot, with the way you are mapping the Firestore data
you are setting the ids in the list wrong using doc.id instead of doc.data().id
You aren't mapping guilds to render. guilds is an array of guild objects, so you should do something like guilds.map(guild => { return <Guild /> }
These are few things to fix, and then try to console.log(this.state.guilds) before rendering and see if you get the right data
I think your issue is that because setState is async, by the time it actually sets the state doc is no longer defined. Try creating the array first, then call setState outside of the loop ie:
guildInfo() {
Fire.firestore().collection('guilds')
.get().then(snapshot => {
let guilds = []
snapshot.forEach(doc => {
guilds.push({
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
});
})
this.setState({guilds});
})
}
Try to use a map function, and in the callback function of the setState, try to console log your state after the update:
guildInfo() {
Fire.firestore().collection('guilds')
.get()
.then(snapshot => {
const guilds = snapshot.map(doc => {
return {
id: doc.id,
guildDesc: doc.data().guildDesc,
guildFaction: doc.data().guildFaction,
guildName: doc.data().guildName,
guildRegion: doc.data().guildRegion,
guildServer: doc.data().guildServer
};
this.setState({guilds}, () => console.log(this.state))
})
})
})
}
If in the console log there's a little [i] symbol near your state, it means that the state is not ready, and therefore it's am async issue. Replacing the forEach with the map function may already help though.

jest.mock is not a function in react?

Could you please tell me how to test componentDidMount function using enzyme.I am fetching data from the server in componentDidMount which work perfectly.Now I want to test this function.
here is my code
https://codesandbox.io/s/oq7kwzrnj5
componentDidMount(){
axios
.get('https://*******/getlist')
.then(res => {
this.setState({
items : res.data.data
})
})
.catch(err => console.log(err))
}
I try like this
it("check ajax call", () => {
const componentDidMountSpy = jest.spyOn(List.prototype, 'componentDidMount');
const wrapper = shallow(<List />);
});
see updated code
https://codesandbox.io/s/oq7kwzrnj5
it("check ajax call", () => {
jest.mock('axios', () => {
const exampleArticles:any = {
data :{
data:['A','B','c']
}
}
return {
get: jest.fn(() => Promise.resolve(exampleArticles)),
};
});
expect(axios.get).toHaveBeenCalled();
});
error
You look like you're almost there. Just add the expect():
expect(componentDidMountSpy).toHaveBeenCalled();
If you need to check if it was called multiple times, you can use toHaveBeenCalledTimes(count).
Also, be sure to mockRestore() the mock at the end to make it unmocked for other tests.
List.prototype.componentDidMount.restore();
To mock axios (or any node_modules package), create a folder named __mocks__ in the same directory as node_modules, like:
--- project root
|-- node_modules
|-- __mocks__
Inside of there, make a file named <package_name>.js (so axios.js).
Inside of there, you'll create your mocked version.
If you just need to mock .get(), it can be as simple as:
export default { get: jest.fn() }
Then in your code, near the top (after imports), add:
import axios from 'axios';
jest.mock('axios');
In your test, add a call to axios.get.mockImplementation() to specify what it'll return:
axios.get.mockImplementation(() => Promise.resolve({ data: { data: [1, 2, 3] } });
This will then make axios.get() return whatever you gave it (in this case, a Promise that resolves to that object).
You can then do whatever tests you need to do.
Finally, end the test with:
axios.get.mockReset();
to reset it to it's default mocked implementation.

Categories