React Component is rendering twice - reactjs

I have no idea why, the first render shows an empty object and the second shows my data:
function RecipeList(props) {
return (
<div>
{console.log(props.recipes)}
{/*{props.recipes.hits.map(r => (*/}
{/* <Recipe initial="lb" title={r.recipe.label} date={'1 Hour Ago'}/>*/}
</div>
)
}
const RECIPES_URL = 'http://cors-anywhere.herokuapp.com/http://test-es.edamam.com/search?i?app_id=426&q=chicken&to=10'
export default function App() {
const classes = useStyles();
const [data, setData] = useState({});
useEffect(() => {
axios.get(RECIPES_URL)
.then(res => {
setData(res.data);
})
.catch(err => {
console.log(err)
})
}, []);
return (
<div className={classes.root}>
<NavBar/>
<RecipeList recipes={data}/>
<Footer/>
</div>
);
}
I don't know why and I have struggled here for over an hour (React newbie), so I must be missing something.

This is the expected behavior. The reason you see two console logs is because, the first time RecipeList is called with no data (empty object), and the second time when the data becomes available. If you would like to render it only when the data is available you could do something like {Object.keys(data).length > 0 && <RecipeList recipes={data}/>}. By the way this is called conditional rendering.

This is perfectly normal, React will render your component first with no data. Then when your axios.get returns and update data, it will be rendered again with the new data

Related

React dynamically added components not rendered

I'm dynamically adding instances of a custom (Kendo-React) component into an array in my main App.
The component:
const PersonDD = () => {
const ages = ["Child", "Adult", "Senior"];
return (
<div>
<div>Person:</div>
<DropDownList
data={ages} style={{ width: "300px", }}
/>
</div>
);
};
I'm adding one instance on initial render, and another two instances after the result from an Ajax call returns.
const SourceTab = (SourceTabProps) => {
....
var componentList = [];
componentList.push(<PersonDD/>);
async function getStrata(){
var url = '/access/.im.read';
const res = await axios.get( url );
console.log(res.data.item);
componentList.push(<PersonDD/>);
componentList.push(<PersonDD/>);
}
React.useEffect(() =>{
getStrata();
},[]);
return (
<Title title="People" />
<div className='assignment_div_css'>
{componentList}
</div>);
};
The problem I have is that the one instance in the initial array are rendered, but the two created after the Ajax call are not rendered.
Do I need to call .render() or something similar to refresh?
You can simply use react useState to rerender component and in jsx map them.
like this :
const SourceTab = (SourceTabProps) => {
const [componentList,setComponentList] = useState([PersonDD])
async function getStrata(){
var url = '/access/.im.read';
const res = await axios.get( url );
console.log(res.data.item);
setComponentList([...componentList,PersonDD,PersonDD])
}
React.useEffect(() =>{
getStrata();
},[]);
return (
<Title title="People" />
<div className='assignment_div_css'>
{componentList.map((Component,index)=> <Component key={index} />)}
</div>);
};
You need to remember that React only re-renders (refreshes the UI/view) when a state changes. Your componentList is not a state at the moment but just an ordinary variable. make it a state by using useState hook.
Not sure if it is a bad practice or not but I haven't seen any react project that keeps an entire component as a state so instead of creating a state with an array of components, just push a data representation of the components you want to render. Then display the component list using your list and using .map
Here's how it would look like.
....
const [personList, setPersonList] = useState([1]);
async function getStrata(){
var url = '/access/.im.read';
const res = await axios.get( url );
setPersonList(state => state.push(2)); //you can make this dynamic so it can rerender as much components as you like, for now im pushing only #2
}
React.useEffect(() =>{
getStrata();
},[]);
return (
<Title title="People" />
<div className='assignment_div_css'>
{personList.map((item, key) => <PersonDD key={key} />)}
</div>);
};
Need to use the map to render a list
<div className='assignment_div_css'>
{componentList.map(component => <>{component}</>)}
</div>);
also, use a usestate to variable
const [componentList , setComponentList ]= React.useState[<PersonDD/>];
inside function set like this
console.log(res.data.item);
setComponentList(state => [...state, <PersonDD/>, <PersonDD/>]);

react, map component, unexpected result

