Setting a ref from with another useRef - react-native

I have a flatlist and inside the onViewableItemsChangedRef I want to set the value of another ref inside the component like so:
const MyComponent= (): JSX.Element => {
const currentId = useRef('');
const onViewableItemsChangedRef = useRef(({ viewableItems }) => {
// I want to set currentId in here
});
return (
<FlatList
// other props here like data, renderItem, etc.
ref={ref}
keyExtractor={(item) => item.id
onViewableItemsChanged={onViewableItemsChangedRef.current}
/>
);
};
But when I try to set currentId.current inside of onViewableItemsChangedRef, it's always undefined.
I tried switching the useRef to useCallback instead, but I would keep getting the error listed here React Native FlatList onViewableItemsChanged callback encounter error after state changed rerender
Is there another way of doing this?

onViewableItemsChangedRef.current contains the first function definition which is bound to the first values, instead you can use useCallback calling a ref function like this:
const onViewableItemsChangedRef = useRef()
onViewableItemsChangedRef.current = ({ viewableItems }) => {
// I want to set currentId in here
});
onViewableItemsChanged = useCallback((...args)=>onViewableItemsChangedRef.current(...args),[])
// now use onViewableItemsChanged instead of onViewableItemsChangedRef.current
Of course, you can use the effective arguments instead of ...args

Related

React Native component not re-rendering when state is updated

I have a component in my React Native app that displays a list of pending friends. This component makes a GET request to an API to retrieve the list of pending friends and then uses a useEffect hook to map over the list and render each friend as a Pressable component. I'm also using the useFocusEffect hook to make the get request when the screen renders.
Here is the relevant code for the component:
const Pending = () => {
const [pendingFriends, setPendingFriends] = useState(null)
let pendingFriendsRender = []
useEffect(() => {
if (pendingFriends !== null) {
for(let i = 0; i < pendingFriends.length; i++) {
pendingFriendsRender.push(
<Pressable key={i} style={styles.friend}>
<Text style={styles.friendText}>{pendingFriends[i].username}</Text>
</Pressable>
)
}
}
}, [pendingFriends])
useFocusEffect(
useCallback(() => {
async function fetchData() {
const accessToken = await AsyncStorage.getItem('accessToken')
try {
const res = await instance.get('/pending_friends', {
headers: { authorization: `Bearer ${accessToken}`},
})
setPendingFriends(res.data)
} catch (error) {
console.log(error.response.status)
}
}
fetchData()
}, [])
)
return(
<View style={styles.friendsContainer}>
{pendingFriendsRender}
</View>
)
}
I have tried using an empty array as the second argument in the useEffect hook but that approach has not worked. I also tried removing the useEffect hook so the if statement with the for loop stands at the top of the component without the hook, that worked but I can't update it in this way after the component rendered. I checked the API and it is returning the correct data.
The first useEffect you have really isn't needed. You can map through your state inside of your JSX. Anytime the state changes, the component will be re-rendered:
// Need a default here, could also set some loading state when fetching your data
if(pendingFriends === null) {
return <>Loading...</>
}
return(
<View style={styles.friendsContainer}>
{pendingFriends.map((friend, i) => {
return (
<Pressable key={friend.id} style={styles.friend}>
<Text style={styles.friendText}>{friend.username}</Text>
</Pressable>
)
})}
</View>
)
Also keep in mind, it's not recommended to use the index as the key, it can lead to unexpected bugs and issues. Instead use a unique string key (as shown above).
React: using index as key for items in the list
pendingFriendsRender should be the state:
const [pendingFriendsRender, setPendingFriendsRender] = useState([])
Instead of
let pendingFriendsRender = []
Then just clone the array so you lose reference to the object and add the new element
const newPendingFriendsRender = [...pendingFriendsRender, newElement]
or you can use FlatList to make it easier.

Prevent Child Rerendering if Parent is Rerendered Using Hooks

