Why useEffect runs every time when component re-render? - javascript

In my Home component(I call it Home Page!) I am using Cards.JS component which has posts attribute as shown in following code.
const Home = () => {
const dispatch = useDispatch()
const isLoading = useSelector(state => state.isLoading)
const currentPage = useSelector((state) => state.idFor.currentPageHome)
const homePosts = useSelector((state) => state.posts)
useEffect(() => {
dispatch(setIsLoading(true))
dispatch(getAllPosts(currentPage))
}, [dispatch, currentPage])
return (
isLoading ? (
<Loader type="ThreeDots" color="#000000" height={500} width={80} />
) : (
<Cards posts={homePosts} setCurrentPage={setCurrentPageHome} currentPage={currentPage} pageName={"LATEST"} />
)
)
}
And Cards.Js is as following
const Cards = ({ posts, setCurrentPage, currentPage, pageName }) => {
console.log('Cards.JS called', posts);
const dispatch = useDispatch()
useEffect(() => {
dispatch(setIsLoading(false))
})
const handleNextPage = () => {
dispatch(setIsLoading(true))
dispatch(setCurrentPage(currentPage + 1))
}
const handlePreviousPage = () => {
dispatch(setIsLoading(true))
dispatch(setCurrentPage(currentPage - 1))
}
return (
<div className="container">
<h4 className="page-heading">{pageName}</h4>
<div className="card-container">
{
posts.map(post => <Card key={post._id} post={post} />)
}
</div>
<div className="page-div">
{currentPage !== 1 ? <span className="previous-page" onClick={handlePreviousPage}><</span>
: null}
<span className="next-page" onClick={handleNextPage}>></span>
</div>
</div>
)
}
My Problem:
When i come back to home page useEffect is called everytime and request same data to back-end which are already avaliable in Redux store.
Thanks in Advance :)

useEffect will run every time the component rerenders.
However, useEffect also takes a second parameter: an array of variables to monitor. And it will only run the callback if any variable changes in that array.
If you pass an empty array, it will only run once initially, and never again no matter how many times your component rerenders.
useEffect(() => {
dispatch(setIsLoading(false))
}, [])

Related

change in one item re runs whole map component loop in ReacJS

I am dispatching an add comment action on a specific post re runs component loop again instead of updating a specific one. Suppose, If I have 100 posts adding comments to one post runs the component loop again and iterates again 100 times. Is there is any way to re-render only a specific item instead of running the whole component loop again?
Here's my code of the looped component
const Post = ({totalComments, like, _id, image, caption}) => {
const {enqueueSnackbar} = useSnackbar();
const dispatch = useDispatch();
const { user } = useSelector((state) => state.loadUser);
const { loading: loadingComments, comments } = useSelector((state) => state.listComments);
const { success: addCommentSucess, error: addCommentError } = useSelector((state) => state.addComment);
const [openComment, setOpenComment] = useState('');
const [commentsLength, setCommentsLength] = useState(totalComments);
//listing of comments
useEffect(() => {
if (addCommentError) {
enqueueSnackbar(addCommentError, {variant: 'error'});
dispatch(clearErrors());
}
}, [addCommentError, addCommentSucess, dispatch, enqueueSnackbar]);
const openCommentHandler = () => {
dispatch(listComments(_id));
setOpenComment(!openComment);
}
const addCommentHandler = (data) => {
console.log('addcomehandle')
dispatch(addComment(data));
setCommentsLength(commentsLength+1);
}
return (
<Card className='post-container'>
<div className='button-wrapper'>
<IconButton onClick={openCommentHandler} color={openComment ? 'primary' : 'default'}>
<SvgIcon component={CommentOutlinedIcon} />
</IconButton>
<p>{commentsLength}</p>
</div>
{!loadingComments && openComment && (
<Comments comments={comments} />
)}
<Divider />
<AddComment onAddComment={addCommentHandler} postId={_id} />
</Card>
);
};
export default Post;

Problem displaying an item according to the url - React

I have a question. I have a component that when entering /category/:categoryId is rendered doing a fecth to "api url + categoryId". My problem is that if I change from one category to another the page only changes if the useEffect is executed infinitely which generates problems to the view as seen below. How can I make it run once and when I change from /category/1 to /category/2 the useEffect is executed correctly?
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;
My router file:
<Route path="/category/:categoryId" component={Categories} />
This is the problem that is generated, there comes a time when a fetch is made to a previously requested category and then the new requested category is executed.
See my problem in video
You can simply add categoryId to useEffect array argument. Function inside the useEffect is called, only when categoryId changes
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[categoryId]);
you can not edit producto directly, you should use productos :
const Categories = () => {
let [producto, productos] = useState([]);
const { categoryId } = useParams();
useEffect(() => {
fetch('https://fakestoreapi.com/products/category/' + categoryId)
.then(res=>res.json())
.then(data=>productos(data))
},[]);
console.log(producto)
return(
<div className="container">
{producto && producto.map((p) => (
<Producto
title={p.title}
price={p.price}
description={p.description}
image={p.image}
key={p.id}
id={p.id}
/>
))}
</div>
)
}
export default Categories;