I am building Weather App, my idea is to save city name in database/localhost, place cities in useState(right now it's hard coded), iterate using map in first child component and display in second child component.
The problem is that 2nd child component outputs only one element (event though console.log prints both)
BTW when I change code in my editor and save, then another 'li' element appears
main component
const App = () => {
const [cities, setCities] = useState(['London', 'Berlin']);
return (
<div>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
export default App
first child component
const DisplayWeather = ({displayWeather}) => {
const [fetchData, setFetchData] = useState([]);
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData([...fetchData , data]);
})
}, [])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
export default DisplayWeather
second child component
const Weather = ({data}) => {
console.log(data) // it prints correctly both data
return (
<li>
{data.name} //display only one data
</li>
)
}
export default Weather
The Problem
The setFetchData hooks setter method is asynchronous by default, it doesn't give you the updated value of the state immediately after it is set.
When the weather result for the second city is returned and set to state, the current value fetchData at the time is still an empty array, so you're essentially spreading an empty array with the second weather result
Solution
Pass a callback to your setFetchData and get the current previous value of the state and then continue with your spread accordingly.
Like this 👇🏽
setFetchData((previousData) => [...previousData, data]);

How to execute useEffect only once inside looped component

I have component where I have array of data that is being looped using map and rendered a new component base one that and inside the looped component I have a useEffect that fetches the data from the api but it runs same api twice.
Here is the code
I am looping through array of rule_set_versions which is in this case size of 2
const ExpandedContent = ({ experiment }) => {
return experiment.rule_set_versions &&
experiment.rule_set_versions.map((ruleSetVersion) => <RuleSetVersionCollapse key={ruleSetVersion.id} ruleSetVersion={ruleSetVersion} />)
}
const ExperimentsCollapse = ({ experiment }) => {
return <React.Fragment>
<div className={styles.experiment_collapse_root}>
<Collapse>
<Collapse.Panel className={styles.experiment_item} extra={<ExtraTest experiment={experiment} />}>
<ExpandedContent experiment={experiment} />
</Collapse.Panel>
</Collapse>
</div>
</React.Fragment>
}
Here is my RuleSetVersionCollapse snippet
const ruleSet = useSelector(state => state.ruleSet)
React.useEffect(() => {
if (!ruleSet.id) {
dispatch(getRuleSetAction(ruleSetVersion.rule_set_id))
}
}, [dispatch])
And the useEffect runs twice even though the ruleSetVersion.rule_set_id is same on both the case.
Can anyone suggest any way I can solve this issue.
Thanks

Incorrect use of useEffect() when filtering an array

