What is the right place to call a function before render in React? - reactjs

I have some issue on understandsing the lifecycle in React, so iam using useEffects() since i do understand that it was the right way to call a method before the component rendered (the replacement for componentDidMount ).
useEffect(() => {
tagSplit = tagArr.split(',');
});
And then i call tagSplit.map() function on the component, but it says that tagSplit.map is not a function
{tagSplit.map((item, index) => (
<div className="styles" key={index}>
{item}
</div>
))}
Is there something wrong that i need to fix or was it normal ?

useEffect runs AFTER a render and then subsequently as the dependencies change.
So yes, if you have tagSplit as something that doesn't support a map function initially, it'll give you an error from the first render.
If you want to control the number of times it runs, you should provide a dependency array.
From the docs,
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.
This article from Dan Abramov's blog should also help understand useEffect better
const React, { useState, useEffect } from 'react'
export default () => {
const [someState, setSomeState] = useState('')
// this will get reassigned on every render
let tagSplit = ''
useEffect(() => {
// no dependencies array,
// Runs AFTER EVERY render
tagSplit = tagArr.split(',');
})
useEffect(() => {
// empty dependencies array
// RUNS ONLY ONCE AFTER first render
}, [])
useEffect(() => {
// with non-empty dependency array
// RUNS on first render
// AND AFTER every render when `someState` changes
}, [someState])
return (
// Suggestion: add conditions or optional chaining
{tagSplit && tagSplit.map
? tagSplit.map((item, index) => (
<div className='styles' key={index}>
{item}
</div>
))
: null}
)
}

you can do something like this .
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
let tagSplit = tagArr.split(',');
setArr(tagSplit);
}, []);
return (
<>
{arr.map((item, index) => (
<div className="styles" key={index}>
{item}
</div>
))}
</>
)
}

Answering the question's title:
useEffect runs after the first render.
useMemo runs before the first render.
If you want to run some code once, you can put it inside useMemo:
const {useMemo, Fragment} = React
const getItemsFromString = items => items.split(',');
const Tags = ({items}) => {
console.log('rendered')
const itemsArr = useMemo(() => getItemsFromString(items), [items])
return itemsArr.map((item, index) => <mark style={{margin: '3px'}} key={index}>{item}</mark>)
}
// Render
ReactDOM.render(<Tags items='foo, bar, baz'/>, root)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
For your specific component, it's obvious there is no dilema at all, as you can directly split the string within the returned JSX:
return tagArr.split(',').map((item, index) =>
<div className="styles" key={index}>
{item}
</div>
)
But for more complex, performance-heavy transformations, it is best to run them only when needed, and use a cached result by utilizing useMemo

Related

React Slider images changing on Click not working (uncaught error too many re-renders)

I'm trying to create a React slider for images. But I'm getting an error that says Uncaught Error: Too many re-renders. If anyone can just point me in the right direction I would really appreciate it. I'm certain that the issue lays within the onClick aspect of the sliderDots mapping.
import React, { useEffect, useState } from 'react';
import Sliderdots from '../CarasouelDots/Sliderdots.component';
import './Slider.styles.scss'
import sliderImages from '../../MockImages/mockimages';
const Slider = () => {
const images = sliderImages;
//Iterator
const [img, setImg] = useState(0);
//Getting all shoe images from an object array
const shoes = images.map(i => (i.shoe));
const heading = images.map(i => (i.title));
const content = images.map(i => (i.content))
const numbers = shoes.map((i, index) => (index))
const indexSet = (number) =>{
setImg(number);
}
//problem with onClick here??
const sliderD = images.map((dot, index) => <Sliderdots key={index} onClick={indexSet(index)}/>);
useEffect(() => {
const timer = setTimeout(() => {
img == shoes.length - 1 ? setImg(0) : setImg(img + 1)
}, 4500)
}, [img]);
return (
<div className='slider-container' style={{ backgroundImage: `url(${shoes[img]})` }}>
<div className='overlay'>
<h1 className='introduction'>{heading[img]}</h1>
<p className='content'>{content[img]}</p>
<div className='dot-container'>
{sliderD}
</div>
</div>
</div>
);
};
export default Slider;
The reason why your component is constantly rerendering is because your onClick property is actually a function call in disguise that gets executed every render:
// This line actually calls the `indexSet` function each time!
const sliderD = images.map((dot, index) => <Sliderdots key={index} onClick={indexSet(index)}/>);
And since indexSet updates the state of the React component by calling setImg, the React component will always end up re-rendering when it reaches that line of code, and since that line of code always re-calls the indexSet function, your component will infinitely re-render.
To fix your code, you just need to replace that onClick property with an anonymous function:
const sliderD = images.map((dot, index) => <Sliderdots key={index} onClick={() => indexSet(index)}/>);

