Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

React. Rerendering nested array

I am building quiz app and there is a problem that i have to shuffle question options but they are located in the nested question array. Here is the interface:

export interface IConnectionsQuestionData extends IQuestionData {
    type: 'connections';
    questionsTitle: string;
    answersTitle: string;

    pairs: {
        questionOption: IConnectionsOption;
        answerOption: Maybe<IConnectionsOption>;
    }[];
} 

export interface IConnectionsOption {
    key: IDType;
    value: string;
    type: 'answer' | 'question'
}

So in order to shuffle options i have created custom useShuffle hook:

export const useShuffle = <T>(array: T[]): T[] => {
    return useMemo(() => {
        return shuffleArray(array);
    }, [array]) 
}

In Question component I get question from props. I use this hook like this:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

 const shuffledLeftSideOptions = useShuffle(question?.pairs.map(pair => pair.questionOption) ?? [])
 const shuffledRightSideOptions = useShuffle(question?.pairs.map(pair => pair.answerOption) ?? [])

But every time component need to be rerendered when i choose the option, options array shuffles again on every render. I tried to test it and it works fine with pairs array but reshuffles with question or answer options.

>Solution :

You’ve only memoized the shuffling, not the map operation. So you get a new array every time, because map produces a new array every time, and so useShuffle has to do its work every time.

If you need to do the map, you’ll need to memoize that as well, e.g.:

const mappedLeftOptions = useMemo(
    () => question?.pairs.map((pair) => pair.questionOption) ?? [],
    
);
const mappedRightOptions = useMemo(
    () => question?.pairs.map((pair) => pair.answerOption) ?? [],
    
);
const shuffledLeftSideOptions = useShuffle(mappedLeftOptions);
const shuffledRightSideOptions = useShuffle(mappedRightOptions);

But, beware that the memoization provided by useMemo provides only a performance optimization, not a semantic guarantee. (More in the documentation.) But your shuffling code is relying on it as though it were a semantic guarantee.

Instead, when you need a semantic guarantee, use a ref:

export const useShuffle = <T>(array: T[]): T[] => {
    const arrayRef = useRef<T[] | null>(null);
    const shuffleRef = useRef<T[] | null>(null);
    if (!Object.is(array, arrayRef.current)) {
        shuffleRef.current = null;
    }
    if (!shuffleRef.current) {
        arrayRef.current = array;
        shuffleRef.current = shuffleArray(array);
    }
    return shuffleRef.current;
};

Or it’s probably cleaner with a single ref:

export const useShuffle = <T>(array: T[]): T[] => {
    const ref = useRef<{ array: T[]; shuffled: T[] } | null>(null);
    if (!ref.current || !Object.is(ref.current.array, array)) {
        ref.current = {
            array,
            shuffled: shuffleArray(array),
        };
    }
    return ref.current.shuffled;
};
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading