every time the screen switches, all the states reset (useContect) - reactjs

I have a react native appication. It is a mockup of zillow. I created a useContext object that stores several of the different useState to store different information I want the home page and all of its children components to have access to.
From the home page, the user can click on the sort button and it redirects the user to a different page so that he can change how the information is sorted.
when the user clicks on a sort option, I have it os that it goes back to the home page by using navigation.goBack(). The issue is that every time the screen changes, the entire useContect object reverts to the original state that is set by default.
I need to figure out how to get the state in useContect to not revert to default when I change screens.
I tried scoping the useContext.provider to wrap the whole navigation stack to see if that would change anything but it did not work. Does anyone know a way to do this so that I can use different screens without reseting the entire useContext
Here is the HomeScreen:
import React, { useState, createContext, useEffect} from 'react'
import {View, Text, StyleSheet} from 'react-native'
import SearchAndFilterComponent from '../components/HomeScreen/SearchAndFilterComponent';
import ResultsComponent from '../components/HomeScreen/ResultsComponent';
import { SearchFilterSortContextProvider } from '../context/SearchFilterSortContext';
const HomeScreen = () => {
return (
<SearchFilterSortContextProvider>
<View style={styles.screen}>
<SearchAndFilterComponent/>
<View style={styles.fullSplit}></View>
<ResultsComponent />
</View>
</SearchFilterSortContextProvider>
)
}
const styles = StyleSheet.create({
fullSplit: {
width: '100%',
height: 2,
backgroundColor: 'black'
}
})
export default HomeScreen
SearchFilterSortContext.js:
import { useState, createContext, useEffect } from "react";
export const SearchFilterSortContext = createContext(null)
export const SearchFilterSortContextProvider = ({children}) => {
// General Search State
const [currentSearch, setCurrentSearch] = useState('')
const [activeSearch, setActiveSearch] = useState('')
const [sort, setSort] = useState('')
const [filter, sestFilter] = useState()
const [results, setResults] = useState([])
const [resultCounter, setResultCounter] = useState(0)
const [page, setPage] = useState(1)
const [totalPages, setTotalPages] = useState(0)
const [resultView, setResultView] = useState('List')
// Filter Home Type States
const [house, setHouse] = useState(true)
const [apartment, setApartment] = useState(false)
const [condos, setCondos] = useState(false)
const [townHouse, setTownHouse] = useState(false)
const [multiFamily, setMultiFamily] = useState(false)
const [manufactured, setManufactured] = useState(false)
const [land, setLand] = useState(false)
// Filter Home Details States
const [priceMin, setPriceMin] = useState(0)
...
// data management state
const [loadingMain, setLoadingMain] = useState(false)
return (
<SearchFilterSortContext.Provider value={{house, setHouse,
apartment, setApartment,
condos, setCondos,
townHouse, setTownHouse,
multiFamily, setMultiFamily,
manufactured, setManufactured,
land, setLand,
...
loadingMain, setLoadingMain}}>
{children}
</SearchFilterSortContext.Provider>
)
}
SortSelectionScreen.js:
import React, { useContext } from 'react'
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'
import { Feather } from '#expo/vector-icons'
import { useNavigation } from '#react-navigation/native'
import { SearchFilterSortContext } from '../../context/SearchFilterSortContext'
const SortOptionsComponent = () => {
const {sort, setSort} = useContext(SearchFilterSortContext)
const navigation = useNavigation()
return (
<View>
<TouchableOpacity onPress={() => {setSort('Homes_for_You'); navigation.goBack()}} style={styles.row}>
<Text style={styles.text}>Homes For You</Text>
{
sort === 'Homes_for_You' ? <Feather name='check' color={'black'} size={20}/> : null
}
</TouchableOpacity>
<TouchableOpacity onPress={() => {setSort('Newest'); navigation.goBack()}} style={styles.row}>
<Text style={styles.text}>Newest</Text>
{
sort === 'Newest' ? <Feather name='check' color={'black'} size={20}/> : null
}
</TouchableOpacity>
...
</View>
)
}
Component that renders the results
ResultsComponent.js:
import React, { useEffect, useContext } from 'react'
import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from 'react-native'
import axios from 'axios'
import { useNavigation } from '#react-navigation/native'
import { extendedPropertOptions } from '../../../zillow'
import { SearchFilterSortContext } from '../../context/SearchFilterSortContext'
import LoadingComponent from '../GeneralComponents.js/LoadingComponent'
import PropertyTile from './PropertyTile'
const ResultsComponent = () => {
const navigation = useNavigation()
const {results, setResults} = useContext(SearchFilterSortContext)
const {setResultCounter} = useContext(SearchFilterSortContext)
const {setTotalPages} = useContext(SearchFilterSortContext)
const {sort, setCurrentSearch} = useContext(SearchFilterSortContext)
const {loadingMain, setLoadingMain} = useContext(SearchFilterSortContext)
const {currentSearch, activeSearch} = useContext(SearchFilterSortContext)
const getDefaultResults = () => {
setLoadingMain(true)
extendedPropertOptions.params.location = 'Los Angeles, CA'
axios.request(extendedPropertOptions)
.then((response) => {
setResults(response.data.props)
setResultCounter(response.data.totalResultCount)
setTotalPages(response.data.totalPages)
setLoadingMain(false)
})
.catch((error) => {
console.error(error)
})
}
// apply search with all the filters
const getFilteredResults = () => {
console.log('fire search')
console.log(sort)
setLoadingMain(true)
{
activeSearch === '' ?
currentSearch === '' ?
extendedPropertOptions.params.location = 'Los Angeles, CA'
: extendedPropertOptions.params.location = currentSearch
: extendedPropertOptions.params.location = activeSearch
}
extendedPropertOptions.params.sort = sort
console.log(extendedPropertOptions)
axios.request(extendedPropertOptions)
.then((response) => {
setResults(response.data.props)
setResultCounter(response.data.totalResultCount)
setTotalPages(response.data.totalPages)
setCurrentSearch('')
setLoadingMain(false)
})
.catch((error) => {
console.error(error)
})
}
useEffect(() => {
getDefaultResults()
}, [])
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
getFilteredResults()
})
return unsubscribe
}, [navigation])
return (
<ScrollView style={styles.scroll}>
{
loadingMain === true ? <View><LoadingComponent /></View> : <View>{results.map((item) => {return(<View key={item.zpid}><PropertyTile item={item}/></View>)})}</View>
}
</ScrollView>
)
}
when I go back to the results component, and it runs getFilteredResults the sort has reverted back to ''. That is what I need to fix.