Force a single re-render with useSelector

This is a follow-up to Refactoring class component to functional component with hooks, getting Uncaught TypeError: func.apply is not a function
I've declared a functional component Parameter that pulls in values from actions/reducers using the useSelector hook:
const Parameter = () => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
const drawerOpen = useSelector(state => state.filterIconClick);
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
return (
prevState => ({
...prevState,
parameterCurrent: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
//some code describing an alert
});
})
.otherwise(function (err) {
alert(
//some code describing a different alert
);
});
}
);
};
const classes = useStyles();
return (
<div>
{drawerOpen ? (
Object.keys(parameterSelect).map((key, index) => {
return (
<div>
<FormControl component="fieldset">
<FormLabel className={classes.label} component="legend">
{key}
</FormLabel>
{parameterSelect[key].map((valKey, valIndex) => {
return (
<RadioGroup
aria-label="parameter"
name="parameter"
value={parameterCurrent[key]}//This is where the change should be reflected in the radio button
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
<FormControlLabel
className={classes.formControlparams}
value={valKey}
control={
<Radio
icon={
<RadioButtonUncheckedIcon fontSize="small" />
}
className={clsx(
classes.icon,
classes.checkedIcon
)}
/>
}
label={valKey}
/>
</RadioGroup>
);
})}
</FormControl>
<Divider className={classes.divider} />
</div>
);
})
) : (
<div />
)
}
</div >
)
};
export default Parameter;
What I need to have happen is for value={parameterCurrent[key]} to rerender on handleParameterChange (the handleChange does update the underlying dashboard data, but the radio button doesn't show as being selected until I close the main component and reopen it). I thought I had a solution where I forced a rerender, but because this is a smaller component that is part of a larger one, it was breaking the other parts of the component (i.e. it was re-rendering and preventing the other component from getting state/props from it's reducers). I've been on the internet searching for solutions for 2 days and haven't found anything that works yet. Any help is really apprecaited! TIA!
useSelector() uses strict === reference equality checks by default, not shallow equality.
To use shallow equal check, use this
import { shallowEqual, useSelector } from 'react-redux'
const selectedData = useSelector(selectorReturningObject, shallowEqual)
Read more
Ok, after a lot of iteration, I found a way to make it work (I'm sure this isn't the prettiest or most efficient, but it works, so I'm going with it). I've posted the code with changes below.
I added the updateState and forceUpdate lines when declaring the overall Parameter function:
const Parameter = () => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter);
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
const dispatch = useDispatch();
const drawerOpen = useSelector(state => state.filterIconClick);
Then added the forceUpdate() line here:
const handleParameterChange = (event, valKey, index, key) => {
parameterCurrent[key] = event.target.value;
return (
prevState => ({
...prevState,
parameterCurrent: parameterCurrent
}),
() => {
viz
.getWorkbook()
.changeParameterValueAsync(key, valKey)
.then(function () {
//some code describing an alert
});
})
.otherwise(function (err) {
alert(
//some code describing a different alert
);
});
forceUpdate() //added here
}
);
};
Then called forceUpdate in the return statement on the item I wanted to re-render:
<RadioGroup
aria-label="parameter"
name="parameter"
value={forceUpdate, parameterCurrent[key]}//added forceUpdate here
onChange={(e) => dispatch(
handleParameterChange(e, valKey, index, key)
)}
>
I've tested this, and it doesn't break any of the other code. Thanks!

Can i set state in parent from child using useEffect hook in react

