I am trying to move my if-statement [line 30] located in useFetchMovieGenreResults [inside the hooks folder] from outside my my useEffect to inside the useEffect. However when I do this, I am not receiving my expected output (an object), rather I receive the error below.
However, when I move my if-Statement outside of my useEffect I get my expected output after 2 renders.
So my question is,
why is it when I move my If-Statement inside my useEffect, I do'nt
get back the console.log results but I would get them back when I would move my if-Statement.
How do I reduce the number of renders I need to get my my result to
one render?
I have linked to the code sandbox of the problem below so that you could get a bigger picture of the problem and see the app in its entirety. However below that is an example of what I have compared to what I am looking for.
https://codesandbox.io/s/billowing-frog-hr4nwd
What I have and is working. Notice the If-Statement outside the useEffect
import useFetchNavBarCatagories from "./useFetchNavBarCatagories";
import useSelectedGenreInfoExtractor from "./useSelectedGenreInfoExtractor";
import { useEffect, useState } from "react";
export default function useFetchMovieGenreResults(genre) {
const [mymoviegenreinfo, setMymoviegenreinfo] = useState();
const [mymoviegenreinfofinal, setMymoviegenreinfofinal] = useState();
const mymoviegenreobjects = useFetchNavBarCatagories();
console.log("Checking if its reading", genre);
useEffect(() => {
setMymoviegenreinfo(mymoviegenreobjects);
}, [mymoviegenreobjects]);
if (mymoviegenreinfo?.genres?.length > 0) {
//! The bottom maps the key's values {genre[]}. Basically removing the outter object
var mappedvalues = Object.keys(mymoviegenreobjects).map(
(e) => mymoviegenreobjects[e]
);
console.log("The latest testinggggggggggggggggggg", mappedvalues);
const flatarrayofvalues = mappedvalues.flat();
console.log("Finally flat", flatarrayofvalues);
const filtteredarray = flatarrayofvalues.filter(
(character) => character.name === genre
);
console.log("This is avangers two ID", filtteredarray[0].id);
console.log("just a check", mymoviegenreinfo);
console.log(
"a check for lengthhhhhhhhhhhhhhhhhh",
mymoviegenreinfo?.genres?.length
);
return mymoviegenreinfo;
}
}
What I am looking for.
Notice the if-Statement is inside the useEffect. However when I do it like this, my if-statement never runs because none of my judging by the fact that my console.logs don't run even when my conditions for the if-statement are fulfilled.
import useFetchNavBarCatagories from "./useFetchNavBarCatagories";
import useSelectedGenreInfoExtractor from "./useSelectedGenreInfoExtractor";
import { useEffect, useState } from "react";
export default function useFetchMovieGenreResults(genre) {
const [mymoviegenreinfo, setMymoviegenreinfo] = useState();
const [mymoviegenreinfofinal, setMymoviegenreinfofinal] = useState();
const mymoviegenreobjects = useFetchNavBarCatagories();
console.log("Checking if its reading", genre);
useEffect(() => {
setMymoviegenreinfo(
mymoviegenreobjects,
console.log("This is my infoooooo from the setstae", mymoviegenreinfo)
);
if (mymoviegenreinfo?.genres?.length > 0) {
//! The bottom maps the key's values {genre[]}. Basically removing the outter object
var mappedvalues = Object.keys(mymoviegenreobjects).map(
(e) => mymoviegenreobjects[e]
);
console.log("I said HELLLLO0000");
console.log("The latest testinggggggggggggggggggg", mappedvalues);
const flatarrayofvalues = mappedvalues.flat();
console.log("Finally flat", flatarrayofvalues);
const filtteredarray = flatarrayofvalues.filter(
(character) => character.name === genre
);
console.log("This is avangers two ID", filtteredarray[0].id);
console.log("just a check", mymoviegenreinfo);
console.log(
"a check for lengthhhhhhhhhhhhhhhhhh",
mymoviegenreinfo?.genres?.length
);
setMymoviegenreinfofinal(filtteredarray);
console.log(filtteredarray);
}
}, [mymoviegenreobjects]);
return mymoviegenreinfo;
}
I think you are putting if statement wrong place you can have multiple useEffect() in react so you can put that code another one or inside useEffect() after setMymoviegenreinfo(mymoviegenreobjects);
inside the fat arrow function body.
Instead of using a separate state for a computed object, use useMemo. Don't use state in the same function you assign using set, as it will cause an unresolved loop.
const mymoviegenreinfofinal = useMemo(() => {
if (mymoviegenreinfo?.genres?.length > 0) {
var mappedvalues = Object.keys(mymoviegenreinfo).map(
(e) => mymoviegenreinfo[e]
);
const flatarrayofvalues = mappedvalues.flat();
const filtteredarray = flatarrayofvalues.filter(
(character) => character.name === genre
);
return filtteredarray;
}, [mymoviegenreinfo])
Related
I have react component which needs to consume a customized react hook from within the component.
However, this hook should only be called when a feature toggle is enabled. I understand this is sort of anti-pattern as it is against the rule of hooks here: https://reactjs.org/docs/hooks-rules.html
So my component file is roughly in this structure:
const someFeatureToggle = useSomeFeatureToggleHook(React);
const callBackMethod = ()=>{
// doing the logic
}
const someRef1 = React.useRef();
const someOtherRef = React.useRef();
...
There are lots of useState( ) here
return (
JSX
)
For the customized hook:
export default function myCustomizedHook(topics, messagesReceivedFn, subscriptionOptions = {}) {
if (!isValidTopics(topics)) {
throw new Error(`Topics arg is invalid - Arg ${JSON.stringify(topics)}`);
}
const [someSubTopics] = useState([topics].flat());
const context = useContext(SomeEventContext);
if (isUndefined(context)) {
throw new Error(`${customizedHook.name} must be used within SomeProvider`);
}
const { connectionStatus, connector } = context;
const isConnectorConnected = connector?.connected ?? false;
const isConnectorReconnecting = connector?.reconnecting ?? false;
const messageReceivedHandler = useCallback(
(receivedTopic, message) => {
if (subscribedTopics.some((topic) => matches(topic, receivedTopic))) {
messagesReceivedFn?.(receivedTopic, message);
}
},
[messagesReceivedFn, subscribedTopics]
);
useEffect(() => {
isConnectorConnected && connector?.on(CLIENT_EVENTS.MESSAGE, messageReceivedHandler);
return () => {
connector?.off(CLIENT_EVENTS.MESSAGE, messageReceivedHandler);
};
}, [messageReceivedHandler, connector, isConnectorConnected]);
useDeepCompareEffect(() => {
isConnectorConnected && connector.subscribe(subscribedTopics, subscriptionOptions);
return () => {
subscribedTopics && connector?.unsubscribe(subscribedTopics);
};
}, [connector, isConnectorConnected, subscribedTopics, subscriptionOptions]);
return { isConnected: isConnectorConnected, isReconnecting: isConnectorReconnecting, connectionStatus, subscribedTopics };
Now the error trace is like this:
Uncaught Error: Should have a queue. This is likely a bug in React. Please file an issue.
at updateReducer (react-dom.development.js:15255:15)
at updateState (react-dom.development.js:15671:12)
at Object.useState (react-dom.development.js:16472:18)
at useState (react.development.js:1533:23)
at customizedHook (customizedHook.js:28:38)
at componentThatConsumeHook (componentThatConsumeHook.js:67:99)
at renderWithHooks (react-dom.development.js:15015:20)
at updateFunctionComponent (react-dom.development.js:17386:22)
at beginWork (react-dom.development.js:19093:18)
at HTMLUnknownElement.callCallback (react-dom.development.js:3942:16)
and there is this warning from dev console in the browser:
Warning: React has detected a change in the order of Hooks called by myComponent. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
Previous render Next render
------------------------------------------------------
1. useRef useRef
2. useState useState
3. useEffect useEffect
4. useRef useState
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
line 28 of customizedHook is point to this line below:
const [someSubTopics] = useState([topics].flat());
This is how I consume the hook in the component:
const result = (!!featureToggles.Flag) && customHook(arg1, callbackMethod);
I am scratching my head here as I have no clue why this is happening, any thought or advice would be greatly appreciated !!!!
That's because you're calling hook inside a condition.
const result = (!!featureToggles.Flag) && customHook(arg1, callbackMethod);
is basically the same as
let result = false
if (!!featureToggles.Flag){
result = customHook(arg1, callbackMethod);
}
From React document:
Don’t call Hooks inside loops, conditions, or nested functions.
What you could do is to pass featureToggles into your custom hook and do the check inside that instead.
Make sure your condition check is at the bottom of your hook, below every hook call.
// pass featureToggles here
export default function myCustomizedHook(featureToggles, topics, messagesReceivedFn, subscriptionOptions = {}) {
// your code here
useDeepCompareEffect(() => {
isConnectorConnected && connector.subscribe(subscribedTopics, subscriptionOptions);
return () => {
subscribedTopics && connector?.unsubscribe(subscribedTopics);
};
}, [connector, isConnectorConnected, subscribedTopics, subscriptionOptions]);
// make sure to do the check at the bottom, below every hook call
if (!!featureToggles.Flag) {
// handle this feature toggle
return null
}
return { isConnected: isConnectorConnected, isReconnecting: isConnectorReconnecting, connectionStatus, subscribedTopics };
I have a working rest service that I want to invoke in react.
The code does display the list of countries.
import {React, useEffect, useState } from 'react'
export const Noc = () => {
const [nocs, setNoc] = useState();
useEffect(
() => {
const fetchNoc = async () => {
const response = await fetch('http://localhost:8080/countrydefinitions');
const data = await response.json();
setNoc(data);
};
fetchNoc();
},[]
);
return <div>
<div>NOC LIST</div>
{nocs.map(noc => <div>{noc.region}</div>)}
</div>
}
But most of the times I get this error
TypeError: Cannot read property 'map' of undefined
sometimes it prints the list soemetimes it does'nt. Is there some sort of a delay or wait that I need to introduce?
How can I introduce a delay or make sure that setnoc has been called and nocs has a value before printing it.
React will rerender when the props change. Indeed at the beginning its nothing yet.
2 things to make it more solid can be done.
Add a default value, so that map is available on the array.
const [nocs, setNoc] = useState([]);
And/Or wait until noc is not undefined anymore, and validating that It has a map function, before trying to use map.
{nocs && typeof nocs.map === 'function' && nocs.map(noc => <div>{noc.region}</div>)}
No. there is no need to introduce any delay/wait. That is already handled by the async/await syntax.
You can either set an initial value in your useState or early return a custom message if nocs is undefined.
If you are fetching from your own api, you can return a response with an error if the fetch request should fail. And then at client side, you can handle that error by wrapping your fetch call inside a try-catch block
import {useEffect, useState } from 'react'
export const Noc = () => {
const [nocs, setNoc] = useState();
useEffect(
() => {
const fetchNoc = async () => {
const response = await fetch('http://localhost:8080/countrydefinitions');
const data = await response.json();
setNoc(data);
};
try{
fetch()
}catch(error){
//handle error here
console.log(error);
}
},[]
);
if(!nocs){
return (<p>No NOC found</p>)
}
return <div>
<div>NOC LIST</div>
{nocs.map(noc => <div>{noc.region}</div>)}
</div>
}
at first render, nocs has no data then useEffect runs and the second render, nocs will have data.
you need to check if nocs is undefined
{nocs && nocs.length > 0 && nocs.map(noc => <div>{noc.region}</div>)}
When I try to execute the following react code, the axios.get() executed multiple times.
I have attached the screenshot of the log. Console Logs.
Can anyone please help me regarding this.
const CaskList = () =>{
const [casklist,getCaskList] = useState('');
const [searchCaskName, getCaskForSearch] = useState('');
const [searchResultCaskName, setSearchResultCaskName] = useState('');
const getCaskForSearchFromInput = (event) =>{
console.log(event.target.value);
getCaskForSearch(event.target.value);
};
useEffect(()=>{
const func = async() =>{
const resultCasks = await axios.get('http://localhost:3001/getAllApps');
const actualData = resultCasks.data;
console.log("**********************" + actualData);
getCaskList(actualData);
}
func();
})
const caskToBeRendered = [];
for(let i=0;i<casklist.length;i++){
caskToBeRendered.push(<Cask allCasks={casklist[i]} >);
};
const options = {
includeScore: false,
findAllMatches : true,
threshold : 0.3
};
const fuse = new Fuse(casklist,options);
const result = fuse.search(searchCaskName);
setSearchResultCaskName(result);
return (
<div>
{caskToBeRendered}
</div>
);
}
you need to pass a second argument to hook useEffect. You can read about that
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
useEffect(()=>{
const func = async() =>{
const resultCasks = await axios.get('http://localhost:3001/getAllApps');
const actualData = resultCasks.data;
getCaskList(actualData);
}
func();
},[])
You need to add a empty dependency array.
If you want to fire useEffect once on initial mount only. Like
useEffect(() => {
//your code goes here
}, []);
If you want useEffect to fire on initial mount and every re-render, you don't pass any dependency array. Like
useEffect(() => {
//your code goes here
});
I have a useEffect() that checks a trigger boolean in state if there is a message sound that should play, and after playing it sets that active message trigger to false in state.
However, the useEffect() goes into an infinite loop crashing the app. Probably because changing the state triggers it again (and again...)
Usually, with useState, this is fairly simple to fix with something like useEffect(() => {logic}, [trigger])
In my case I am not using useState, but I am using a reducer to change state.
Edit: The weird thing is, the reducer sometimes works to modify state, and sometimes it does not. It will execute without errors but the state remains unchanged.
Let me show you my commented code:
import React, { useEffect } from "react";
import { getCachedImage } from "../helpers";
const MessageNotification = (props) => {
const messageImg= getCachedImage("/ui/newmessage.png");
// Function that plays given sound
function playSound(soundFile) {
let audio = new Audio("/audio/messages/" + soundFile);
audio.play();
}
// Check if a Message is set to active. If so, execute logic
useEffect(() => {
// Get messages from state and find the message with "active" set to true
const messagesState = props.state.messages;
const activeMessage = messagesState.find((element) => element.active === true);
if (activeMessage) {
playSound(activeMessage.audio);
// Mark the message as userNotified and set it to inactive in state
let updatedMessagesState = messagesState ;
let index = messagesState.indexOf(activeMessage);
if (~index) {
updatedMessagesState[index].userNotified= true;
updatedMessagesState[index].active = false;
}
/* This is the weird part, the updatedMessagesState is correct,
but the dispatch reducer does not pass it to state.
This does work when I remove the useEffect
(but that gives me a fat red warning in console) */
props.dispatch({ type: "UPDATE_MESSAGES", payload: updatedMessagesState });
}
});
return (
<div>
<img src={"images" + messageImg} alt="message" width="90" height="90"></img>
</div>
);
};
export default MessageNotification;
As you can see, I do not use useState but work with a reducer instead. The solution that I often find which pertains to something like the following is not my solution as far as I can tell:
// Not applicable solution for me, since I use reducer
const [trigger] = useState();
useEffect(() => {
// Logic here
}, [trigger]);
Edit: Since the reducer does not seem to modify state when used in useEffect, let me post its code:
const reducer = (state, action) => {
switch (action.type) {
case "UPDATE_MESSAGES":
return { ...state, messages: action.payload };
default:
throw new Error();
}
};
export default reducer;
Try adding a dependency for your useEffect, such as:
useEffect(() => {
if (activeMessage) {
playSound(activeMessage.audio);
//mark the message as userNotified and set it to inactive in state
let updatedMessagesState = messagesState ;
let index = messagesState.indexOf(activeMessage);
if (~index) {
updatedMessagesState[index].userNotified= true;
updatedMessagesState[index].active = false;
}
props.dispatch({ type: "UPDATE_MESSAGES", payload: updatedMessagesState });
}
}, [activeMessage]);
By not specifying a dependency array, your useEffect will run on EVERY render, hence creating an infinite loop.
Also, you are trying to directly modify a prop (and it is an anti pattern) on this line:
const messagesState = props.state.messages;
Try changing it to this:
const messagesState = [...props.state.messages];
Also, let index = messagesState.indexOf(activeMessage); will not work since messagesState is an object array. To get the index of the active message, try this:
let index = messagesState.map(message => message.active).indexOf(true);
I think if you add props.state.messages as dependency, the problem will fixed. Also if you use only the messagesState and messagesState in useEffect, you should move this variables to that block:
useEffect(() => {
const messagesState = props.state.messages;
const messagesState = messagesState.find((element) => element.active === true);
if (activeMessage) {
playSound(activeMessage.audio);
//mark the message as userNotified and set it to inactive in state
let updatedMessagesState = messagesState ;
let index = messagesState.indexOf(activeMessage);
if (~index) {
updatedMessagesState[index].userNotified= true;
updatedMessagesState[index].active = false;
}
/* This is the weird part, the updatedMessagesState is correct,
but the dispatch reducer does not pass it to state.
This does work when I remove the useEffect
(but that gives me a fat red warning in console) */
props.dispatch({ type: "UPDATE_MESSAGES", payload: updatedMessagesState });
}
}, [props.state.messages]);
// Check if a Message is set to active. If so, execute logic
useEffect(() => {
// Get messages from state and find the message with "active" set to true
const messagesState = props.state.messages;
const activeMessage = messagesState.find((element) => element.active === true);
if (activeMessage) {
playSound(activeMessage.audio);
// Mark the message as userNotified and set it to inactive in state
let updatedMessagesState = messagesState ;
let index = messagesState.indexOf(activeMessage);
if (~index) {
updatedMessagesState[index].userNotified= true;
updatedMessagesState[index].active = false;
}
/* This is the weird part, the updatedMessagesState is correct,
but the dispatch reducer does not pass it to state.
This does work when I remove the useEffect
(but that gives me a fat red warning in console) */
props.dispatch({ type: "UPDATE_MESSAGES", payload: updatedMessagesState });
}
});
your useEffect needs a dependency, if you are not providing dependency in useEffect like in your case it'll always run on every render. Provide [] as second argument in your useEffect or [any state or prop on which this effect depends].
I've been loving getting into hooks and dealing with all the new fun issues that come up with real-world problems :) Here's one I've run into a couple of times and would love to see how you "should" solve it!
Overview: I have created a custom hook to capsulate some of the business logic of my app and to store some of my state. I use that custom hook inside a component and fire off an event on load.
The issue is: my hook's loadItems function requires access to my items to grab the ID of the last item. Adding items to my dependency array causes an infinite loop. Here's a (simplified) example:
Simple ItemList Component
//
// Simple functional component
//
import React, { useEffect } from 'react'
import useItems from '/path/to/custom/hooks/useItems'
const ItemList = () => {
const { items, loadItems } = useItems()
// On load, use our custom hook to fire off an API call
// NOTE: This is where the problem lies. Since in our hook (below)
// we rely on `items` to set some params for our API, when items changes
// `loadItems` will also change, firing off this `useEffect` call again.. and again :)
useEffect(() => {
loadItems()
}, [loadItems])
return (
<ul>
{items.map(item => <li>{item.text}</li>)}
</ul>
)
}
export default ItemList
Custom useItems Hook
//
// Simple custom hook
//
import { useState, useCallback } from 'react'
const useItems = () => {
const [items, setItems] = useState([])
// NOTE: Part two of where the problem comes into play. Since I'm using `items`
// to grab the last item's id, I need to supply that as a dependency to the `loadItems`
// call per linting (and React docs) instructions. But of course, I'm setting items in
// this... so every time this is run it will also update.
const loadItems = useCallback(() => {
// Grab our last item
const lastItem = items[items.length - 1]
// Supply that item's id to our API so we can paginate
const params = {
itemsAfter: lastItem ? lastItem.id : nil
}
// Now hit our API and update our items
return Api.fetchItems(params).then(response => setItems(response.data))
}, [items])
return { items, loadItems }
}
export default useItems
The comments inside the code should point out the problem, but the only solution I can come up with right now to make linters happy is to supply params TO the loadItems call (ex. loadItems({ itemsAfter: ... })) which, since the data is already in this custom hook, I am really hoping to not have to do everywhere I use the loadItems function.
Any help is greatly appreciated!
Mike
If you plan to run an effect just once, omit all dependencies:
useEffect(() => {
loadItems();
}, []);
You could try with useReducer, pass the dispatch as loadItems as it never changes reference. The reducer only cares if the action is NONE because that is what the cleanup function of useEffect does to clean up.
If action is not NONE then state will be set to last item of items, that will trigger useEffect to fetch using your api and when that resolves it'll use setItems to set the items.
const NONE = {};
const useItems = () => {
const [items, setItems] = useState([]);
const [lastItem, dispatch] = useReducer(
(state, action) => {
return action === NONE
? NONE
: items[items.length - 1];
},
NONE
);
useEffect(() => {
//initial useEffect or after cleanup, do nothing
if (lastItem === NONE) {
return;
}
const params = {
itemsAfter: lastItem ? lastItem.id : Nil,
};
// Now hit our API and update our items
Api.fetchItems(params).then(response =>
setItems(response)
);
return () => dispatch(NONE); //clean up
}, [lastItem]);
//return dispatch as load items, it'll set lastItem and trigger
// the useEffect
return { items, loadItems: dispatch };
};