Related

infinite scroll, react-intersection-observer, How to add new Array to the foundation Array? (i used spread... but didn't worked)

I'm making movie app (using react.js)
I want to show a list of new movies whenever user scrolls down.
but when i write these codes, it doesn't work.
I used react-intersection-observer and made the second useEffect for adding new list.
can you see what is the problem...?
**import { useInView } from "react-intersection-observer";**
import { useEffect, useRef, useState } from "react";
import Movie from "../components/Movie";
import HeaderComponent from "../components/HomeButton";
import GlobalStyle from "../GlobalStyle";
import { Route, useParams } from "react-router-dom";
import { LoadingStyle, ListContainer } from "../components/styles";
function Home() {
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const [movieSearch, setMovieSearch] = useState("");
const [movieName, setMovieName] = useState("");
const [pageNumber, setPageNumber] = useState(1);
** const { ref, inView } = useInView({
threshold: 0,
});**
const param = useParams();
const getMovies = async () => {
const json = await (
await fetch(
`https://yts.mx/api/v2/list_movies.json?minimum_rating=1&page=${pageNumber}&query_term=${movieName}&sort_by=year`
)
).json();
setMovies(json.data.movies);
setLoading(false);
};
const onChange = event => {
setMovieSearch(event.target.value);
};
const onSubmit = event => {
event.preventDefault();
if (typeof param === Object) {
setMovieName(movieSearch);
getMovies();
} else {
Route(`/main`);
}
};
// When User Searching...
useEffect(() => {
getMovies();
}, [movieName]);
// When User Scroll, Keep Adding Movies at the bottom...
** useEffect(() => {
setPageNumber(prev => prev + 1);
setMovies(prev => {
return [...movies, ...prev];
});
getMovies();
}, [inView]);
**
return (
<>
<GlobalStyle />
<HeaderComponent
onSubmit={onSubmit}
onChange={onChange}
movieSearch={movieSearch}
/>
{loading ? (
<LoadingStyle>Loading...</LoadingStyle>
) : (
<>
<ListContainer>
{movies.map(item => {
return (
<Movie
key={item.title}
id={item.id}
title={item.title}
year={item.year}
medium_cover_image={item.medium_cover_image}
rating={item.rating}
runtime={item.runtime}
genres={item.genres}
summary={item.summary}
/>
);
})}
</ListContainer>
**{inView ? <>🎭</> : <>🧶</>}**
<div ref={ref} style={{ width: "100%", height: "20px" }}></div>
</>
)}
</>
);
}
export default Home;
and, when i debug this code, this errors comes out.
react_devtools_backend.js:4026 Warning: Encountered two children with the same key, `Headless Horseman`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
at div
at O (http://localhost:3000/static/js/bundle.js:47415:6)
at Home (http://localhost:3000/static/js/bundle.js:908:80)
at Routes (http://localhost:3000/static/js/bundle.js:41908:5)
at Router (http://localhost:3000/static/js/bundle.js:41841:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:40650:5)
at App

My SetState is erasing my previous content

I return this content from my ReviewPictures.tsx screen.
My problem is when i take a new Photo with my Photo Button
My SetState erase the previous Photos List
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 ?
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);
};

Expo Refresh only working in certain components

so I am just digging into building apps with React Native/Expo and I have come across a weird behavior that I am not sure why is happening. I have a dashboard component that returns a 'UsersFlatList' component
const DashBoard = () => {
return (
<>
<View style={styles.dashboard__wrapper}>
<TextInput
placeholder="why is this working??
"
style={styles.form__input}
></TextInput>
<UsersFlatList />
</View>
</>
)
}
If I change that text - I see the changes happening in expo go immediately. However if I make changes to the data in the flatlist, I don't see any changes at all and I have to refresh the app to see any of the updates changes.
import { useEffect, useState } from "react"
import { Text, View, SafeAreaView, FlatList, StyleSheet } from "react-native"
import UserCard from "./UserCard"
import { collection, getDocs } from "firebase/firestore"
import db from "../firebaseConfig"
const UsersFlatList = () => {
const [onlineUsers, setOnlineUsers] = useState([])
useEffect(() => {
const fetchAllData = async () => {
const foundUsers = await getDocs(collection(db, "users"))
const currentUsers = []
foundUsers.forEach((user) => currentUsers.push(user.data()))
setOnlineUsers(() => currentUsers)
}
const matchedUsers = fetchAllData()
}, [])
return (
<FlatList
data={onlineUsers}
renderItem={UserCard}
keyExtractor={(item) => item.email}
></FlatList>
)
}
export default UsersFlatList
import { Text, View, StyleSheet } from "react-native"
const UserCard = ({ item }) => {
console.log(item)
return (
<View style={styles.userCard}>
<View style={styles.online__icon}></View>
<Text>{item.username} is a</Text>
</View>
)
}
const styles = StyleSheet.create({})
export default UserCard
Appreciate this is a long one to read but any help or a pointer would be amazing thanks

Using Draft js mention plugin with react hooks

I have been trying to get draft js mention plugin to work with react hooks but can't seem to figure what's wrong with the code. Appreciate any help on this.
import React, { useRef, useState, useEffect } from "react";
import { EditorState } from "draft-js";
import Editor from "draft-js-plugins-editor";
import createMentionPlugin, { defaultSuggestionsFilter } from "draft-js-mention-plugin";
import mentions from "./mentions";
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
const [suggestions, setSuggestions] = useState(mentions);
const editor = useRef(null);
useEffect(() => {
editor.current.focus();
}, [])
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
};
return (
<div style={{ border: "1px solid gray" }}>
<Editor
editorState={editorState}
onChange={editorState => setEditorState(editorState)}
plugins={plugins}
ref={editor}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
/>
</div>
);
}
You need to move the draft-js plugin configuration outside the component arrow function. This is a pretty basic Draft-JS implementation using a functional component and hooks:
import React, { useState, useRef } from 'react'
import { EditorState } from 'draft-js'
import Editor from 'draft-js-plugins-editor'
import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin'
import 'draft-js/dist/Draft.css'
import 'draft-js-mention-plugin/lib/plugin.css'
import mentions from "./mentions"
// Draft-JS-Mentions plugin configuration
const mentionPlugin = createMentionPlugin()
const { MentionSuggestions } = mentionPlugin
const plugins = [mentionPlugin]
const MyEditor= () => {
const [suggestions, setSuggestions] = useState(mentions)
// Draft-JS editor configuration
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
)
const editor = useRef(null)
// Check editor text for mentions
const onSearchChange = ({ value }) => {
setSuggestions(defaultSuggestionsFilter(value, mentions))
}
const onAddMention = () => {
}
// Focus on editor window
const focusEditor = () => {
editor.current.focus()
}
return (
<div onClick={() => focusEditor()}>
<Editor
ref={editor}
editorState={editorState}
plugins={plugins}
onChange={editorState => setEditorState(editorState)}
placeholder={'Type here...'}
/>
<MentionSuggestions
onSearchChange={onSearchChange}
suggestions={suggestions}
onAddMention={onAddMention}
/>
</div>
)
}
export default MyEditor
Just move these lines outside component and it will work:
const mentionPlugin = createMentionPlugin();
const { MentionSuggestions } = mentionPlugin;
const plugins = [mentionPlugin];
export default function MentionEditor() {
const [editorState, setEditorState] = useState(EditorState.createEmpty());
.. ... ...
}
!!!!!!!!!!!!!!!! PAY ATTENTION !!!!!!!!!!!!
The onSearchChange method will be triggered once the '#' character is typed, so in this case it will return just 5 items that fit the empty string...
To prevent this to be happened, just check that the value we want to search is not empty:
const onSearchChange = ({ value }) => {
if (value) {
setSuggestions(defaultSuggestionsFilter(value, mentions));
}
};

