React object undefined on render - reactjs

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;

Related

react firestore - Unable to pass useEffect dependency / useState as prop to component

I'm trying to conditionally render a component using a useState prop when it is finished loading in my useEffect hook but I am having issues. I've tested my theory thus far and am able to render parts of the data once it is finished loading but I can't pass it as a prop to my component and when it is passed, it says it is undefined in the component. I'm trying to get the component to be able to render when the data has finished loading from Firestore.
Its throwing me for a loop that i cant render my component but i can conditionally render the other blocks below.
here is my code:
export default function Dashboard() {
const [keys, setKeys] = useState(allKeys);
const [user, loading, error] = useAuthState(auth);
const [dataToShow, setData] = useState();
const actionsPath = `Users/${user.uid}/XXXXXXXXX/`;
let arrayActions = [];
useEffect(() => {
onSnapshot(collection(db, actionsPath), (snapshot) => {
const docs = snapshot.docs;
docs.forEach((doc) => arrayActions.push(doc.data())); // this works
setData(arrayActions);
});
}, []);
return (
<React.Fragment>
<h1>Dashboard</h1>
<h1>{user.displayName}</h1>
<h1>{user.uid}</h1>
<h2>
<strong>Stat</strong>
</h2>
{/* able to conditionally render */}
{dataToShow ? <p>Data is here</p> : <p>nothing</p>}
{/* able to show the data */}
{dataToShow ? dataToShow?.map((doc, index) => (
<h1>{doc.Add}</h1>
)) : <p>nothing</p>}
{/* block below shows an error and gives error: `Uncaught TypeError: csvData is not iterable`*/}
{dataToShow ? <Profile
propsData={dataToShow} {/* shows undefined in console log in Profile Component */}
keys={keys}
colors={colors}
height="600px"
/> : <p>nothing</p>}
</React.Fragment>
);
Any and all help and direction is appreciated.
The ternary to check for dataToShow is passing (or it would render <p>nothing</p>) - so the data is being passed as a prop.
{dataToShow ? <Profile
propsData={dataToShow} {/* shows undefined in console log in Profile Component */}
keys={keys}
colors={colors}
height="600px"
/> : <p>nothing</p>}
The error Uncaught TypeError: csvData is not iterable looks like the Profile component is specifically searching for a csvData object and trying to iterate through it - and failing.
You should check (and share) your Profile component to see if csvData is hard-coded somewhere.
Perhaps also possible is that you're using a library to handle the data in the Profile component. Are you using d3js by any chance?

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-query doesn't refresh the component

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.

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