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!
Related
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.
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
I am new in react-native and the hooks. In my react-native project, I have one screen needs to query data from backend, then, there are some code using the backend returned data should only be run once when the screen mounted. This is what I did (I am using react-query for data fetching from backend):
const MyScreen = ()=> {
// fetch data from backend or cache, just think this code gets data from backend if you don't know react-query
const {status, data, error} = useQuery(['get-my-data'], httpClient.fetchData);
// these code only need to run once when screen mounted, that's why I use useEffect hook.
useEffect(() => {
// check data
console.log(`data: ${JSON.stringify(data)}`);
// a function to process data
const processedData = processeData(data);
return () => {
console.log('Screen did unmount');
};
}, []);
return (<View>
{/* I need to show processed data here, but the processedData is scoped in useEffect hook & I need to have the process data function in useEffect since only need it to be run once */}
</View>)
}
My questions are:
Does react native guarantee the order that the code above useEffect is invoked always first after that run the useEffect code?
As you can see the processedData is returned inside useEffect, how can I pass that return to the layout code to render the processed data?
First question: useEffect is run after the component has fully rendered and does not block the browser's painting. Consider this example:
export default function App() {
console.log("I am code from the app")
React.useEffect(() => {
console.log("I am the effect")
})
React.useLayoutEffect(() => {
console.log("I am the layout effect")
})
return (
<div className="App">
{console.log("I am inside the jsx")}
<h1>Hello World</h1>
</div>
);
}
Will output:
I am code from the app
I am inside the jsx
I am the layout effect
I am the effect
So the useEffect callback will happen as the last thing, after everything else has been done.
Second Question: You can only pass that by using useState and setting the state inside your effect:
const [data, setData] = React.useState()
React.useEffect(() => {
// Your other code
const processedData = processeData(data);
setData(processedData)
}, [setData])
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
How can I assert that a button is disabled in React Native Testing Library? I would imagine something like:
expect(getByRole('button')).toBeDisabled()
but RNTL doesn't provide toBeDisabled assertion.
this is a common issue due to RN nature. I have managed to reach my goal by just testing the actual effect of callback function, not just comparing the number of calls or something like that...
describe('<Button /> - ', () => {
let state = false
const onPressMock = jest.fn(() => {
state = !state
})
const props: ButtonProps = {
text: 'Submit',
onPress: onPressMock
}
it('should become disabled', () => {
// act: render container
const { container } = render(<Button {...props} isDisabled />)
// assert 1: check if button receives {isDisabled}
expect(container.props.isDisabled).toEqual(true)
// act2: fire callback
fireEvent(container, 'onPress')
// assert 2: "state" should remain as false.
expect(state).toEqual(false)
})
})
make sure that your button looks like:
const isBlockedInteraction: boolean = isLoading || isDisabled;
return (
<TouchableOpacity
onPress={!isBlockedInteraction && onPress}
disabled={isBlockedInteraction}
{...props}
/>
)
Quite a simple try toHaveProperty method, I hope that helped.
example:
import React from 'react'
import {fireEvent, render} from '#testing-library/react-native';
import {SignInScreen} from './SignInScreen';
it('disabled button if email and password are empty', () => {
const screen = render(<SignInScreen />);
const button = screen.getByText('Login');
// screen.debug();
// console.log(button.props);
expect(button.props).toHaveProperty('disabled', true);
});