Why state always changes back to init value when redux rerender - javascript

State changes back to init value and useEffect run everytime when redux rerender. it looks like component remount after redux state change.
console print hello twice and input box change to empty when i input some text and click Search button.
import React, {useEffect, useState} from "react";
import Button from "react-bootstrap/es/Button";
import fetch from "cross-fetch";
import actions from "#/actions";
import api from "#/api";
import {useDispatch} from "react-redux";
const getData = (title, page) => dispatch => {
dispatch(actions.notice.loading());
return fetch(api.notice.list({title, page}), {method: 'POST'})
.then(response => response.json())
.then(resultBean => {
dispatch(actions.notice.success(resultBean.data.list))
}).catch(error => {
dispatch(actions.notice.failure(error));
});
};
const View = _ => {
const [title, setTitle] = useState('');
const dispatch = useDispatch();
useEffect(() => {
console.log('hello');
dispatch(getData(title, 1))
}, []);
return (
<>
<input onChange={e => setTitle(e.target.value)} value={title} type="text"/>
<Button onClick={e => dispatch(getData(title, 1))}>Search</Button>
</>
)
};
export default View;

You not showing the complete logic, in case you dispatch action and it triggers View parent to render, View may re-render or even unmount depending on the condition of its parent.
For View not to re-render, you need to memorize it:
// default is shallow comparison
export default React.memo(View);
For View to not to unmount, check its parent logic.
Also, note that using _ at const View = _ => still allows you to access it like _.value which I believe is not what you intended.

Related

Too many re-renders React error while fetching data from API

