-1

I return this content from my ReviewPictures.tsx screen.

My photos list

My problem is when i take a new Photo with my Photo Button

enter image description here

My SetState erase the previous Photos List

enter image description here

The Code :

ReviewPictures.tsx : Here i return my photos and my PhotoIcons Button which can retake a photo if the user want so.

import {NavigationProp, RouteProp, useNavigation, useRoute} from '@react-navigation/native'; import React, {useContext, useEffect, useState} from 'react'; import {Image, StyleSheet, Text, View, ScrollView} from 'react-native'; import {Button} from 'react-native-paper'; import AnalyseContext from '../../contexts/Photoshoot/AnalyseContext'; import {Step, StepAlias} from '../../domain/photoShoot/Step'; import {PhotoshootStackParamList} from '../../navigation/PhotoshootNavigator'; import {IconButton, Colors} from 'react-native-paper'; import I18n from 'i18n-js'; import {AppStackParamList} from '../../navigation/AppNavigator'; import {OnboardedStackParamList} from '../../navigation/OnboardedNavigator';

const styles = StyleSheet.create({   image: {
    width: 150,
    height: 150,   },   container: {
    flex: 1,   },   box: {height: 250},   row: {flexDirection: 'column', alignItems: 'center'}, });

const ReviewPictures: React.FC = () => {
  const {params} = useRoute<RouteProp<OnboardedStackParamList, 'Photoshoot'>>();

  const {picturePaths} = useContext(AnalyseContext);
  const [content, setContent] = useState<JSX.Element[]>([]);   
  const navigation = useNavigation<NavigationProp<PhotoshootStackParamList>>();
  const Appnavigation = useNavigation<NavigationProp<AppStackParamList>>();

  const toRedo = !params?.redo;

  useEffect(() => {
    setContent(setPageContent());
  }, []);

  const setPageContent = () => {
    const c: JSX.Element[] = [];

    picturePaths.forEach((value: string, step: StepAlias) => {
      console.log(value);
      c.push(
        <View style={[styles.box, styles.row]}>
          <Text>{I18n.t(`${step}`)}</Text>
          <Image style={styles.image} source={{uri: value}} />
          <IconButton
            icon="camera"
            color={Colors.blue500}
            size={40}
            onPress={() => {
              console.log('Pressed');
              Appnavigation.navigate('Logged', {
                screen: 'Onboarded',
                params: {screen: 'Photoshoot', params: {redo: toRedo, onboarded: false, review: step}},
              });
            }}
          />
        </View>,
      );
    });
    return c;   };

  return (
    <>
      <ScrollView style={styles.container}>{content}</ScrollView>
      <Button onPress={() => navigation.navigate('Mileage')}>Valider</Button>
    </>   ); };

export default ReviewPictures;

And Photoshoot.tsx which take and store the photos :

import {RouteProp, useRoute} from '@react-navigation/native';
import I18n from 'i18n-js';
import React, {useContext, useEffect, useState} from 'react';
import {StatusBar} from 'react-native';
import Loading from '../../components/UI/loading/Loading';
import AnalysesContext from '../../contexts/Analyses/AnalysesContext';
import {UserContext} from '../../contexts/User/UserContext';
import {ONBOARDING_SCENARIO, WEAR_SCENARIO} from '../../domain/photoShoot/Scenario';
import {fromStepAlias, Step} from '../../domain/photoShoot/Step';
import {OnboardedStackParamList} from '../../navigation/OnboardedNavigator';
import PhotoshootNavigator from '../../navigation/PhotoshootNavigator';
import {
  createRetakeScenario,
  extractReferenceTyresToRetake,
  extractWearTyresToRetake,
} from '../../utils/retakeTyres.utils';

/**
 * Wrap AnalyseWearNavigator.
 * Ensures steps and/or referenceId are loaded before use of AnalyseWearNavigator.
 * Meanwhile, it shows a waiting loader.
 *
 * We have to wait before mounting AnalyseWearNavigator. Otherwise, Navigator take configuration it had at first mount and don't care if you update state later.
 */
const Photoshoot = () => {
  const {params} = useRoute<RouteProp<OnboardedStackParamList, 'Photoshoot'>>();
  const {user, vehicleId} = useContext(UserContext);
  const {wear, reference, fetchingAnalysis} = useContext(AnalysesContext);
  const [isLoading, setIsLoading] = useState(true);
  const [referenceId, setReferenceId] = useState<number>();
  const [steps, setSteps] = useState<Step[]>([]);

  useEffect(() => {
    if (!fetchingAnalysis) {
      if (user && vehicleId) {
        if (params?.redo) {
          loadRetakeScenario();
        }
        if (params?.review) {
          console.log('Joué ' + params?.review);
          loadReviewScenario();
        } else {
          loadScenario();
        }
      }
    }
  }, [user, vehicleId, wear, reference, params, fetchingAnalysis]);

  const loadReviewScenario = () => {
    if (params?.review) {
      setSteps([fromStepAlias(params.review)]);
      setIsLoading(false);

      // HERE THE PROBLEM WITH SETSTEPS
    }
  };

  const loadRetakeScenario = () => {
    const wearTyresToRetake = extractWearTyresToRetake(wear?.tyreWears);
    const referenceTyresToRetake = extractReferenceTyresToRetake(reference?.tyreReferences);
    const scenario = createRetakeScenario(wearTyresToRetake, referenceTyresToRetake);
    setSteps(scenario);
    setIsLoading(false);
  };

  const loadScenario = async () => {
    setReferenceId(reference?.id);
    setSteps(!!params?.onboarded ? WEAR_SCENARIO : ONBOARDING_SCENARIO);
    setIsLoading(false);
  };

  const content = isLoading ? (
    <Loading waitingText={I18n.t('ANALYSIS_PAGE.LOADING_MESSAGE')} />
  ) : (
    <>
      <StatusBar barStyle="dark-content" />
      <PhotoshootNavigator steps={steps} referenceId={referenceId} redo={!!params?.redo} />
    </>
  );
  return content;
};

