Francesco Bonizzi
Francesco Bonizzi

Reputation: 5302

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

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:

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}
            />

    // ...

Upvotes: 0

Views: 161

Answers (2)

hong developer
hong developer

Reputation: 13926

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]);

Upvotes: 1

Hagai Harari
Hagai Harari

Reputation: 2877

Instead of using useEffect, just use useFocusEffect

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

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

Upvotes: 1

Related Questions