React-query doesn't refresh the component - reactjs

I've got a problem with react-query. I've got a component which includes components that show a loading spinner if the integrationDetails query is fetching. But when the query finishes fetching, my component doesn't update at all. I tried creating state and then using that, but the problem persits.
UPDATE: The component does in fact update, but the child component doesn't.
...
const currentWorkspace = useGetCurrentWorkspaceDetail();
const integrationDetails = useGetIntegrationDetails();
useEffect(() => {
integrationDetails.refetch(); //force refetch the query whenever I enter the page.
//problem persists even if I comment this part out
}, []);
useEffect(() => {
console.log(integrationDetails.isFetching); //this logs true and then false.
}, [integrationDetails]);
if (currentWorkspace.isLoading) {
return (
<div className={styles.layout}>
<LoadingSpinner />
</div>
);
}
return (
<div className={styles.layout}>
<div className={styles.rest}>
<div className={styles.column}>
<Card>
<CardTabs tabs={["1"]}>
<div>
<h3>Global Tracking Code</h3>
<div style={{ marginTop: 12 }}>
<StatusStripe
isLoading={integrationDetails.isFetching}
...
/>
</div>
...

Your first useEffect would be just like componentDidMount and it will only be called on first render as you have passed an empty list i.e [] in its second argument.
You could have written your logic in your second useEffect where you know when the data is fetched and you can do your state setting or binding there.

Related

How can I render one component conditionally twice and not lose internal states/unmounts?

I have one component which needs to be rendered conditionally. Renders the same component with different styles. So, I did like this
import ComponentToRender from '../../ComponentToRender'
const Main =()=> {
const [expand,setExpand] =useState(false)
return (
<div>
{!expand && <ComponentToRender {...someProps} />}
{expand && <div>
<ComponentToRender {...otherProps} />
</div>
}
<button onClick={()=>setExpand(pre => !pre)}>Expand</button>
</div>
)
}
For the above code, I get what I want in terms of UI. But, all the internal states are lost. I must render two components like that and keep the internal states. Is that possible to do that in React?
You can achieve this by keeping the component rendered unconditionally and hiding it with CSS.
You get to preserve Component‘s state for free along with the DOM state (scroll, focus, and input position). However, this solution has drawbacks, too:
You mount the component on startup, even if the user never accesses it.
You update the component even when it’s invisible.
import ComponentToRender from "../../ComponentToRender";
const Main = () => {
const [expand, setExpand] = useState(false);
return (
<div>
<div style={{ display: expand ? null : "none" }}>
<ComponentToRender {...someProps} />
</div>
<div style={{ display: !expand ? null : "none" }}>
<div>
<ComponentToRender {...otherProps} />
</div>
</div>{" "}
<button onClick={() => setExpand((pre) => !pre)}>Expand</button>
</div>
);
};
The reconciliation algorithm is such that when on next render you move from one component to component of different type (assuming they have same spot in component hierarchy), instance of old component is destroyed.
Since you have <ComponentToRender/> and another one is <div><ComponentToRender/></div>, they are different components (because one is inside a div).
Read about reconciliation.
What you can do is move the state of ComponentToRender to Main and pass it as props. Now even if the component unmounts the state will not be lost.

React Typescript useState Hook doesn´t Update by using "setState" method

I am coding a React application and have a problem with the state management.
I added a State "headlineState" which is a boolen which is by default false.
Also i have a Callback Method which updates this state.
When the Callback value gets executed, my state doesnt Change.
I implemented a useEffect hool which displays the changes of my state in an "window.alert", and in this hook i see that my state is changing.
But after that, my state returns directly back to false.
function App() {
const [headlineState, setHeadlineState] = useState<boolean>(false);
useEffect(() => {
window.alert("HS changed" + headlineState)
}, [headlineState]);
const moveHeadline = (value:boolean) => {
setHeadlineState(value);
}
return (
<ThemeProvider theme={theme}>
<div className="App">
<div className="headerDiv">
<Header isOnSubpage={headlineState} backBtnClick={() => { moveHeadline(false) }}/>
</div>
<div className="contentPageDiv">
<ContentPage />
</div>
</div>
</ThemeProvider>
);
}
export default App;
I am not sure what you want to achieve but in case of searching solution to set state to opposite one, you can update your function like this
<Header isOnSubpage={headlineState} backBtnClick={() => {moveHeadline(!headlineState)}}/>
The "state returns directly back to false" because you set it like so in moveHeadline(false).

React object undefined on render

I have an API that responds with the following object:-
[
{
"A":4,
"B":3,
"C":2,
"D":1,
}
]
I want to display the object data in the following component:-
export default function name() {
const dispatch = useDispatch();
const { postsInfo } = useSelector((state) => state.posts);
useEffect(() => {
dispatch(getPostsInfo());
}, [dispatch]);
console.log(postsInfo[0]);
return (
<>
<div className="db-wrapper">
<div className="cards-container">
<Card title={"A"} value={postsInfo[0].A} />
<Card title={"B"} value={postsInfo[0].B} />
<Card title={"C"} value={postsInfo[0].C} />
<Card title={"D"} value={postsInfo[0].D} />
</div>
</div>
</>
);
}
but whenever the page is rendered it throws an error in the console log and nothing is shown on the page.
Uncaught TypeError: Cannot read properties of undefined (reading '0')
This is happening because it's an asynchronous process and is being mounted before useEffect ends fetching the data and populates its state.
To deal with it you can use Optional Chaining:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Array item access with optional chaining:
let arrayItem = arr?.[42];
Your component return will be like this:
return (
<>
<div className="db-wrapper">
<div className="cards-container">
<Card title={"A"} value={postsInfo?.[0].A} />
<Card title={"B"} value={postsInfo?.[0].B} />
<Card title={"C"} value={postsInfo?.[0].C} />
<Card title={"D"} value={postsInfo?.[0].D} />
</div>
</div>
</>
);
Welcome to asynchronous programming!
Dispatch and useEffect are asynchronous. This means that the render function continues to execute without waiting for the effect callback to complete and the dispatch to populate your redux state. As a result postsInfo is undefined on the initial render, and is only later populated on subsequent renders when the redux state changes.
Whenever you are initializing state in a react component via an asynchronous operation, your render function needs to correctly handle the state being uninitialized. The 'standard' way to do this is to return null if the state is uninitialized, like so:
if (!postsInfo) return null;

React: Setting and updating based on props

Currently I am facing the problem that I want to change a state of a child component in React as soon as a prop is initialized or changed with a certain value. If I solve this with a simple if-query, then of course I get an infinite loop, since the components are then rendered over and over again.
Component (parent):
function App() {
const [activeSlide, setActiveSlide] = useState(0);
function changeSlide(index) {
setActiveSlide(index);
}
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}
Component (child):
function Button(props) {
const Icon = Icons[props.icon];
const [activeClass, setActiveClass] = useState("");
// This attempts an endless loop
if(props.active == props.index) {
setActiveClass("active");
}
function toggleView(e) {
e.preventDefault();
props.handler(props.index);
}
return(
<button className={activeClass} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
}
Is there a sensible and simple approach here? My idea would be to write the if-query into the return() and thus generate two different outputs, even though I would actually like to avoid this
The React docs have a nice checklist here used to determine if something does or does not belong in state. Here is the list:
Is it passed in from a parent via props? If so, it probably isn’t state.
Does it remain unchanged over time? If so, it probably isn’t state.
Can you compute it based on any other state or props in your component? If so, it isn’t state.
The active class does not meet that criteria and should instead be computed when needed instead of put in state.
return(
<button className={props.active == props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
)
This is a great use of useEffect.
instead of the if statement you can replace that with;
const {active, index} = props
useEffect(_ => {
if(active == index) {
setActiveClass("active");
}
}, [active])
The last item in the function is a dependency, so useEffect will only run if the active prop has changed.
React automatically re-renders a component when there is a change in the state or props. If you're just using activeClass to manage the className, you can move the condition in the className as like this and get rid of the state.
<button className={props.active === props.index ? 'active' : ''} data-index={props.index} onClick={toggleView}>
<Icon />
</button>
however, if you still want to use state in the child component, you can use the useEffect hook to to update the state in the child component.
Try to use the hook useEffect to prevent the infinite loop. (https://fr.reactjs.org/docs/hooks-effect.html)
Or useCallback hook. (https://fr.reactjs.org/docs/hooks-reference.html#usecallback)
Try this and tell me if it's right for you :
function App() {
const [activeSlide, setActiveSlide] = useState(0);
const changeSlide = useCallback(() => {
setActiveSlide(index);
}, [index]);
return (
<div className="app">
<div className="app__nav">
<Button icon="FiSun" handler={changeSlide} active={activeSlide} index="0" />
<Button icon="FiSettings" handler={changeSlide} active={activeSlide} index="1" />
</div>
</div>
);
}

useEffect not running on refresh

I'm having an issue with the useEffect hook on this blog site I'm building. Im trying to fetch all the blogs from the backend so I can use them to populate this section with the latest five blogs. When I use the code below, the empty array in the useEffect prevents the infinite amount of fetch calls, which is great.
But then I run into a problem where if I refresh the page or navigate back to it I get an error on line 35 saying "cannot find mainImage of undefined".
My question is how do I have the fetchCall populate the state and do so even on a refresh so that I can still access the info I need. Thanks!
import React, {useState, useEffect} from 'react';
import CardItem from './CardItem';
import './Cards.css';
function Cards() {
const [blogs, setBlogs] = useState();
useEffect(() => {
fetchBlogs();
// eslint-disable-next-line react-hooks/exhaustive-deps
},[]);
const fetchBlogs = async () => {
console.log('ok');
const response = await fetch('http://localhost:3000/blogs');
const data = await response.json();
setBlogs(data);
};
return (
<div className='cards'>
<div className='header-container'>
<img
className='logo'
alt='logo'
src='https://Zi.imgur.com/MaLqLee.png'
/>
<h1 className='cards-title'>Hot takes and hometown bias</h1>
</div>
<div className='cards-container'>
<div className='cards-wrapper'>
<ul className='cards-items'>
<CardItem
src={blogs.length > 0 ? blogs[0].mainImage : ''}
text="Don't look now, but Zach Lavine officially kicks ass."
label='Bulls'
path='/bulls'
/>
<CardItem
src='https://i.imgur.com/EmVgHk2.jpg'
text='The curious case of Mitch Trubisky apologists.'
label='Bears'
path='/bears'
/>
</ul>
<ul className='cards-items'>
<CardItem
src='https://i.imgur.com/ZZ5dLJU.jpg'
text='How Did We Get Here? The Suddenly Bleak State of the Cubs.'
label='Cubs'
path='/cubs'
/>
<CardItem
src='https://i.imgur.com/MwgKmUM.jpg'
text='Pace and Nagy: So, how much can we blame these guys?'
label='Bears'
path='/bears'
/>
<CardItem
src='https://i.imgur.com/Y2Eorvu.jpg'
text='Thad Young: An Ode to the NBA Journeyman.'
label='Bulls'
path='/bulls'
/>
</ul>
</div>
</div>
</div>
);
}
export default Cards;
The fetch call is asynchronous. This means it is not guaranteed to be complete before the program enters the next line.
Because of this the blogs array will be empty at the first render. You can add an check in the src CardItem component to only use the value returned from the fetch call when it is available:
<CardItem
src={blogs.length > 0 ? blogs[0].mainImage : ''}
...
/>
An alternative would be to use the fact that blogs is an array and use the map operator to build one or more CardItems.
<ul className='cards-items'>
{blogs.map(blog => <CardItem
src={blog.mainImage}
...
/>)}
</ul>
I faced the same problem, and here is how I solved it..
First, I created a loading state, and set the initial state to true.
// const [singlePackage, setPackage] = useState([]);
const [isLoading, setLoading] = useState(true);
then, in the useEffect hook, I set the state to false like so..
useEffect(() => {
axios.get(baseURL).then((response) => {
setPackage(response.data);
setLoading(false);
})
}, []);
Then, I used a condition, if the loading state is true, return the spinner else return the component like so...
if (isLoading) {
return (
<div className="loadingContainer">
<Loader
type="ThreeDots"
color="#00b22d"
height={100}
width={100}
//3 secs
/>
</div>
)
} else {
return (
// your code here
)}
I am using react-loader-spinner, and just styled the container
you can install it using...
npm install react-loader-spinner --save
the style for container ...
.loadingContainer{
position: fixed;
top: 50%;
left:50%;
transform: translate(-50%,50%);
}

Resources