I have this React app that's is getting data from a file showing in cards. I have an input to filter the cards to show. The problem I have is that after I filter once, then it doesn't go back to all the cards. I guess that I'm using useEffect wrong. How can I fix this?
import { data } from './data';
const SearchBox = ({ onSearchChange }) => {
return (
<div>
<input
type='search'
placeholder='search'
onChange={(e) => {
onSearchChange(e.target.value);
}}
/>
</div>
);
};
function App() {
const [cards, setCards] = useState(data);
const [searchField, setSearchField] = useState('');
useEffect(() => {
const filteredCards = cards.filter((card) => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
});
setCards(filteredCards);
}, [searchField]);
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={cards} />
</div>
);
}
you should Include both of your state "Card", "searchedField" as dependincies to useEffect method.once any change happens of anyone of them, your component will re-render to keep your data up to date,
useEffect(() => { // your code }, [searchField, cards]);
cards original state will be forever lost unless you filter over original data like const filteredCards = data.filter().
though, in a real project it's not interesting to modify your cards state based on your filter. instead you can remove useEffect and create a filter function wrapped at useCallback:
const filteredCards = useCallback(() => cards.filter(card => {
return card.name.toLowerCase().includes(searchField.toLowerCase());
}), [JSON.stringify(cards), searchField])
return (
<div>
<SearchBox onSearchChange={setSearchField} />
<CardList cards={filteredCards()} />
</div>
);
working example
about array as dependency (cards)
adding an object, or array as dependency at useEffect may crash your app (it will throw Maximum update depth exceeded). it will rerun useEffect forever since its object reference will change everytime. one approach to avoid that is to pass your dependency stringified [JSON.stringify(cards)]

using useEffect in combination with Axios fetching API data returns null - how to deal with this?

Fetching data using Axios and useEffect results in null before the actual object is loaded.
Unfortunately, I am not able to use object destructuring before the actual object is not empty.
I am forced to use a hook to check whether an object is empty or not.
For instance, I would like to create multiple functions and split my code up in separate functions for better readability.
This is my HTTP request Hook:
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useHttp = (url, dependencies) => {
const [isLoading, setIsLoading] = useState(false);
const [fetchedData, setFetchedData] = useState(null);
useEffect(() => {
setIsLoading(true);
axios
.get(url)
.then(response => {
setIsLoading(false);
setFetchedData(response.data);
})
.catch(error => {
console.error('Oops!', error);
setIsLoading(false);
});
}, dependencies);
return [isLoading, fetchedData];
};
Followed by my page component:
import React from 'react';
import { PAGE_ABOUT, API_URL } from 'constants/import';
import Header from './sections/Header';
import Main from './sections/Main';
import Aside from './sections/Aside';
import { useHttp } from 'hooks/http';
const About = () => {
const [isLoading, about] = useHttp(PAGE_ABOUT, []);
if (!isLoading && about) {
return (
<section className="about">
<div className="row">
<Header
featuredImage={API_URL + about.page_featured_image.path}
authorImage={API_URL + about.page_author_image.path}
authorImageMeta={about.page_author_image.meta.title}
title={about.about_title}
subtitle={about.about_subtitle}
/>
<Main
title={about.page_title}
content={about.page_content}
/>
<Aside
title={about.about_title}
content={about.about_content}
/>
</div>
</section>
);
}
};
export default React.memo(About);
The actual problem I am not able to nest functions before the object is actually returned.
Is there a way to fetch data without a check by any chance? or a cleaner solution would help.
I would like to use multiple components to split up the code.
Any advice or suggestion would be highly appreciated.
What you are asking would be bad for UIs. You don't want to block the UI rendering when you're fetching data. So the common practice is showing a Loading spinner or (if you're counting on the request being fast) just rendering nothing until it pops up.
So you would have something like:
const About = () => {
const [isLoading, about] = useHttp(PAGE_ABOUT, []);
if (isLoading) return null; // or <Loading />
return (
<section className="about">
<div className="row">
<Header
featuredImage={API_URL + about.page_featured_image.path}
authorImage={API_URL + about.page_author_image.path}
authorImageMeta={about.page_author_image.meta.title}
title={about.about_title}
subtitle={about.about_subtitle}
/>
<Main
title={about.page_title}
content={about.page_content}
/>
<Aside
title={about.about_title}
content={about.about_content}
/>
</div>
</section>
);
};
If your api has no protection for errors and you're afraid of about being null or undefined you can wrap the component with an Error Boundary Component and show a default error. But that depends if you use those in your app.
Error boundaries are React components that catch JavaScript errors
anywhere in their child component tree, log those errors, and display
a fallback UI instead of the component tree that crashed. Error
boundaries catch errors during rendering, in lifecycle methods, and in
constructors of the whole tree below them.
You are not returnig any if your condition in our About component is false so add a case if isLoading or !about you could add a loader maybe:
const About = () => {
const [isLoading, about] = useHttp(PAGE_ABOUT, []);
let conditionalComponent = null;
if (!isLoading && about) {
conditionalComponent= (
<section className="about">
<div className="row">
<Header
featuredImage={API_URL + about.page_featured_image.path}
authorImage={API_URL + about.page_author_image.path}
authorImageMeta={about.page_author_image.meta.title}
title={about.about_title}
subtitle={about.about_subtitle}
/>
<Main
title={about.page_title}
content={about.page_content}
/>
<Aside
title={about.about_title}
content={about.about_content}
/>
</div>
</section>
);
}
return conditionalComponent
};
As I recognize you want to get rid of condition if (!isLoading && about) {
You can make it in several ways:
1) You can create Conditional component by your own. This component should receive children prop as a function and should apply other props to this function:
function Conditional({ children, ...other }) {
if (Object.entries(other).some(([key, value]) => !value)) {
return "empty"; // null or some `<Loading />` component
}
return children(other);
}
Also I made a small demo https://codesandbox.io/s/upbeat-cori-ngenk
It helps you to create only the one place for condition and reuse it in dozens of components :)
2) You can use some library, which is built on a top of React.lazy and React.Suspense (but it wouldn't work on a server-side).
For example https://dev.to/charlesstover/react-suspense-with-the-fetch-api-374j and a library: https://github.com/CharlesStover/fetch-suspense

Resources