export default Photoshoot;

Step.ts which guarantee the position of the tyre, it's a Tyre Recognition app :

import {PhotoType} from '../../models/PhotoType';
import {TyrePosition} from '../TyrePosition';

/** Step of a photo shoot */
export type Step = {
  tyre: TyrePosition;
  whichSideToTake: PhotoType;
};

export type StepAlias = `${TyrePosition}_${PhotoType}`;

export const toStepAlias = (step: Step): StepAlias => {
  return `${step.tyre}_${step.whichSideToTake}`;
};

export const fromStepAlias = (stepAlias: StepAlias): Step => {
  console.log(stepAlias.split('_'));
  const split = stepAlias.split('_');
  return {tyre: split[0] as TyrePosition, whichSideToTake: split[1] as PhotoType};
};

What's wrong with setStep ?

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Harkayn
  • 197
  • 2
  • 9
  • Does this answer your question? [Correct way to push into state array](https://stackoverflow.com/questions/37435334/correct-way-to-push-into-state-array) – crashmstr Mar 04 '22 at 17:38
  • Unfortunately i don't achieve to solve my problem with your suggestion – Harkayn Mar 04 '22 at 17:47
  • 1
    ***DO NOT STORE JSX IN REACT STATE***.... it is anti-pattern in React and a surefire way to have stale state enclosures, store data and render the UI from the data stored in state. Other than this, I think we need more complete code examples so see how these components interact. Is the issue in the `// HERE THE SETSTATE SUPRESS THE PREVIOUS CONTENT` comment of what I'm assuming is omitted code? – Drew Reese Mar 04 '22 at 17:51
  • That other question is exactly the problem. You set the state to a new array with one element. That does, of course and per intent, replace the previous state. Regardless of if you should or should not be storing JSX (don't, store the data to map into the components), your code intentionally ignores what was there. – crashmstr Mar 04 '22 at 19:54
  • @DrewReese Yes it's where i need help to make it works and thanks for the information about anti-pattern i didn't knew at all i'm editing to show the whole code – Harkayn Mar 04 '22 at 22:39
  • Well, can we see what your code is doing there where you need help? – Drew Reese Mar 04 '22 at 22:49
  • Was struggling with editing it's ok now, thank you for caring my case – Harkayn Mar 04 '22 at 22:55
  • It seems you are asking specifically what is wrong with `setSteps` but not explaining the issue. `setSteps` is a state updater function, I doubt there's anything wrong with it as it's part of React and well tested. The part of your code you've marked with `// HERE THE PROBLEM WITH SETSTEPS`... What is the problem there? What is going on there in your code? – Drew Reese Mar 05 '22 at 01:38
  • What is going on is when i update the state of my ReviewPicture screen after taking a new photo i lose all the content ( The photos list pushed by ReviewPicture ). When in Photoshoot.tsx i setSteps i can properly do a photo based on the position of the Tyre but when the process continue after validating the new photo it brings me back just the updated photo and everything else is disapeared ( my photos list ) i don't want to my setStep to only return the photo he updated but i want my whole ReviewPicture screen WITH the updated photo, i hope i'm more precise – Harkayn Mar 05 '22 at 02:20
  • 1
    I don't see where `ReviewPicture` pushes anything anywhere. Are you referring to the `c` array that is set into the `content` state and never updated after the initial render cycle? What does this have to do with the state in the `Photoshoot` component where you say there's a problem? Other than `ReviewPictures` passing a `step` as `review` in route state to `Photoshoot`, what do these two components have to do with each other? It seems like you've some likely stale state in `ReviewPictures` and duplicated state in `Photoshoot`. – Drew Reese Mar 05 '22 at 05:13
  • Yes i'm referring to the '''c''' array i start to see clearer of what i need to do thanks to your comments i'll try without readonly – Harkayn Mar 05 '22 at 14:53

1 Answers1

1

From what I can understand of your post, you are having some issue with the step state and updating it. In the three places in Photoshoot.tsx file where you enqueue any steps state updates you should probably use a functional state update to shallowly copy and update from any previously existing state instead of fully replacing it.

Example:

const loadReviewScenario = () => {
  if (params?.review) {
    setSteps(steps => [...steps, fromStepAlias(params.review)]);
    setIsLoading(false);
  }
};

const loadRetakeScenario = () => {
  const wearTyresToRetake = extractWearTyresToRetake(wear?.tyreWears);
  const referenceTyresToRetake = extractReferenceTyresToRetake(reference?.tyreReferences);
  const scenario = createRetakeScenario(wearTyresToRetake, referenceTyresToRetake);
  setSteps(steps => [...steps, scenario]);
  setIsLoading(false);
};

const loadScenario = async () => {
  setReferenceId(reference?.id);
  setSteps(steps => [
    ...steps,
    !!params?.onboarded ? WEAR_SCENARIO : ONBOARDING_SCENARIO
  ]);
  setIsLoading(false);
};
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Starting to look like a solution for my case but it returns this and i don't manage to get rid of it... [The error](https://ibb.co/8dLPbPL) – Harkayn Mar 05 '22 at 02:12
  • 1
    @Harkayn The params are read-only, looks like something in your code is trying to mutate it. – Drew Reese Mar 05 '22 at 05:02