I want only one component state to be true between multiple components

I am calling components as folloews
{userAddresses.map((useraddress, index) => {
return (
<div key={index}>
<Address useraddress={useraddress} />
</div>
);
})}
Their state:
const [showEditAddress, setShowEditAddress] = useState(false);
and this is how I am handling their states
const switchEditAddress = () => {
if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
}
};
Well, it's better if you want to toggle between true and false to use the state inside useEffect hook in react.
useEffect will render the component every time and will get into your condition to set the state true or false.
In your case, you can try the following:
useEffect(() => { if (showEditAddress === false) {
setShowEditAddress(true);
} else {
setShowEditAddress(false);
} }, [showEditAddress])
By using useEffect you will be able to reset the boolean as your condition.
Also find the link below to react more about useEffect.
https://reactjs.org/docs/hooks-effect.html
It would be best in my opinion to keep your point of truth in the parent component and you need to figure out what the point of truth should be. If you only want one component to be editing at a time then I would just identify the address you want to edit in the parent component and go from there. It would be best if you gave each address a unique id but you can use the index as well. You could do something like the following:
UserAddress Component
const UserAddress = ({index, editIndex, setEditIndex, userAddress}) => {
return(
<div>
{userAddress}
<button onClick={() => setEditIndex(index)}>Edit</button>
{editIndex === index && <div style={{color: 'green'}}>Your editing {userAddress}</div>}
</div>
)
}
Parent Component
const UserAddresses = () => {
const addresses = ['120 n 10th st', '650 s 41 st', '4456 Birch ave']
const [editIndex, setEditIndex] = useState(null)
return userAddresses.map((userAddress, index) => <UserAddress key={index} index={index} editIndex={editIndex} setEditIndex={setEditIndex} userAddress={userAddress}/>;
}
Since you didn't post the actual components I can only give you example components but this should give you an idea of how to achieve what you want.

Single responsibility in React component

I was learning Single responsibility principle in React and created such component:
import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
export const SingleResponsibilityPrinciple = () => {
const {filteredUsers , isLoading} = useGetRemoteData()
const showDetails = (userId) => {
const user = filteredUsers.find(user => user.id===userId);
alert(user.contact)
}
return <>
<div> Users List</div>
<div> Loading state: {isLoading? 'Loading': 'Success'}</div>
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(user.id)}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
</>
}
As you can see above, I have this code
const user = filteredUsers.find(user => user.id===userId);
The question is Is it best practice that if whenever we use map, reduce or any array function in React component, we should extract that logic out of a component, that is, filteredUsers.find(user => user.id===userId); should be extracted and we need to create utility function. So, function should not care about how a particular thing is done. Is it true?
Thank you for your question. I want to advice as follows
It is better for your to check if filteredUsers exists or not in your return.
{filteredUsers?.map(user=>{
//your code
})
You don't need to get specific user as you already loop in your map method. Just simply do something like this
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(alert(user.contact))}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
Remember the difference between find, filter method of Javascript array. If you have unique value according to userId, you simply use find method to get the first value not array, if you use filter, you get arrays of the condition. Are you sure you don't need to alert(user[0].contact), not alert(user.contact)?

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/

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

Resources