My bestSellerDummy data doesn't change, so I'd like to prevent the same Product child to be rerendered if parent rerenders. I have tried using useMemo in parent and React.memo in child but no luck, it's still showing log 'Rendering Product component..' every time parent rerenders. What am I missing here? Please advice.
Note: Parent is expected to be rerendered every time I call addToCart function (of CartContext) in a Product component.
I'm using CartContext, maybe related to this, I'm not sure. Here is the sandbox: https://codesandbox.io/s/dazzling-moore-po1c6?file=/src/App.js
Home.tsx
const [bestSellerDummy] = useState(
[...new Array(5)].map((item, key) => ({
id: key,
imageUri:'https://1.jpg',
name: 'My Dummy 1',
price: 25,
})),
);
const bestSellers = useMemo(() => {
return bestSellerDummy.map((productDummy, key) => {
return (
<Product key={key} product={productDummy} />
);
});
}, [bestSellerDummy]);
return (
...
{bestSellers}
...
)
Product.tsx
const Product: FunctionComponent<IProductProps> = (
productProps,
) => {
...
console.log('Rendering Product component..');
...
}
export default React.memo(Product);
=== EDIT: MY VERSION OF ANSWER ===
Finally! After playing around with useCallback, useMemo, fast-memoize plugin.. What suits the best for me is using useReducer in Context combine with wrapping the expensive component with React.memo. I think this is the most clean and elegant way to optimize child components. Working sandbox is here: https://codesandbox.io/s/eloquent-albattani-8x7h9?file=/src/App.js
Since you are using useContext, your component will always re-renders.
When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.
Reference: https://reactjs.org/docs/hooks-reference.html#usecontext
I was trying to refactor your code using the 2nd strategy pointed from the docs: https://github.com/facebook/react/issues/15156#issuecomment-474590693.
However, I soon realized that the addToCart function has cartItems as its dependency, so whenever cartItems changes, addToCart changes and it's kind of impossible to avoid re-renders since every Product component use addToCart function.
That leads me to the use of useReducer because React guarantees that its dispatch is stable and won't change during re-renders.
So here's the working Codesandbox: https://codesandbox.io/s/red-feather-dc7x6?file=/src/App.js:786-797
Wrap BestSellers component with React.memo too. Don't use useMemo to avoid unnecessary component updating because it may cause bugs. It is used for computing expensive values.
Source: https://reactjs.org/docs/hooks-reference.html#usememo
This is the best way to clear your concepts about useCallback, useMemo and useEffect.
App.js
import Child1 from "./Child1";
import Child2 from "./Child2";
import { useState, useEffect, useMemo, useCallback } from "react";
function App() {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
console.log("Parent");
const printx = useCallback(() => {
console.log("x:" + x);
}, [x]);
useEffect(() => {
printx();
console.log("-------");
}, [printx]);
const child1 = useMemo(() => {
return <Child1 x={x} />;
}, [x]);
const child2 = useMemo(() => {
return <Child2 y={y} />;
}, [y]);
return (
<div className="App">
<h1>Parent</h1>
<button onClick={() => setX(x + 1)}>X+</button>
<button onClick={() => setY(y + 1)}>Y+</button>
{child1}
{child2}
</div>
);
}
export default App;
Child1.js
const Child1 = ({ x }) => {
console.log("Child1");
return (
<div>
<h1>Child 1:{x}</h1>
</div>
);
};
export default Child1;
Child2.js
const Child2 = ({ y }) => {
console.log("Child2");
return (
<div>
<h1>Child 2:{y}</h1>
</div>
);
};
export default Child2;
Try this way
const [bestSellerDummy, setBestSellerDummy] = useState([]); // default empty
// get data from `useCallback`
const sellerData = React.useCallback(
() => {
return [...new Array(5)].map((item, key) => ({
id: key,
imageUri:'https://1.jpg',
name: 'My Dummy 1',
price: 25,
}))
}, []
);
useEffect( () => {
setBestSellerDummy( sellerData() ); // set data when screen rendered from `useCallback`
}, [])
const bestSellers = useMemo(() => {
// ....
}, [bestSellerDummy]);
return (
// ...
{bestSellers}
// ...
)
The thing is you are using dynamic index for key . when you used dynamic key always react re render this .So use product id or some unique key for this then problem will be solved . I also have same problem and i resolved it

