change props value of a child component causing too many renders - react-native

child component takes a prop. Now from button click of the parent, i want to continue updating the prop so that the child component can update itself with new calculated result
const childComponent = ({value}) =>{
const [total, updateTotal] = useState('enter 0 or higher value and hit calculate button');
const calculateTotal = () =>{
updateTotal(value * 0.25);
}
useEffect(()=>{
if (value>0){
calculateTotal();
}
}, [value]);
return (<Text>{total}</Text>)
}
ParentComponent:
const [value, updateValue]= useState(0);
const generateValue = ()=>{
//logic that generates a number
const numberGenerated = generateValue();
updateValue(numberGenerated);
}
return (<View><ChildComponent value={value}/> <Button onPress={generateValue}>Calculate</Button></View>)
But when I click the button, it says :
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

Maybe it's because you missed the arrow function:
<Button onPress={()=>generateValue()}>Calculate</Button>

Related

Re-render component everytime screen is opened react native

I'm kinda new to React Native. I'm using the getFamily() on my screen MyFamily but when I go to another screen there change the value of the Family and come back to my MyFamily screen then I don't see the changes.
I tried doing it with the useEffect but still nothing happens, also the log doesn't happen. How can I solve this?
export default function MyFamily({ navigation, props, person, inheritors }) {
console.log(getFamily());
let [family, setFamily] = useState(getFamily());
useEffect(() => {
console.log(getFamily());
setFamily(getFamily());
}, [getFamily]);
In the screen where I set the Family again I do this:
And I know that's correct because the Json that is shown shows the updated value.
import { setFamily } from '../../utilities/family';
setFamily(responseJson.family);
This is the way family is formulated:
let family = '';
export default family;
export function getFamily() {
return family;
}
export function setFamily(f) {
family = f;
}
React doesn't actually know that the value returned from the getFamily function changes each render. In the useState function, it's only used in the initial state, and the useEffect function never gets re-run because the getFamily function itself doesn't ever change and re-trigger the useEffect. You have to change the getFamily() function to use a state that's stored in a parent component and pass it into the MyFamily component as a prop.
e.g.
// the parent component that renders the MyFamily screen
function Router() {
const [family, setFamily] = useState('')
return (
<Navigator>
<Screen component={<MyFamily family={family} setFamily={setFamily} />
<Screen component={<OtherComponent family={family} setFamily={setFamily} />
</Navigator>
}
)
}
And then from MyFamily:
function MyFamily({ family }) {
console.log(family); // this should be updated
}
and from OtherComponent:
function OtherComponent({ setFamily }) {
return (<Button onClick={() => setFamily('newFamily')>Change family</Button>)
}

Infinite loop with useEffect hook with useSelector

I have a normalized Redux store in my React Native application.
The structure of my reducer is:
{
byId: {},
allIds: []
}
In my component, I get the slice of Redux state using the useSelector hook:
const categories = useSelector((state: AppState) =>
state.products.allIds.map((id) => state.categories.byId[id.toString()])
);
The logic in the useSelector just converts the byId object into an array.
The infinite looping occurs when I set the categories array as a dependency:
const [values, setValues] = useState<any[]>([]);
useEffect(() => {
setValues([{ id: 1 }]);
console.log("logging");
}, [categories]);
Not sure what is the problem. I believe it's the useSelector logic that converts the objects into an array.
EDIT:
Full component code:
// React
import React, { useEffect, useState } from "react";
// React redux
import { useSelector } from "react-redux";
import { AppState } from "#reducers/rootReducer";
// Logic
import ProductsScreenLogic from "./ProductsScreen.logic";
// Common components
import ScreenView from "#common/screen/Screen.view";
// Components
import NewProductModalView from "#components/products/new-product-modal/NewProductModal.view";
import ProductsTabsView from "#components/products/products-tabs/ProductsTabs.view";
import ProductListView from "#components/products/products-list/ProductList.view";
import CategoryListView from "#components/products/category-list/CategoryList.view";
const ProductsScreenView: React.FC = () => {
const { displayProductList, setDisplayProductList, products } =
ProductsScreenLogic();
// Makes the categories ById object into an array of categories
const categories = useSelector((state: AppState) => state.categories.allIds.map((id) => state.categories.byId[id.toString()])
);
const [values, setValues] = useState<any[]>([]);
useEffect(() => {
setValues([{ id: 1 }]);
console.log("logging");
}, [categories]);
return (
<>
<NewProductModalView />
<ScreenView></ScreenView>
</>
);
};
export default ProductsScreenView;
The problem is that your selector always returns a new reference (because of the call to map). You could instead use createSelector which will memoize it and only return a new reference when something inside either allIds or byId changes:
const selectAllCategories = createSelector(
(state: AppState) => state.categories.allIds,
(state: AppState) => state.categories.byId,
(categoriesIds, categoriesById) => categoriesIds.map((id) => categoriesById[id.toString()])
);
But ideally you should avoid such selectors that go through the whole byId object, because it kind of negates the benefits of having a normalized state. You should rather have a parent component that only selects state.categories.allIds, and then passes the ids as props to child components, and every child component will select its own state.categories.byId[id]. This way if a category changes, only the corresponding child component will rerender, instead of having the parent and all of the children rerender.
In useEffect you update the state , wich cause render , render call useSelector wich every time return new array for useEffect , wich cause update the state. To fix you can remove categories from useEffect dependency array

How to trigger a hook by pressing a button in React Native?

I'm making an app using React Native and facing a problem while making a countdown timer.
I needed to use setInterval in order to implement this, but I found that setInterval will not act what I have expected.
So I used custom Hooks from this post, and here's the code:
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
After that, I made a button to make a timer to be triggered:
const [leftTime, setTime] = useState(300000);
const triggerTimer = () => {
useInterval(() => setTime(leftTime - 1), 1000);
};
return (
<>
<Text>{leftTime}</Text>
<TouchableOpacity onPress={() => triggerTimer()}
<Text>Start Countdown!</Text>
</TouchableOpacity>
</>
);
However, I got an error saying 'Invalid hook call. Hooks can only be called inside of the body of a function component.'
I already know that Hooks must be called at the very top of the function component, but is there any way to trigger a hook by pressing a button in the app?
Also, I want to stop the timer automatically and do something after that when the state 'leftTime' becomes 0.

how to change params value passed to axios route to change data react native?

I am building a kind of book app (Holy Quran )... user will go for a list of Surahs, each Surah contains around 5 - 50 pages. I managed navigating user from the list to first page of each Surahs.. and through getting an api request data for first page will be shown and this is the code in the showScreen
const [quran, setQuran] = useState([]);
const page = navigation.getParam('page');
const name = navigation.getParam('name');
let pageNumber = page;
useEffect(() => {
Quran();
}, []);
const Quran = async () => {
const response = await QuranApi.get(`/${pageNumber}/quran-uthmani`);
setQuran(response.data.data.ayahs);
}
so let's imagine that first page is page number 200, I am looking for some way so when user clicks go to page 201 or 199 (next or previous) and refetching the data so show for him requested page
I need some help here please and thanks in advance
Basically you need to add some sort of button or any element in your 'markup section' which will trigger next/previous action. For example:
// The following line makes react listen for changes in page state variable.
// If you use setPage() anywhere, react will auto update all the components
// where page variable is used. You don't need to manually do it
const [page, setPage] = useState(nav.getParam('page'))
// ....
// This function does exactly that
const handleClick = event => {
if(event.target.value === 'next-page') // If next button was pressed
setPage(page + 1) // We increment the page state variable by 1
else if(event.target.value === 'prev-page') // If prev button was pressed
setPage(page - 1) // We decrement the page state variable by 1
// By doing so, react will auto update every element which uses this variable
}
// .....
// We tell react, if [page] state variable changes by user clicking
// next or previous button, fetch data from the api using updated
// page number and using that, we update the [quran] variable using
// 'setQuran()' function
useEffect(async () => {
const response = await QuranApi.get(`/${page}/quran-uthmani`)
setQuran(response.data.data.ayahs)
}, [page] );
//......
// markup section
return(
//....
<Button onClick={handleClick} value="next-page">Next Page {page + 1}</Button>
<Button onClick={handleClick} value="prev-page">Prev Page {page - 1}</Button>
//....
)
Thank you dear #Sayed it finally works but I have to make some modifications with the same idea to be
const [page, setPage] = useState(navigation.getParam('page'))
const handleRightClick = () => {
setPage(parseInt(page) + 1)
};
const handleLeftClick = () => {
setPage(parseInt(page) - 1);
}
useEffect(() => {
return () => {
console.log("cleaned up");
};
}, []);
useEffect(() => {
Quran();
}, [page]);
<Button title = 'Next Page' onPress = {handleRightClick}/>
<Button title = 'Prev-page' onPress ={handleLeftClick} />
So it's working well in that case without an error
Try this small change...
const [page,setPage]=useState(nav.getParam('page'))
useEffect(() => {
Quran();
}, [page]);
by passing an empty array as a second parameter to the useEffect function, you are telling react to only execute the side effect once (at mount time).
However if that array contains a list of state variables to watch for. React will only re-run the side effect if one of the items in this array changes.
The above mentioned change in useEffect would make your useEffect sensitive to any change in the state variable (page) passed to the useEffect array. As you pass new pagenumbers in the url as a parameter, it would result in calling useEffect everytime as the page state variable is set.
Hope this helps!

How to get the last value emit from vue event bus?

I'm using vue event bus, for example in resize window:
resize.ts:
import Vue from 'vue';
export const windowResize = new Vue();
when I emit value:
const innerWidth = …;
const innerHeight = …;
resize.$emit('resize', { innerWidth, innerHeight });
when I received value:
resize.$on('resize', ({ innerWidth, innerHeight }) => { … });
My question is it possible to get the get the last value has been dispatched?
for example say in lazy component I want to have the width and height that calculate in the parents components? I think something like subject behavior (not BehaviorSubject) in rxjs…
In this lazy component I want to write
resize.$on('resize', ({ innerWidth, innerHeight }) => { … });
but I don't want to trigger the resize event.