I'm new to ReactNative
and React
and there is a thing I don't understand: how to make an already navigated page re-render when a child page changes its state. Let me explain better:
I implemented these components/pages:
ProfilesPage
: a page with aFlatList
with some profiles (name, surname...). Also with a BottomSheet popup here you can create a new profile or delete one.ProfileDetailPage
: when you select a single Profile from theProfilesPage
, it navigates here. In this page you can edit the profile's name and surname or delete it.
In the ProfilesPage
everything works fine: I got the profiles array from my repository implementation and everytime a new profile is added or delete, I just use setState
to rerender the whole page.
The problem comes when I am in the ProfileDetailPage
: if a edit a profile detail name or surname or delete it and navigate back to the ProfilesPage
, the page won't rerender!
My questions are:
- Is it true that by design when a page is re navigated it won't rerender?
- Is there an hook to force rerender when the page is navigated? I tried the following behavior but I'm not happy with it because the changes are applied after the transition animation and you clearly see that content is update.
What I've tried (related to question 2)
useEffect(() => { const unsubscribe = props.navigation.addListener('focus', () => { console.log('FOCUS'); setRefresh(previousState => !previousState); // To force re-render setCurrentProfiles(ProfilesRepository.getAllProfiles()); }); return unsubscribe;}, [props.navigation]);
- Is
Redux
or another state management library what I'm searching for? It seems that it has hooks to rerender each page when the state changes. - How to do what I want without another library? It seems that Redux puts a lot of boilerplate.
My code:
ProfilesRepository
(Note that I keep profiles array cached, I don't see why I should re get it from the AsyncStorage
everytime)
const initializeAsync = async () => { if (!profiles) { const profilesRaw = await AsyncStorage.getItem(profilesKey); if (profilesRaw) { profiles = JSON.parse(profilesRaw).map((p) => Object.assign(new Profile(), p) ); } else { profiles = Array<Profile>(); } }};let profiles: Array<Profile> | null = null;initializeAsync();export const getAllProfiles = (): Profile[] => { return profiles!;};export const getProfileById = (profileId: number): Profile | undefined => { return profiles!.find((p) => p.id === profileId);};export const deleteProfileById = async (profileId: number) => { const profileToDelete = profiles!.find((p) => p.id === profileId); profiles = profiles!.filter((item) => item !== profileToDelete); await AsyncStorage.setItem(profilesKey, JSON.stringify(profiles));};
ProfilesPage
const ProfilesPage = (props) => { const [currentProfiles, setCurrentProfiles] = React.useState( // Pass only the function without calling it to use the lazy init overload // This is because otherwise everytime the bottomPopup does something, it redo getAllProfiles ProfilesRepository.getAllProfiles ); const [refresh, setRefresh] = React.useState(false); // I need this to re-render the profiles list when coming back from a view useEffect(() => { const unsubscribe = props.navigation.addListener('focus', () => { console.log('FOCUS'); setRefresh(previousState => !previousState); // To force re-render setCurrentProfiles(ProfilesRepository.getAllProfiles()); }); return unsubscribe; }, [props.navigation]); const [currentSelectedProfile, setCurrentSelectedProfile] = React.useState<Profile>(); const onDeleteSelectedProfileHandler = async () => { await ProfilesRepository.deleteProfileById( currentSelectedProfile!.id ); setCurrentProfiles(ProfilesRepository.getAllProfiles()); }; return (<View style={styles.screen}><FlatList data={currentProfiles} keyExtractor={(item, index) => item.id.toString()} renderItem={renderProfileItem} /> // ...