I am building a simple recipe app and I have a problem with fetching my data from the API, because the code seems to run on every render and I do not even understand why it re-runs since I found that if I add the dependency array, it should run only once, right ?
App.js
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={recipesList} getRecipes={setRecipesList} />
</div>
);
}
export default App;
Recipes.js
import React, {useEffect, useState} from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = (props) => {
useEffect( () => {
if (props.recipesList.length === 0) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.getRecipes(result.recipes);
}
)
}
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [props])
const recipeComponent = props.recipesList.map( (item) => {
return <Recipe className="recipe" info={item}/>
})
return(
<div className="recipes">
{recipeComponent}
<h1>Hello</h1>
</div>
)
}
export default Recipes;
Components will re-render every time your the props or state changes inside of the component.
I would recommend keeping the fetching logic inside of the Recipes component, because A: its recipe related data, not app related data. And B: this way you can control the state in Recipes instead of the props. This will give you more control on how the component behaves instead of being dependent on the parent component.
In the useEffect hook, leave the dependency array empty. This will cause the component to render, call useEffect only the first time, load your data and then render the recipes without re-rendering further.
import React, { useEffect, useState } from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = () => {
const [recipesList, setRecipesList] = useState([]);
useEffect(() => {
fetch("myapi.com/blablabla")
.then((res) => res.json())
.then((result) => {
setRecipesList(result.recipes);
});
return () => console.log("unmounting");
}, []);
// On the first render recipeComponents will be empty.
const recipeComponents = recipesList.map((item) => <Recipe className="recipe" info={item}/>)
return (
<div className="recipes">
{recipeComponents}
<h1>Hello</h1>
</div>
);
};
export default Recipes;
try this code :
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
const getListPropd = (e) => {
setRecipesList(e)
}
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={(e) => getListPropd (e)} getRecipes={setRecipesList} />
</div>
);
}
export default App;
const [checkData , setCheckData ] = useState(true)
useEffect( () => {
if (checkData) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.recipesList(result.recipes);
}
if(props.recipesList.length > 0) {
setCheckData(false)
}
)
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [checkData])
the useEffect hook uses an empty dependency array, [] if it should ONLY run once after component is mounted. This is the equivalent of the old lifecycle method componentDidMount()
If you add a non-empty dependency array, then the component rerenders EVERY time this changes. In this case, every time your component receives new props (i.e. from a parent component, this triggers a reload.
see more info here https://reactjs.org/docs/hooks-effect.html , especially the yellow block at the bottom of the page
Happy coding!

React Re-Render Issue : How Can I Stop Re-Render?

I'm new in coding and i couldn't get how to fix the issue after i googled many times. The issue is i have a layout component which contains 4 different components. When i call a function in a function component it affects the others and the others re-render. But i don't pass the new props to them. I only pass props to one component which contains click events. I hope I made myself clear , thanks in advance. So here are my code samples :
This is my layout component.
import React, { useState } from "react";
import Header from "./Header";
import MenuTitle from "./MenuTitle";
import MenuList from "./MenuList";
import Cart from "./Cart";
import Footer from "./Footer";
function Layout({
cartData,
menuList,
menuTitles,
callMenuList,
addToCart,
title,
removeFromCart,
currency,
}) {
const [isCartOpened, setIsCartOpened] = useState("closed");
const openCart = () => {
if (isCartOpened == "closed") {
setIsCartOpened("opened");
} else {
setIsCartOpened("closed");
}
};
const closeCart = () => {
setIsCartOpened("closed");
};
return (
<div>
<Header openCart={() => openCart()} cartData={cartData} />
<MenuTitle
menuTitles={menuTitles}
callMenuList={(titleProp) => callMenuList(titleProp)}
/>
<MenuList
title={title}
menuList={menuList}
addToCart={(data) => addToCart(data)}
/>
<Cart
currency={currency}
cartData={cartData}
removeFromCart={(itemId) => removeFromCart(itemId)}
isCartOpened={isCartOpened}
closeCart={() => closeCart()}
/>
<Footer />
</div>
);
}
export default Layout;
And this is my App component
import React, { useState, useEffect } from "react";
import Layout from "./Components/Layout";
function App() {
const [data, setData] = useState([]);
const [menuTitle, setMenuTitle] = useState([]);
const [title, setTitle] = useState("");
const [currency, setCurrency] = useState("");
const [menuList, setMenuList] = useState([]);
const [cart, setCart] = useState([]);
const API = "./db.json";
const callMenuList = React.useCallback((titleProp) => {
setTitle(titleProp);
const filterMenuList = data.filter((title) => title.TYPE == titleProp);
setMenuList(filterMenuList);
});
const addToCart = React.useCallback((data) => {
setCart([...cart, data]);
});
const removeFromCart = React.useCallback((itemId) => {
const cartItems = cart;
cartItems.map((item) => {
if (item.CODE == itemId) {
const filtered = cartItems.filter(
(cartItem) => cartItem.CODE != itemId
);
setCart(filtered);
}
});
});
useEffect(() => {
const titles = [];
const fetchData = async () => {
const response = await fetch(API);
const responseData = await response.json();
setData(responseData);
console.log(responseData);
// Filtering menu types
responseData.map((item) => titles.push(item.TYPE));
const filtered = titles.filter(
(item, index, self) => self.indexOf(item) == index
);
setMenuTitle(filtered);
const initialMenuList = responseData.filter(
(item) => item.TYPE == filtered[0]
);
setTitle(initialMenuList[0].TYPE);
setCurrency(initialMenuList[0].CURRENCY);
setMenuList(initialMenuList);
};
fetchData();
}, []);
return (
<Layout
menuTitles={menuTitle}
menuList={menuList}
data={data}
callMenuList={(titleProp) => callMenuList(titleProp)}
addToCart={(data) => addToCart(data)}
removeFromCart={(itemId) => removeFromCart(itemId)}
cartData={cart}
title={title}
currency={currency}
/>
);
}
export default React.memo(App);
I have to add this as an answer even though it's more of a comment because so many people become overzealous about preventing renders when it doesn't matter.
React is very fast out of the box - it is supposed to be re-rendering components when props don't change. But, just to illustrate, you can design your components (using children) so that not everything re-renders all the time.
Compare these two stackblitz:
with children - C2 does NOT rerender
without children - C2 does rerender
But none of this actually matters, you should only look at preventing unnecessary renders if you see performance issues.
If you see logical issues that are fixed by preventing a re-render, then you've got a bug that you need to fix somewhere else.
If you aren't experiencing any performance or logic issues, then the answer to your question is to stop worrying about it.
You can use React.memo, but memoizing a component could easily end up being a performance penalty, rather than a win. Memoizing something isn't free.
I urge you to forget about this stuff unless you are seeing performance or logical errors.
Stop worrying, everything is functioning normally when your components re-render without props/state changes if their parents have re-rendered
If you set a new state in your layout component, it will re-run and re-render all the components in its JSX.
Don't worry, it is not the problem of React.
If you want your Header, Menu, Cart, Footer not to be re-render, read about React.PureComponent (for class), React.memo, or useMemo, useCallback (for funtional component).

Add user input to start of list using UseEffect with Redux and Axios

First things first, my code is working and pulling data from Redux like it should. All input boxes receives user input and submits it perfectly after submit (as seen in my console).
The only problem I'm having is figuring out how to send that information to the actual DOM in my useEffect(). Preferably I'd like it to display at the top of the current list (unshift()) instead of the bottom when new data is submitted by user.
I have been staring at this for so long that I'm pretty sure I'm probably over thinking it. Any help though would be greatly appreciated.
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';
const Posts = (props) => {
const [data, setData] = useState([]);
useEffect(() => {
if(setData(props.fetchPosts())){
props.posts.unshift(...props.post, props.newPost);
}
},[props.newPost])
console.log('new post',props.newPost)
const postItems = props.posts.map((item, index) => (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.body}</p>
</div>
))
return (
<div>
<h1>Posts</h1>
{postItems}
</div>
)
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts })(Posts);
there are two ways to solve this issue.
1. Update state value (props are immutable).
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';
const Posts = (props) => {
const [data, setData] = useState([]);
useEffect(() => {
if(props.fetchPosts()){
let dupData = [...data]
dupData.unShift(props.newPost)
setData(dupData)
}
},[props.newPost])
console.log('new post',props.newPost)
let updatedPosts = [data, ...props.posts]
const postItems = updatedPosts.map((item, index) => (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.body}</p>
</div>
))
return (
<div>
<h1>Posts</h1>
{postItems}
</div>
)
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts })(Posts);
Update redux state
If you simply render the postItems based on your Redux state, I think the issue should be solved. Dispatching an action in useEffect will change the Redux state which should automatically trigger a rerender of the component. You can also consider dispatching an action directly upon the submission of the form, this may make everything a bit cleaner.
Also, I would not recommend using the useState hook here as it is not necessary here if you're also using Redux (in this component). A good practice is to use the useState hook only for state that is local to the component and is not helpful to any others in your app.

