Global screen loader in react - reactjs

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

Related

How to convert methods from Library to fit functional component

Need help converting RNLocation.requestPermission and _startUpdatingLocation to work with function components in react native.
Not sure how to approach this
LatLon Component File
import React, {useState} from "react";
import { SafeAreaView, StyleSheet, Text, } from "react-native";
import RNLocation from "react-native-location";
import moment from "moment";
import styled from 'styled-components/native';
function LatLonBox(){
const [currentLocation, setLocation] = useState(null);
const styles = StyleSheet.create({
Custom_Text:{
textAlign:'center',
fontSize:15
}
});
const LocationContainer = styled.View`
marginTop: 10;
marginBottom: 10;
`;
//How to make this work in functional style component
RNLocation.requestPermission({
ios: "whenInUse"
}).then(granted => {
if (granted) {
this._startUpdatingLocation();
}
});
}
//How to make this work in functional style component
_startUpdatingLocation = () => {
this.locationSubscription = RNLocation.subscribeToLocationUpdates(
locations => {
this.setState({ location: locations[0] });
}
);
};
return(
<SafeAreaView>
<React.Fragment>
<LocationContainer>
<Text style = {styles.CustomText}>{currentLocation.longitude}</Text>
<Text style = {styles.CustomText}>{currentLocation.latitude}</Text>
</LocationContainer>
</React.Fragment>
</SafeAreaView>
);
}
export default LatLonBox;
Screen File //Just extra if needed
import React from 'react';
import { SafeAreaView } from 'react-native';
import { StackParamList } from '../types';
import { BottomSheetModalProvider } from '#gorhom/bottom-sheet';
import { NativeStackScreenProps } from '#react-navigation/native-stack';
import LatLonBox from '../components/LatLonBox';
export default ({}: NativeStackScreenProps<StackParamList, 'Main'>): JSX.Element => {
return (
<BottomSheetModalProvider>
<SafeAreaView style={{ flex: 1}}>
<LatLonBox/> //where LatLon will be placed
</SafeAreaView>
</BottomSheetModalProvider>
);
};
Link to RNLocation Library
You can check this
Hey hope this helps, feel free for doubts:
const _startUpdatingLocation = () => {
const locationSubscription = RNLocation.subscribeToLocationUpdates(
locations => {
setLocation(locations[0])
}
);
};
const requestPermission = useCallback(() => {
RNLocation.requestPermission({
ios: "whenInUse"
}).then(granted => {
if (granted) {
_startUpdatingLocation();
}
});
}
},[_startUpdatingLocation])
useEffect(() => {
requestPermission()
},[requestPermission])
//How to make this work in functional style component

Deploying react app using fabricjs causing error in functionality

I'm trying to deploy a React app using fabricjs. On localhost it's working fine but after deploying , fabric functions not working and causing an error
Uncaught TypeError: Cannot read properties of null (reading 'add')
what can be the issue ? and if anyone know it what can be the possible solution ?
here is the code for adding text box to the fabric canvas
import { fabric } from 'fabric';
import ContextCanvas from '../../../context/ContextCanvas';
import { Button } from '#chakra-ui/react';
const FabricTextBox = () => {
const [canvas] = useContext(ContextCanvas);
function addTextBox() {
const textbox = new fabric.Textbox('Click on the Rectangle to move it.', {
fontSize: 20,
left: 50,
top: 100,
width: 200,
fill: 'black',
color: 'white',
cornerColor: 'blue',
});
canvas.add(textbox);
canvas.requestRenderAll();
}
return (
<>
<Button
type="button"
colorScheme="blue"
onClick={addTextBox}
variant={'ghost'}
_hover={{}}
_focus={{}}
_active={{}}
textColor={'white'}
fontWeight={'light'}
>
Text Field
</Button>
</>
);
};
export default FabricTextBox;
here is my fabric canvas
import React, { useContext, useLayoutEffect } from 'react';
import { fabric } from 'fabric';
import ContextCanvas from '../../context/ContextCanvas';
const FabricCanvas = () => {
const [canvas, initCanvas] = useContext(ContextCanvas);
useLayoutEffect(() => {
return () => {
initCanvas(new fabric.Canvas('c'));
};
}, []);
return (
<>
<canvas
id="c"
width={window.innerWidth}
height={window.innerHeight}
/>
</>
)
}
export default FabricCanvas;
here is my context provider
import { fabric } from 'fabric';
const ContextCanvas = createContext();
export function CanvasProvider({ children }) {
const [canvas, setCanvas] = useState(null);
const initCanvas = c => {
setCanvas(c);
c.renderAll();
};
return (
<ContextCanvas.Provider value={[canvas, initCanvas]}>
{children}
</ContextCanvas.Provider>
);
}
export default ContextCanvas;

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

React useToggle component returning TypeError: Object is not a function or its return value is not iterable

I'm attempting a component which is essentially a "like" button that should be toggled on and off, using React and useToggle, however it's returning the TypeError: Object is not a function or its return value is not iterable.
import React, { useToggle } from 'react';
import ThumbUpIcon from '#material-ui/icons/ThumbUp';
const ThumbUpButton = {
backgroundColor: 'green',
border: 'none',
padding: '5px',
borderRadius: '5px',
}
const ThumbUp = {
color: 'white'
}
const Liker = () => {
const [like, unLike] = useToggle();
return (
<div>
<button style={ThumbUpButton} onClick={unLike}>
{like ? <ThumbUpIcon style={ThumbUp}/> : <ThumbUpIcon />}
</button>
</div>
);
}
export default Liker;
useToggle is not a hook exported from react. Have you written it in another file and messed uo the imports, perhaps? Cause this hook is not a React basic hook
You should define the hook 'useToogle` somewhere, then use it inside the 'Liker' component.
import React, { useState } from 'react';
const ToogleHook = (initValue) => {
const [value, setValue] = useState(initValue);
function toogleValue() {
setValue(!value);
}
return [value, toogleValue];
}
const Liker = () => {
const [like, toogleLike] = ToogleHook(false);
return <button onClick={toogleLike}>{like ? '👍🏿' : '👎🏿'}</button>
}
export default Liker;

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

Resources