My SetState is erasing my previous content - reactjs

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

Related

TypeError: Cannot read properties of null (reading 'useContext')

I want add {searchWord} from useContext, to function with API call getServerSideProps(), but I got different errors.
The last one: Cannot read properties of null (reading 'useContext').
Maybe something wrong with my provider???
File with API call function /search-results:
import Link from 'next/link';
import { Text, Stack, Box, SimpleGrid, IconButton, Button, useQuery } from '#chakra-ui/react'
import ListOfCards from '../components/ListOfCards';
import { SearchContext, SearchContextProps } from './_app';
import { useContext, useEffect, useState } from 'react';
import { ChevronLeftIcon } from '#chakra-ui/icons';
function SearchResult({ searchResultForMapping }) {
console.log(searchResultForMapping)
const [queryKey, setQueryKey] = useState('lebowski');
const { searchWord } = useContext<SearchContextProps>(SearchContext);
useEffect(() => {
setQueryKey(searchWord);
}, [searchWord]);
const mockup = {"status":"OK","copyright":"Copyright (c) 2022 The New York Times Company. All Rights Reserved.","has_more":false,"num_results":1,"results":[{"display_title":"The Big Lebowski","mpaa_rating":"R","critics_pick":1,"byline":"Janet Maslin","headline":"Big Lebowski, the (Movie)","summary_short":"In a film both sweeter and loopier than \u0026quot;Fargo,\u0026quot; Joel and Ethan Coen and their rogues' gallery of actors explore Southern California's post-hippie, surfer-dude bowling scene. The results are irresistible, at least for audiences on the film's laid-back wavelength and in tune with its deadpan humor. With shaggy, baggy looks that belie his performance's fine comic control, Jeff Bridges plays a guy who truly deserves to be called Dude, and is by turns oblivious and indignant once a ruined rug leads him into neo-noir escapades. In a film that certainly keeps its audience guessing what might happen next (Saddam Hussein renting bowling shoes?), John Turturro takes the cake as the most fiendish of bowlers. — Janet Maslin","publication_date":"1998-03-06","opening_date":"1998-03-06","date_updated":"2017-11-02 04:17:54","link":{"type":"article","url":"https://www.nytimes.com/1998/03/06/movies/film-review-a-bowling-ball-s-eye-view-of-reality.html","suggested_link_text":"Read the New York Times Review of The Big Lebowski"},"multimedia":null}]}
return (
<><Button leftIcon={<ChevronLeftIcon />} colorScheme='pink' variant='solid' position='absolute' right='20px;' top='25px'>
<Link href="/">Home</Link>
</Button>
<Box p={5}>
<SimpleGrid gap={12} p={12} columns={1}>
<Text fontSize='4xl'> Search Result for keyword "{queryKey} i {searchWord}"</Text>
<Stack spacing={3} rowGap={3}>
<ListOfCards movieDetails={mockup} />
</Stack>
<Text fontSize='xl'><Link href="/">Search last key word..</Link> </Text>
</SimpleGrid>
</Box></>
);
}
const { searchWord } = useContext<SearchContextProps>(SearchContext);
export async function getServerSideProps() {
// const queryWord = 'iron'
const queryWord = searchWord;
const apiKey = "nTCRt5WnuaiL5Q5VsPEgeGM8oZifd3Ma";
const endpoint = "https://api.nytimes.com/svc/movies/v2/reviews/search.json?";
const createUrl = `${endpoint}query=${queryWord}&api-key=${apiKey}`;
const res = fetch(createUrl);
const searchResultForMapping = (await res).json();
return { props: { searchResultForMapping } };}
export default SearchResult
Page where i setSearchWord SearchLine.tsx:
import { Button } from "#chakra-ui/button";
import { Input, InputGroup, InputRightElement } from "#chakra-ui/input";
import React, { useContext } from "react";
import { useRouter } from 'next/router'
import { SearchContext } from "../pages/_app";
const SearchLine = () : JSX.Element => {
const { searchWord, setSearchWord, searchHistory, setSearchHistory } = useContext(SearchContext);
const router = useRouter()
const handleChange = (keyword) => setSearchWord(keyword.target.value);
const handleOnclick = () => {
setSearchHistory([...searchHistory, searchWord]);
router.push('/search-result');
}
return (
<InputGroup size="md">
<Input
onChange={handleChange}
placeholder='Enter the name of the movie or keyword'
size='md'
/>
<InputRightElement width="4.5rem">
<Button onClick={handleOnclick}>
Enter
</Button>
</InputRightElement>
</InputGroup>
)
}
export default SearchLine;
my _app.tsx:
import { ChakraProvider } from '#chakra-ui/react'
import { QueryClientProvider } from '#tanstack/react-query';
import { AppProps } from 'next/app';
import { createContext, useContext, useMemo, useState } from 'react';
export interface SearchContextProps {
searchWord: string;
setSearchWord: (f: string) => void;
searchHistory: string[];
setSearchHistory: (f: string[]) => void;
}
export const SearchContext = createContext<SearchContextProps>({
searchWord: '',
setSearchWord: () => {
// do nothing
},
searchHistory: [],
setSearchHistory: () => {
// do nothing
},
});
const SearchProvider = (props: any) => {
const [searchWord, setSearchWord] = useState('');
const [searchHistory, setSearchHistory] = useState('');
const value = useMemo(() => ({ searchWord, setSearchWord, searchHistory, setSearchHistory }), [searchWord]);
return <SearchContext.Provider value={value} {...props} />;
};
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const context = useContext(SearchContext);
return (
<ChakraProvider>
<SearchProvider>
<Component {...pageProps} />
</SearchProvider>
</ChakraProvider>
)
}
export default MyApp
It seems your SearchProvider is wrong.
const SearchProvider = (props: any) => {
const [searchWord, setSearchWord] = useState('');
const [searchHistory, setSearchHistory] = useState('');
const value = useMemo(() => ({ searchWord, setSearchWord, searchHistory, setSearchHistory }), [searchWord]);
return (
<SearchContext.Provider value={value}>
{props.children}
</SearchContext.Provider>
);
};

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

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.