I have a set of buttons in a child component where when clicked set a corresponding state value true or false. I have a useEffect hook in this child component also with dependencies on all these state values so if a button is clicked, this hook then calls setFilter which is passed down as a prop from the parent...
const Filter = ({ setFilter }) => {
const [cycling, setCycling] = useState(true);
const [diy, setDiy] = useState(true);
useEffect(() => {
setFilter({
cycling: cycling,
diy: diy
});
}, [cycling, diy]);
return (
<Fragment>
<Row>
<Col>
<Button block onClick={() => setCycling(!cycling)}>cycling</Button>
</Col>
<Col>
<Button block onClick={() => setdIY(!DIY)}>DIY</Button>
</Col>
</Row>
</Fragment>
);
};
In the parent component I display a list of items. I have two effects in the parent, one which does an initial load of items and then one which fires whenever the filter is changed. I have removed most of the code for brevity but I think the ussue I am having boils down to the fact that on render of my ItemDashboard the filter is being called twice. How can I stop this happening or is there another way I should be looking at this.
const ItemDashboard = () => {
const [filter, setFilter] = useState(null);
useEffect(() => {
console.log('on mount');
}, []);
useEffect(() => {
console.log('filter');
}, [filter]);
return (
<Container>..
<Filter setFilter={setFilter} />
</Container>
);
}
I'm guessing, you're looking for the way to lift state up to common parent.
In order to do that, you may bind event handlers of child components (passed as props) to desired callbacks within their common parent.
The following live-demo demonstrates the concept:
const { render } = ReactDOM,
{ useState } = React
const hobbies = ['cycling', 'DIY', 'hiking']
const ChildList = ({list}) => (
<ul>
{list.map((li,key) => <li {...{key}}>{li}</li>)}
</ul>
)
const ChildFilter = ({onFilter, visibleLabels}) => (
<div>
{
hobbies.map((hobby,key) => (
<label {...{key}}>{hobby}
<input
type="checkbox"
value={hobby}
checked={visibleLabels.includes(hobby)}
onChange={({target:{value,checked}}) => onFilter(value, checked)}
/>
</label>))
}
</div>
)
const Parent = () => {
const [visibleHobbies, setVisibleHobbies] = useState(hobbies),
onChangeVisibility = (hobby,visible) => {
!visible ?
setVisibleHobbies(visibleHobbies.filter(h => h != hobby)) :
setVisibleHobbies([...visibleHobbies, hobby])
}
return (
<div>
<ChildList list={visibleHobbies} />
<ChildFilter onFilter={onChangeVisibility} visibleLabels={visibleHobbies} />
</div>
)
}
render (
<Parent />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
Yes, you can, useEffect in child component which depends on the state is also how you typically implement a component which is controlled & uncontrolled:
const NOOP = () => {};
// Filter
const Child = ({ onChange = NOOP }) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
onChange(counter);
}, [counter, onChange]);
const onClick = () => setCounter(c => c + 1);
return (
<div>
<div>{counter}</div>
<button onClick={onClick}>Increase</button>
</div>
);
};
// ItemDashboard
const Parent = () => {
const [value, setState] = useState(null);
useEffect(() => {
console.log(value);
}, [value]);
return <Child onChange={setState} />;
};

How to give child component control over react hook in root component

I have an application that adds GitHub users to a list. When I put input in the form, a user is returned and added to the list. I want the user to be added to the list only if I click on the user when it shows up after the resource request. Specifically, what I want is to have a click event in the child component trigger the root component’s triggering of the hook, to add the new element to the list.
Root component,
const App = () => {
const [cards, setCards] = useState([])
const addNewCard = cardInfo => {
console.log("addNewCard called ...")
setCards([cardInfo, ...cards])
}
return (
<div className="App">
<Form onSubmit={addNewCard}/>
<CardsList cards={cards} />
</div>
)
}
export default App;
Form component,
const Form = props => {
const [username, setUsername] = useState('');
const chooseUser = (event) => {
setUsername(event.target.value)
}
const handleSubmit = event => {
event.persist();
console.log("FETCHING ...")
fetch(`http://localhost:3666/api/users/${username}`, {
})
.then(checkStatus)
.then(data => data.json())
.then(resp => {
console.log("RESULT: ", resp)
props.onSubmit(resp)
setUsername('')
})
.catch(err => console.log(err))
}
const checkStatus = response => {
console.log(response.status)
const status = response.status
if (status >= 200 && status <= 399) return response
else console.log("No results ...")
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Gitbub username"
value={username}
required
onChange={chooseUser}
onKeyUp={debounce(handleSubmit, 1000)}
/>
<button type="submit">Add card</button>
</form>
)
}
export default Form;
List component,
const CardsList = props => {
return (
<div>
{props.cards.map(card => (
<Card key={card.html_url} {... card}
/>
))}
</div>
)
}
export default CardsList
and the Card Component,
const Card = props => {
const [selected, selectCard] = useState(false)
return (
<div style={{margin: '1em'}}>
<img alt="avatar" src={props.avatar_url} style={{width: '70px'}} />
<div>
<div style={{fontWeight: 'bold'}}><a href={props.html_url}>{props.name}</a></div>
<div>{props.blog}</div>
</div>
</div>
)
}
export default Card
Right now, my Form component has all the control. How can I give control over the addNewCard method in App to the Card child component?
Thanks a million in advance.
One solution might be to create a removeCard method in App which is fired if the click event you want controlling addNewCard doesn't happen.
// App.js
...
const removeCard = username => {
console.log("Tried to remove card ....", username)
setCards([...cards.filter(card => card.name != username)])
}
Then you pass both removeCard and addNewCard to CardList.
// App.js
...
<CardsList remove={removeCard} cards={cards} add={addNewCard}/>
Go ahead and pass those methods on to Card in CardsList. You will also want some prop on card assigned to a boolean, like, "selected".
// CardsList.js
return (
<div>
{props.cards.map(card => (
<Card key={card.html_url} {... card}
remove={handleClick}
add={props.add}
selected={false}
/>
))}
</div>
Set up your hook and click event in the child Card component,
// Card.js
...
const [selected, selectCard] = useState(false)
...
and configure your events to trigger the hook and use the state.
// Card.js
...
return (
<div style={{margin: '1em', opacity: selected ? '1' : '0.5'}}
onMouseLeave={() => selected ? null : props.remove(props.name)}
onClick={() => selectCard(true)}
>
...
This doesn't really shift control of addNewCard from Form to Card, but it ultimately forces the UI to follow the state of the Card component.

Categories