React dynamically added components not rendered - reactjs

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/>]);

Related

Data from useRef renders only after changing the code and saving it

I'm having an issue with my react app. I retrieve data from my elasticsearch server and trying to display it on the website.
const RecipesPage = (props: Props) => {
const recipes = useRef<Recipe[]>([]);
const avCategories = ['meats', 'pastas', 'vegan', 'seafood', 'desserts', 'all'];
const currentCategory = props.match.params.category_name.toLowerCase();
useEffect(() => {
const recipesReq = getRecipesByCategory(currentCategory);
recipesReq
.then((data) => recipes.current = data.hits.hits)
}, [currentCategory])
if (avCategories.includes(currentCategory)) {
return (
<div>
<Navbar />
<ul style={{marginTop: "5.5rem"}}>{recipes.current.map((recipe: Recipe) => <p key={recipe._id}>{recipe._source.recipe_name}</p>)}</ul>
</div>
);
} else {
return (
<div>
<Navbar />
<p style={{marginTop: "5.5rem"}}>No category named {currentCategory}</p>
</div>
);
}
};
export default RecipesPage
The problem is that when I'm trying to display the data it shows up only after saving the code and then after refreshing the page it's gone. I guess it's a problem related to useRef hook, but I'm not sure.
You should use state if you need the component to rerender.
When using useEffect, you shouldn't pass an array or object reference as a dependency. React uses referential comparison to check for changes, which means the useEffect hook will run every time a new object/array is created regardless if the actual data changes, which can cause an infinite render loop:
https://www.benmvp.com/blog/object-array-dependencies-react-useEffect-hook/

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)]

React Component is rendering twice

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

React Hook useEffect() run continuously although I pass the second params

I have problem with this code
If I pass the whole pagination object to the second parameters of useEffect() function, then fetchData() will call continuously. If I only pass pagination.current_page so It will call only one time, but when I set new pagination as you see in navigatePage() function, the useEffect() does not call to fetchData() although pagination has changed.
How to solve this. Thank you very much!
Besides I do not want the use useEffect() call when first time component mounted because the items is received from props (It is fetch by server, this is nextjs project).
import React, {useEffect, useState} from 'react';
import Filter from "../Filter/Filter";
import AdsListingItem from "../AdsListingItem/AdsListingItem";
import {Pagination} from "antd-mobile";
import styles from './AdsListing.module.css';
import axios from 'axios';
const locale = {
prevText: 'Trang trước',
nextText: 'Trang sau'
};
const AdsListing = ({items, meta}) => {
const [data, setData] = useState(items);
const [pagination, setPagination] = useState(meta);
const {last_page, current_page} = pagination;
const fetchData = async (params = {}) => {
axios.get('/ads', {...params})
.then(({data}) => {
setData(data.data);
setPagination(data.meta);
})
.catch(error => console.log(error))
};
useEffect( () => {
fetchData({page: pagination.current_page});
}, [pagination.current_page]);
const navigatePage = (pager) => {
const newPagination = pagination;
newPagination.current_page = pager;
setPagination(newPagination);
};
return (
<>
<Filter/>
<div className="row no-gutters">
<div className="col-md-8">
<div>
{data.map(item => (
<AdsListingItem key={item.id} item={item}/>
))}
</div>
<div className={styles.pagination__container}>
<Pagination onChange={navigatePage} total={last_page} current={current_page} locale={locale}/>
</div>
</div>
<div className="col-md-4" style={{padding: '15px'}}>
<img style={{width: '100%'}} src="https://tpc.googlesyndication.com/simgad/10559698493288182074"
alt="ads"/>
</div>
</div>
</>
)
};
export default AdsListing;
The issue is you aren't returning a new object reference. You save a reference to the last state object, mutate a property on it, and save it again.
const navigatePage = (pager) => {
const newPagination = pagination; // copy ref pointing to pagination
newPagination.current_page = pager; // mutate property on ref
setPagination(newPagination); // save ref still pointing to pagination
};
In this case the location in memory that is pagination remains static. You should instead copy all the pagination properties into a new object.
const navigatePage = (pager) => {
const newPagination = {...pagination}; // shallow copy into new object
newPagination.current_page = pager;
setPagination(newPagination); // save new object
};
To take it a step further you really should be doing functional updates in order to correctly queue up updates. This is in the case that setPagination is called multiple times during a single render cycle.
const navigatePage = (pager) => {
setPagination(prevPagination => {
const newPagination = {...prevPagination};
newPagination.current_page = pager;
});
};
In the case of pagination queueing updates may not be an issue (last current page set wins the next render battle), but if any state updates actually depend on a previous value then definitely use the functional update pattern,

Resources