react component is passing down the default value of useState instead of passing the API data

I have this API call in my parent component.
I am trying to pass down it's data as props to be consumed by their child components.
The data that the component is passing is the initial state of the variable instead of the API data. This messes up the rest of the application.
This is my code
import React, { useState, useEffect } from 'react';
import './App.css';
import Playlists from './Playlists';
import PlaylistFilter from './PlaylistFilter';
import axios from 'axios';
function App() {
const [filters, setFilters] = useState(['initial state'])
const [playlists, setPlaylists] = useState([])
useEffect( ()=> {
axios.get(`/api/filters`)
.then(res => setFilters(res.data.filters))
.catch(err => console.log(err))
}, [])
//console.log(filters)
useEffect( ()=> {
axios.get(`/api/playlists`)
.then(res => setPlaylists(res.data.filters))
.catch(err => console.log(err))
}, [])
//console.log(playlists)
return (
<div className="App">
<PlaylistFilter filters={filters} />
<Playlists playlists={playlists} />
</div>
);
}
export default App;
(also the URLs are like that because I have configured the proxy in my package.json)
when I was checking the API call with console.log it was running it a first time showing only the default values and then it printed the API call 3 more times like so
One option is to only render the app once both filters and playlists have been populated. Set their initial state to undefined or null, then use conditional rendering:
function App() {
const [filters, setFilters] = useState();
const [playlists, setPlaylists] = useState();
// ...
return (
<div className="App">
{
filters && playlists && (<>
<PlaylistFilter filters={filters} />
<Playlists playlists={playlists} />
</>)
}
</div>
);
}
This way, if PlaylistFilter and Playlists expect the initial prop passed down to be populated, they won't choke up on initial mount.

Why does my useEffect react function run when the page loads although I am giving it a second value array

Why is my useEffect react function running on every page load although giving it a second value array with a query variable?
useEffect( () => {
getRecipes();
}, [query]);
Shouldn't it only run when the query state variable changes? I have nothing else using the getRecipes function except of the useEffect function.
import React, {useEffect, useState} from 'react';
import './App.css';
import Recipes from './components/Recipes/Recipes';
const App = () => {
// Constants
const APP_ID = '111';
const APP_KEY = '111';
const [recipes, setRecipes] = useState([]);
const [search, setSearch] = useState('');
const [query, setQuery] = useState('');
const [showRecipesList, setShowRecipesList] = useState(false);
// Lets
let recipesList = null;
// Functions
useEffect( () => {
getRecipes();
}, [query]);
// Get the recipie list by variables
const getRecipes = async () => {
const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=3&calories=591-722&health=alcohol-free`);
const data = await response.json();
console.log(data.hits);
setRecipes(data.hits);
}
// Update the search constant
const updateSearch = e => {
console.log(e.target.value);
setSearch(e.target.value);
}
const runQuery = e => {
e.preventDefault();
setQuery(search);
}
// List recipes if ready
if (recipes.length) {
console.log(recipes.length);
recipesList = <Recipes recipesList={recipes} />
}
return (
<div className="App">
<form className='search-app' onSubmit={ runQuery }>
<input
type='text'
className='search-bar'
onChange={ updateSearch }
value={search}/>
<button
type='submit'
className='search-btn' > Search </button>
</form>
<div className='recipesList'>
{recipesList}
</div>
</div>
);
}
export default App;
Following this: https://www.youtube.com/watch?v=U9T6YkEDkMo
A useEffect is the equivalent of componentDidMount, so it will run once when the component mounts, and then only re-run when one of the dependencies defined in the dependency array changes.
If you want to call getRecipes() only when the query dependency has a value, you can call it in a conditional like so:
useEffect(() => {
if(query) {
getRecipes()
}
}, [query])
Also, as your useEffect is calling a function (getRecipes) that is declared outside the use effect but inside the component, you should either move the function declaration to be inside the useEffect and add the appropriate dependencies, or wrap your function in a useCallback and add the function as a dependency of the useEffect.
See the React docs for information on why this is important.
UseEffect hook work equivalent of componentDidMount, componentDidUpdate, and componentWillUnmount combined React class component lifecycles.but there is a different in time of acting in DOM.componentDidMount and useEffect run after the mount. However useEffect runs after the paint has been committed to the screen as opposed to before. This means you would get a flicker if you needed to read from the DOM, then synchronously set state to make new UI.useLayoutEffect was designed to have the same timing as componentDidMount. So useLayoutEffect(fn, []) is a much closer match to componentDidMount() than useEffect(fn, []) -- at least from a timing standpoint.
Does that mean we should be using useLayoutEffect instead?
Probably not.
If you do want to avoid that flicker by synchronously setting state, then use useLayoutEffect. But since those are rare cases, you'll want to use useEffect most of the time.

Categories