Gryphon Patlin
Gryphon Patlin

Reputation: 1

Is there a better way to operate with the next state when using react + immer after a setState call?

I'm using Typescript, React, and Immer to save states, which includes a profile list under which each profile has multiple options (stored as profile.options: Option[]). I would like to, while saving options, simply make sure the active profile has the correct option data, and then save the active profile.

const { updateProfileList, getProfile } = useProfiles();
const [ activeProfileId, setActiveProfileId ] = useState<string>('dev');
const [ activeOptionId, setActiveOptionId ] = useState<string>("1");

const [ activeProfile, setActiveProfile ] = useImmer(getProfile(activeProfileId));
const [ activeOption, setActiveOption ] = useImmer(getOption(activeOptionId, activeProfileId));

const saveActiveProfile = (id: string = activeProfileId) => {
    updateProfileList((draft) => { 
        const target = draft[draft.findIndex((x)=>x.id === id)] = activeProfile;
        target.id = id; // ID of location should not change!!!
    });
}

const saveActiveOption = (id: string = activeOptionId) => {
    setActiveProfile((draft) => { 
        const target = draft.options[draft.options.findIndex((x)=>x.id === id)] = activeOption;
        target.id = id; // ID of location should not change!!!
    });
    // saveActiveProfile();
}

However, because of the way the React state works, I suspect that this will not have the intended behavior because saveActiveProfile() references the activeProfile state, which will not be updated until after the current render is finished calculating. Is there a better way of doing this besides saving Immer's current(draft) and passing it into the save function?

Edit: Here are the alternative functions:

const saveProfile = (id: string, profile: Profile) => {
    updateProfileList((draft) => { 
        const target = draft[draft.findIndex((x)=>x.id === id)] = profile;
        target.id = id; // ID of location should not change!!!
    });
}

const saveActiveProfile = (id: string = activeProfileId) => {
    saveProfile(id, activeProfile);
}

const saveActiveOption = (id: string = activeOptionId) => {
    let intermediateActiveProfile = activeProfile;
    updateActiveProfile((draft) => { 
        const target = draft.options[draft.options.findIndex((x)=>x.id === id)] = activeOption;
        target.id = id; // ID of location should not change!!!
        intermediateActiveProfile = current(draft);
    });
    saveProfile(activeProfileId, intermediateActiveProfile);
}

Upvotes: 0

Views: 48

Answers (1)

Suren Poghosyan
Suren Poghosyan

Reputation: 68

⁤Instead of relying on React's asynchronous state updates, you can pass the updated state directly to your save functions.

const saveProfile = (id: string, profile: Profile) => {
    updateProfileList((draft) => { 
        const targetIndex = draft.findIndex((x) => x.id === id);
        draft[targetIndex] = { ...profile, id };
    });
}

const saveActiveProfile = (updatedProfile?: Profile) => {
    const profileToSave = updatedProfile || activeProfile;
    saveProfile(activeProfileId, profileToSave);
}

const saveActiveOption = (id: string = activeOptionId) => {
    let updatedProfile: Profile;
    setActiveProfile((draft) => { 
        const targetIndex = draft.options.findIndex((x) => x.id === id);
        draft.options[targetIndex] = { ...activeOption, id };
        updatedProfile = current(draft);
    });
    saveActiveProfile(updatedProfile);
}

also check out this State as a Snapshot topic in React's documentation, it will help you better to understand, how states work.

Upvotes: 0

Related Questions