Home > OS >  React Native simple CRUD: how to make a page rerender when coming back from a detail page
React Native simple CRUD: how to make a page rerender when coming back from a detail page

Time:01-18

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 a FlatList 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 the ProfilesPage, 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:

  1. Is it true that by design when a page is re navigated it won't rerender?
  2. 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]);
  1. 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.
  2. 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}
            />

    // ...

CodePudding user response:

Instead of using useEffect, just use useFocusEffect

import { useFocusEffect } from '@react-navigation/native';

 useFocusEffect(useCallback(() => {
    setCurrentProfiles(ProfilesRepository.getAllProfiles());
}, []));

CodePudding user response:

Your problem can be solved simply through redux. Examples corresponding to this are as follows.

useSelector Example

To solve the current problem without using redux, you must bring the modified profile data.

//Repository.js
export const getAllProfiles = async (): Profile[] => {
    await initializeAsync();
    return profiles!;
};
//ProfilePage.js
const [currentProfiles, setCurrentProfiles] = React.useState([]);
...
useEffect(() => {
        const unsubscribe = props.navigation.addListener('focus', () => {
              ProfilesRepository.getAllProfiles().then(res => setCurrentProfiles(res))
        });

        return unsubscribe;
    }, [props.navigation]);
  •  Tags:  
  • Related