Accessing Refs in React Functional Component

Im using this package to add credit cards to my app, how would you åccess the refs as in the example when using this in a functional component?
This is how they show to update values:
this.refs.CCInput.setValues({ number: "4242" });
I don't know how to access that inside a functional component?
This is my component to Edit a card, and I want to add the current values to the inputs.
import React, {useContext, useState, useRef} from 'react';
import {Text, View, StyleSheet, TouchableOpacity} from 'react-native';
import {CreditCardInput} from 'react-native-input-credit-card';
import Store from '../../store/context';
const styles = StyleSheet.create({
});
export default function EditCard(props) {
const {navigate} = props.navigation;
const {cardNumber} = props.navigation.state.params;
const {state, dispatch} = useContext(Store);
const [card, setCard] = useState(
state.cards.find(card => {
return card.values.number === cardNumber;
}),
);
const _onChange = form => {
if (form.valid) {
setCard(form);
}
};
const updateCard = () => {
dispatch({
type: 'ADD_CARD',
payload: card,
});
navigate.goBack();
};
return (
<View style={styles.container}>
<CreditCardInput
validColor={'#47B278'}
invalidColor={'#E23C3C'}
placeholderColor={'#efefef'}
onChange={_onChange}
requiresName
/>
<TouchableOpacity
style={
card
? card.valid
? styles.button
: styles.buttonDisabled
: styles.buttonDisabled
}
disabled={card ? (card.valid ? false : true) : true}
onPress={updateCard}>
<Text style={styles.buttonText}>Update Credit Card</Text>
</TouchableOpacity>
</View>
);
}
You need to use useRef hook:
const cciRef = useRef();
<CreditCardInput ref={cciRef}/>;
// cciRef.current holds the reference
export default function EditCard(props) {
const cciRef = useRef();
useEffect(() => {
console.log(cciRef.current);
cciRef.current.setValues({ number: "4242" });
}, []);
return (
<View style={styles.container}>
<CreditCardInput ref={cciRef} />
</View>
);
}

Resources