wait for useState to set values ​and then call function

I'm using react native without expo, when trying to set a value with UseState it doesn't set immediately, and I can't get the values ​​in another function.
const [gridData, setGridData] = useState([]);
useEffect(() => {
getGridApi().then((response)=>{
setGridData(response);
pressed('Mon', 0);
})
}, []);
const pressed = async (category, index) => {
console.log(gridData); // empty
}
How can I make it wait to set and then call the function pressed()
you can use this package or you can your own custom hook for this. unfortunately react don't provide useState With Callback functionality
Example:
import useStateWithCallback from 'use-state-with-callback';
const App = () => {
const [count, setCount] = useStateWithCallback(0, count => {
if (count > 1) {
console.log('Threshold of over 1 reached.');
} else {
console.log('No threshold reached.');
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
Cioa, unfortunately with hooks, setting state is async and you cannot get the last value in this way. But you can use another useEffect hook to retrieve any changes of state variable.
Try this:
useEffect(() => {
console.log(gridData); // this shows the last value of gridData setted by the other useEffect
}, [gridData]);
But pay attention: this useEffect I worte will be triggered every time gridData changes his value!

Prevent an element from rerendering on setState in React Native

I am using setState in order to dynamically update an image's source when a button is pushed in React Native. However, I am also using the TypeWriter library which types out text with a special 'typewriter' animated effect.
When my setState is called to change the element and the page is rerendered, TypeWriter types the text out again. I don't want this. Is there a way to exclude my TypeWriter text from being rerendered?
Code snippet:
export const AccountScreen = ({ navigation }) => {
this.state = {
img1Src: require('../assets/img/token.png')
}
const [state, setState] = useState(this.state);
changeImgSrc = () =>{
setState({
img1Src: require('../assets/img/X2.png')
})
}
return (
<TypeWriter> //I don't want this to re-render on setState
<Text>My Account</Text>
</TypeWriter>
<Animatable.Image source={state.img1Src}/>
<Button onPress={this.changeImgSrc}>
Click me!
</Button>
etc...//
export const NewComponent = ({ navigation }) => {
const [state, setState] = useState({
img1Src: require('../assets/img/token.png')
});
changeImgSrc = () =>{
setState({
img1Src: require('../assets/img/X2.png')
})
}
return (
<Animatable.Image source={state.img1Src}/>
<Button onPress={this.changeImgSrc}>
Click me!
</Button>
etc...//
You can make a new component is called NewComponent. Then,
export const AccountScreen = ({ navigation }) => {
return (
<TypeWriter> //I don't want this to re-render on setState
<Text>My Account</Text>
</TypeWriter>
<NewComponent />
etc...//
Also, you cannot use this in the functional component. In addition, if you want to call a function in functional component, you must use it with useCallback.

How to use useState variable in StackNavigator

I'm fetching a list of categories via the useEffect Hook:
useEffect(() => {
fetch(
`/read.php?catId=${catId}`
)
.then(res => res.json())
.then(res => {
setProducts([...products, ...res.data]);
});
}, []);
and store the data in a useState Hook
const [products, setProducts] = useState([]);
The {catId} I'm using in the URL is forwarded via routeName from the previous screen and I get it with getParam like that:
const catId = props.navigation.getParam("categoryId");
That's working great for inside of the functional component. The problem is, I also need props of the 'products' variable in my StackNavigator. Therefore I tried to use navigationData function of navigationOptions, once again getParam and use the find method on the products variable:
CategoryProductsScreen.navigationOptions = navigationData => {
const catId = navigationData.navigation.getParam("categoryId");
const selectedCategory = products.find(cat => cat.id === catId);
return {
headerTitle: selectedCategory.category_name
};
};
Unfortunately the 'products' variable is not available outside of the functional component. I can't also use useEffect outside to fetch my data again, obviously.
Is there a way to get data from my useState variable in StackNavigator?
Thank you