Global screen loader in react

I am looking for a solution for using a global screen loader in react.
I am not that much familiar to react context, but I was wondering if that could help me here.
Basically I am introducing a screenloader and I was thinking that maybe the best way would be to have a global loader somewhere in main component.So to conclude:
I want to have global loader in main component
I want to update the state of global loader wherever I want in app
I don't want to pollute all the components with ScreenLoaders where I need to use it
I want to use hooks for it
So is there a way to have a global state of loader/loaderText and setting and resetting whenever needed using context?
If there is a simple way to do it, then do you think there might be any drawbacks of using such solution? Maybe that's an overkill for it.
What about creating a custom hook, useLoading, which can be used in any component that gives access to loading and setLoading from global context?
// LoadingContext.js
import { createContext, useContext, useState } from "react";
const LoadingContext = createContext({
loading: false,
setLoading: null,
});
export function LoadingProvider({ children }) {
const [loading, setLoading] = useState(false);
const value = { loading, setLoading };
return (
<LoadingContext.Provider value={value}>{children}</LoadingContext.Provider>
);
}
export function useLoading() {
const context = useContext(LoadingContext);
if (!context) {
throw new Error("useLoading must be used within LoadingProvider");
}
return context;
}
// App.jsx
import { LoadingProvider } from "./LoadingContext";
function App() {
return (
<LoadingProvider>
<RestOfYourApp />
</LoadingProvider>
);
}
// RestOfYourApp.js
import { useLoading } from "./LoadingContext";
function RestOfYourApp() {
const { loading, setLoading } = useLoading();
return (
{loading && <LoadingComponent />}
...
);
}
useLoader.js (hook)
import React, { useState } from "react";
import Loader from "./loader";
const useLoader = () => {
const [loading, setLoading] = useState(false);
return [
loading ? <Loader /> : null,
() => setLoading(true),
() => setLoading(false),
];
};
export default useLoader;
loader.js (loader componenet)
import React from "react";
import styled from "styled-components";
import spinner from "./loader.gif"; // create gif from https://loading.io
import Color from "../../Constant/Color";
const Loader = () => {
return (
<LoaderContainer>
<LoaderImg src={spinner} />
</LoaderContainer>
);
};
const LoaderContainer = styled.div`
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
background: ${Color.greyBg};
z-index: 100;
`;
const LoaderImg = styled.img`
position: absolute;
`;
export default Loader;
Using Loader hook
import useLoader from "../../../hooks/loader/useLoader"; /// import loader hook
const App = (props) => {
const [loader, showLoader, hideLoader] = useLoader(); //initialize useLoader hook
useEffect(() => {
showLoader(); /// loading starts
Axios.post("url")
.then((res) => {
hideLoader(); // loading stops
})
.catch((error) => {
hideLoader();// loading stops
});
}, []);
return (
<>
{loader} /// important
//// add your elements /////
</>
)
}
export default App;
Some more easy way
Create Provider with context and hook within single file
import React, {useRef} from 'react';
import {Loader} from '#components';
const LoaderContext = React.createContext();
export function LoaderProvider({children}) {
const ref = useRef();
const startLoader = () => ref.current.start();
const stopLoader = () => ref.current.stop();
const value = React.useMemo(
() => ({ref, startLoader, stopLoader}),
[ref, startLoader, stopLoader]
);
return (
<LoaderContext.Provider value={value}>
{children}
<Loader ref={ref} />
</LoaderContext.Provider>
);
}
export const useLoader = () => React.useContext(LoaderContext);
in App.js add provider
import {StoreProvider} from 'easy-peasy';
import React from 'react';
import {StatusBar, View} from 'react-native';
import colors from './src/assets/colors';
import Navigation from './src/navigation/routes';
import {LoaderProvider} from './src/providers/LoaderProvider';
import {ToastProvider} from './src/providers/ToastProvider';
import store from './src/redux/store';
import globalStyles from './src/styles/index';
import('./src/helpers/ReactotronConfig');
function App() {
return (
<StoreProvider store={store}>
<StatusBar
barStyle="light-content"
backgroundColor={colors.backgroundDark}
translucent={false}
/>
<ToastProvider>
<LoaderProvider>
<View style={globalStyles.flex}>
<Navigation />
</View>
</LoaderProvider>
</ToastProvider>
</StoreProvider>
);
}
export default App;
And in any screen use like this way
import {useLoader} from '../../providers/LoaderProvider';
const {startLoader, stopLoader} = useLoader();
Loader.js
import React, {forwardRef, useImperativeHandle, useState} from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {wp} from '../../styles/responsive';
function Loader(props, ref) {
const [loading, setLoading] = useState(0);
useImperativeHandle(
ref,
() => ({
start: () => {
const loadingCount = loading + 1;
setLoading(loadingCount);
},
stop: () => {
const loadingCount = loading > 0 ? loading - 1 : 0;
setLoading(loadingCount);
},
isLoading: () => loading >= 1,
}),
[],
);
if (!loading) {
return null;
}
return (
<View style={styles.container}>
<ActivityIndicator size={'small'} color={'#f0f'} />
</View>
);
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFill,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#11111150',
zIndex: 999,
elevation: 999,
},
});
export default forwardRef(Loader);
You can use this package for simple react loading : https://www.npmjs.com/package/react-global-loading
Usage :
import { GlobalLoading, showLoading } from 'react-hot-toast';
const App = () => {
const show = () => {
showLoading(true);
setTimeout(() => {
showLoading(false);
}, 1000);
};
return (
<div>
<button onClick={show}>Show Loading</button>
<GlobalLoading />
</div>
);
};

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