usememo and useeffect in customhooks - reactjs

I've tried to use useMemo and useEffect in my custom hooks to prevent that from re-rendering ( doing cpu heavy job) which is parsing the JWT and returning the user permissions.
But it re-renders eveerytime that my menu items re-render and runs the usememo too.
Is there a way to avoid it from re-calculating ?
The code:
const usePermission = (controller?: string) => {
const [permoactions, setPermoactions] = React.useState([]);
const fetchPermoactions = () => {
try {
// parse jwt token and return permissions
} catch (error: any) {
throw new Error(error.message);
}
};
useMemo(() => {
fetchPermoactions();
console.count("memo");
}, [1]);
const access = React.useMemo(() => {
// make an permissions object tree
}, [permoactions]);
return controller ? access[controller] : access;
};
export default usePermission;
The usePermission hook is used in the pages where I need to validate for example if a user should see a button or have the ability to delete a line like this:
{permissions?.Create && (
<Grid item xs={2} sx={{ textAlign: "left" }}>
<Button
variant="contained"
onClick={handleAddClick}
endIcon={<AddIcon sx={{ marginRight: 2 }} />}
>
افزودن
</Button>
</Grid>
)}
It's also used in the sidebar to not show the menus that the user doesn't have access to like:
children = children.filter((child: any) => {
let AccessList: string[] = child.name.split(",");
let hasAccess = false;
AccessList.forEach((Access) => {
if (userAccess[Access]?.View) {
hasAccess = true;
} else {
hasAccess = false;
return;
}
return hasAccess && children;
});
return hasAccess ? child : null;
});

Related

Why if statements not working as expected with RTK Query

I'm trying to build an app which pulls data from backend with RTK Query and display that data as posts. I have successfully created RTKQ endpoint that gets data from backend with useEffect. However I want to put data in another array so that I can add more data with infinite scroll to that array but I am not able to do that.
My code is as follow. All help will be highly appreciated.
import { Box, Stack, Skeleton } from "#mui/material";
import React, { useEffect, useState } from "react";
import Post from "../components/postComponent";
import { useGetSimplesMutation } from '../services/authApi'
const Feed = () => {
const [getSimples, { isLoading, isSuccess, data }] = useGetSimplesMutation()
const [loadNow, setLoadNow] = useState(false)
let simplesData = []
useEffect(() => {
const handleGetSimples = async () => {
try {
await getSimples(0).unwrap()
if (isSuccess & !loadNow) {
console.log("Simples loaded")
simplesData = [...simplesData, ...data]
setLoadNow(true)
console.log(simplesData)
}
} catch (error) {
console.error(error.message)
}
}
handleGetSimples()
}, [])
return (
<Box flex={6} p={{ xs: 0, md: 2 }}>
{isLoading && (
<Box marginLeft={3}>
<Stack spacing={1} >
<Skeleton variant="text" height={100} />
<Skeleton variant="text" height={20} />
<Skeleton variant="text" height={20} />
<Skeleton variant="rectangular" height={300} />
</Stack>
</Box>
)}
{(isSuccess && loadNow) && simplesData.map(simples => {
return (
<div key={simples._id}>
<Post
userName={simples.userName}
dateSubmitted={simples.dateSubmitted}
beforePic={simples.beforePic}
afterPic={simples.afterPic}
tag={simples.tag}
/>
</div>
)
})}
</Box>
);
};
export default Feed;
This Code works when I directly map data obtained from RTKQ. However when I pass data to another array in an If statement the code inside if statement does not trigger when isSuccess gets true alongwith loadNow which has default state of false. I want to add data to simplesData array when this condition is true and then setLoadNow to true so that I can render my posts.
The line let simplesData = [] will be re-executed every time that the component re-renders, which will wipe out whatever you have stored.
If you want to keep data across multiple renders then you need to store it in a useState hook.
const [simplesData, setSimplesData] = useState([])
Inside your useEffect, you can call setSimplesData, using a functional update to minimize the dependencies of your effect.
setSimplesData(prevData => [...prevData, ...data])
There are some other issues here, mainly your lack of useEffect dependendencies. I think you want something like this?
const Feed = () => {
const [getSimples, { isLoading, isSuccess }] = useGetSimplesMutation()
const [loadPage, setLoadPage] = useState(0)
const [simplesData, setSimplesData] = useState([])
const handleEndReached = () => {
setLoadPage(prevPage => prevPage + 1);
}
const handleReset = () => {
setSimplesData([]);
setLoadPage(0);
}
useEffect(() => {
const handleGetSimples = async () => {
try {
const pageData = await getSimples(loadPage).unwrap()
console.log(`Simples page ${loadPage} loaded`)
setSimplesData(prevData => [...prevData, ...pageData])
} catch (error) {
console.error(error.message)
}
}
handleGetSimples()
}, [loadPage]);

How to convert this into a class based component

I have tried and I keep running into react 302 errors with multiple instances of react and issues with state. I am still learning react and I always get stuck when it comes to class and function components. I am trying to convert this into a class based component since that is what I am familiar with using and need to integrate into another class based component.
const { useState, useEffect, } = React;
const { useSelector } = ReactRedux;
import { React, AllWidgetProps, getAppStore, appActions, ReactRedux, WidgetProps, WidgetManager, IMState } from 'jimu-core';
import { Button, Label, Row, Col, Select, Option } from 'jimu-ui';
import defaultMessages from './translations/default';
/**
* This widget will show how to control widget state for a collapsible sidebar widget and a widget within the widget controller widget.
*/
export default function Widget(props: AllWidgetProps<{}>) {
// Establish state properties, initial values and their corresponding set state actions
const [sidebarWidgetId, setSidebarWidgetId] = useState(null as string);
const [openCloseWidgetId, setOpenCloseWidgetId] = useState(null as string);
const [sidebarVisible] = useState(true as boolean);
const [openness, setOpenness] = useState(false as boolean);
const [appWidgets, setAppWidgets] = useState({} as Object);
const [widgetsArray, setWidgetsArray] = useState([] as Array<any>);
const [sidebarWidgetsArray, setSidebarWidgetsArray] = useState([] as Array<any>);
// Get the widget state - because the sidebar state may change in the runtime, via Redux's useSelector hook
const widgetState = useSelector((state: IMState) => {
const widgetState = state.widgetsState[sidebarWidgetId];
return widgetState;
});
// Update the appWidgets property once, on page load
useEffect(() => {
const widgets = getAppStore().getState().appConfig.widgets;
setAppWidgets(widgets);
}, []);
// Update the widgetsArray and sidebarWidgetsArray properties every time appWidgets changes
useEffect(() => {
if (appWidgets) {
const widgetsArray = Object.values(appWidgets);
setWidgetsArray(widgetsArray);
setSidebarWidgetsArray(widgetsArray.filter(w => w.uri === 'widgets/layout/sidebar/'));
}
}, [appWidgets]);
// Toggle the sidebar widget
const handleToggleSidebar = (): void => {
// If widget state's collapse property is true, collapse
if (widgetState && widgetState.collapse === true) {
getAppStore().dispatch(appActions.widgetStatePropChange(
sidebarWidgetId,
'collapse',
!sidebarVisible
));
}
// If widget state's collapse property is false, expand
else if (widgetState && widgetState.collapse === false) {
getAppStore().dispatch(appActions.widgetStatePropChange(
sidebarWidgetId,
'collapse',
sidebarVisible
));
}
else {
alert(
defaultMessages.sidebarAlert
)
}
};
// Load the widget class prior to executing the open/close actions
const loadWidgetClass = (widgetId: string): Promise<React.ComponentType<WidgetProps>> => {
if (!widgetId) return;
const isClassLoaded = getAppStore().getState().widgetsRuntimeInfo?.[widgetId]?.isClassLoaded;
if (!isClassLoaded) {
return WidgetManager.getInstance().loadWidgetClass(widgetId);
} else {
return Promise.resolve(WidgetManager.getInstance().getWidgetClass(widgetId));
}
};
// Open widget method
const handleOpenWidget = (): void => {
// Construct the open action, then run the loadWidgetClass method, dipatch the open action
// and, finally, set the openness to true
const openAction = appActions.openWidget(openCloseWidgetId);
loadWidgetClass(openCloseWidgetId).then(() => {
getAppStore().dispatch(openAction);
}).then(() => { setOpenness(true) });
};
// Close widget method
const handleCloseWidget = (): void => {
// Construct the close action, then run the loadWidgetClass function, dipatch the close action
// and, finally, set the openness to false
const closeAction = appActions.closeWidget(openCloseWidgetId);
loadWidgetClass(openCloseWidgetId).then(() => {
getAppStore().dispatch(closeAction);
}).then(() => { setOpenness(false) });
};
// Handler for the openness toggle button
const handleToggleOpennessButton = (): void => {
// Check the openness property value and run the appropriate function
if (openness === false) { handleOpenWidget(); }
else if (openness === true) { handleCloseWidget(); }
else { console.error(defaultMessages.opennessError) }
};
// Handler for the sidebar selection
const handleSidebarSelect = evt => {
setSidebarWidgetId(evt.currentTarget.value);
};
// Handler for the open/close selection
const handleOpenCloseSelect = evt => {
setOpenCloseWidgetId(evt.currentTarget.value);
};
return (
<div className='widget-control-the-widget-state jimu-widget m-2' style={{ width: '100%', height: '100%', maxHeight: '800px', padding: '0.5em' }}>
<h6
title={defaultMessages.title}
>
{defaultMessages.title}
</h6>
{sidebarWidgetsArray && sidebarWidgetsArray.length > 0 &&
<Row className='p-2 justify-content-between align-items-center'>
<Col className='col-sm-6'>
<Label
title={defaultMessages.sidebarLabel}
>
{defaultMessages.sidebarLabel}
</Label>
<Select
defaultValue=''
onChange={handleSidebarSelect}
placeholder={defaultMessages.sidebarPlaceholder}
title={defaultMessages.sidebarPlaceholder}
>
{/* Use the sidebarWidgetsArray to populate the select's options */}
{
sidebarWidgetsArray.map((w) => <Option
value={w.id}
>
{w.label}
</Option>)
}
</Select>
</Col>
{sidebarWidgetId &&
<Col className='col-sm-6'>
<Button
onClick={handleToggleSidebar}
htmlType='submit'
type='primary'
title={defaultMessages.sidebarButtonLabel}
>
{defaultMessages.sidebarButtonLabel}
</Button>
</Col>
}
</Row>
}
{widgetsArray && widgetsArray.length > 0 &&
<Row className='p-2 justify-content-between align-items-center'>
<Col className='col-sm-6'>
<Label
title={defaultMessages.widgetControllerWidgetLabel}
>
{defaultMessages.widgetControllerWidgetLabel}
</Label>
<Select
defaultValue=''
onChange={handleOpenCloseSelect}
placeholder={defaultMessages.widgetControllerWidgetPlaceholder}
title={defaultMessages.widgetControllerWidgetPlaceholder}
>
{/* Use the widgetsArray to populate the select's options */}
{
widgetsArray.map((w) => (
<Option
value={w.id}
>
{w.label}
</Option>
))
}
</Select>
</Col>
{openCloseWidgetId &&
<Col className='col-sm-6'>
<Button
onClick={handleToggleOpennessButton}
htmlType='submit'
type='primary'
title={defaultMessages.widgetControllerWidgetButton}
>
{defaultMessages.widgetControllerWidgetButton}
</Button>
</Col>
}
</Row>
}
</div>
);
};

How to refresh only the updated item in a list, using useQuery() to get the list

I retrieve a list of jobs using useQuery(), each one have a Favourite icon (filled depending if it's favourited)
If I click that button, I managed to refresh the item Favourite icon, but it refreshes all the items.
Whats the correct way to avoid that? Because it appears the Loading wheel again, and I think it has to be a more elegant way.
const Openings = () => {
const onToggleFav = () => {
setFavCount(prev => prev + 1)
}
const [favCount, setFavCount] = useState(0);
const { isLoading, data } = useQuery(
['getRecruiterOpenings', favCount],
() => getRecruiterOpenings()
);
return (
<div>
{ isLoading ? <Loading /> : (
<>
{ data && data.openings && data.openings.map((opening) => (
<>
<Opening {...opening} onToggleFav={() => onToggleFav()} key={opening.id}/>
</>
))}
</>
)}
</div>
)
}
export default Openings;
Inside Opening component I have a method that dispatches when you click the fav icon:
const toggleFav = async (e) => {
e.preventDefault();
await toggleFavOpening(fav, id).then(() => {
if(onToggleFav) onToggleFav()
});
}

Rendering views based on change of TextInput in react native

I'm trying to display list of events based on the search query dynamically.
The problem is that I'm always on the initial View and the renderSearch View is never called.
PastEvent is a function called from the primary redner of the class by scenemap
Please check comments in the code.
//to display the past events tab
PastEvents = () => {
const state = this.state;
let myTableData = [];
if (
state.PastEventList.length !== 0
) {
state.PastEventList.map((rowData) =>
myTableData.push([
this.renderRow(rowData)
])
);
}
function renderPast() {
console.log("im in render past") //shows
return (
<ScrollView horizontal={false}>
<Table style={styles.table}>
{myTableData.map((rowData, index) => (
<Row
key={index}
data={rowData}
style={styles.row}
textStyle={styles.rowText}
widthArr={state.widthArr}
/>
))}
</Table>
</ScrollView>
)
}
function renderSearch() {
console.log("im in render search") //never shows even after changing the text
let searchTable = [];
if (
this.state.seacrhPastList.length !== 0
) {
state.seacrhPastList.map((rowData) =>
searchTable.push([
this.renderRow(rowData)
])
);
}
return (
<ScrollView horizontal={false}>
<Table style={styles.table}>
{searchTable.map((rowData, index) => (
<Row
key={index}
data={rowData}
style={styles.row}
textStyle={styles.rowText}
widthArr={state.widthArr}
/>
))}
</Table>
</ScrollView>
)
}
return (
<View style={styles.container}>
<TextInput placeholder="Search for Events" onChangeText={text => this.onChangeSearch(text)}></TextInput>
{this.state.searching ? renderSearch() : renderPast()} //please check the onchangeSearch function
</View>
)
}
And the function of change is like that:
onChangeSearch = (text) => {
if (text.length > 0) {
let jsonData = {};
//get list of events
let url = "/api/FindEvents/" + text.toLowerCase()
ApiHelper.createApiRequest(url, jsonData, true).then(res => {
if (res.status == 200) {
this.state.seacrhPastList = res.data
this.state.searching= true //I was hoping this change will cause the render
}
})
.catch(err => {
console.log(err);
return err;
});
}
}
How can i change the events based on the query of the input ? Thank you
you need to use useState here
declare useState like this:
PastEvents = () => {
const [searching, setText] = useState(false);
change the searching state here:
if (res.status == 200) {
this.state.seacrhPastList = res.data
setText(true);
}
Hope this helps!
You're in a stateless component you shouldn't use "this" in any way, also you can't use state that way, you need to use react hooks
Import { useState } from 'react'
Then you can use state in a functional component
const [state, setState] = useState(initialvalue);

how can I have a function executed in the child component every time I meet a condition in the parent component to show the child component?

I have a child component called Camera and I call it within a parent component when a condition is met in the style of:
<Camera showCamera = {fieldPhoto} />
 
<!-- component parent -->
handleTakePictureAsync = ({ uri, fieldphoto }) => {
setFieldPhoto(null);
};
const [fieldPhoto, setFieldPhoto] = useState (null)
return(
    <View>
    <Camera
        showCamera = {fieldPhoto}
handleTakePictureAsync={handleTakePictureAsync}
     />
    <Button transparent onPress = {() =>
         setFieldPhoto(true)
     </Button>)
    </View>)
In the child component I have a method to verify if the permissions to use the camera have been accepted, if they have not been accepted, the dialog should be asked asking to allow the camera to run, as many times until it is accepted.
I don't know how to make the confirmation message appear without loading the rest of the component.
<! -- Camera Component -->
export const Camara = props => {
const [hasPermission, setHasPermission] = useState(null);
const [showCamera, setShowCamera] = useState(true);
const propShowCamera = props.showCamera;
const checkPermissionsCamera = () => {
const status = Permissions.askAsync(Permissions.CAMERA).then(permission => {
setHasPermission(status.status === "granted");
});
};
return (
<View>
{checkPermissionsCamera()}
{propShowCamera && hasPermission && ( rest of code
.
.
.
I want to ensure that the rest of the code of the camera component is not loaded until the camera use permissions have been accepted, and that the message asking to accept permissions appears until they are accepted.
Your parent component is passing down prop showCamera which determines whether the Camera is shown. Since this is a boolean value, you should set initial values to false and not null:
const Parent = () => {
handleTakePictureAsync = ({ uri, fieldphoto }) => {
setFieldPhoto(false)
}
const [fieldPhoto, setFieldPhoto] = useState(false)
return (
<View>
<Camera
showCamera={fieldPhoto}
handleTakePictureAsync={handleTakePictureAsync}
/>
<Button transparent onPress={() => setFieldPhoto(true)}></Button>
</View>
)
}
In your child component, you should use a useEffect hook to run the checkPermissionsCamera function - don't call it directly in the render method as this function sets state, which means your component may rerender continuously:
useEffect(() => {
const checkPermissionsCamera = () => {
const status = Permissions.askAsync(Permissions.CAMERA).then(
permission => {
setHasPermission(status.status === 'granted')
}
)
}
checkPermissionsCamera()
}, [setHasPermission])
return (
<View>
// Don't do this:
// {checkPermissionsCamera()}
{propShowCamera && hasPermission && ( rest of code...
I'm also unclear on why you need two showCamera values:
const [showCamera, setShowCamera] = useState(true);
const propShowCamera = props.showCamera;
Also:
the dialog should be asked asking to allow the camera to run, as many
times until it is accepted
Are you sure this is what you want? A never-ending popup asking for camera permission until the user gives permission for the camera to be used? What if they don't want to give permission? You should probably accept that they don't want to and display a message "You need to give camera permission to use this part of the app".
One way to write your camera component could be like this:
export const Camara = ({ showCamera }) => {
const [hasPermission, setHasPermission] = useState(false)
useEffect(() => {
const checkPermissionsCamera = () => {
const status = Permissions.askAsync(Permissions.CAMERA).then(
permission => {
setHasPermission(status.status === 'granted')
}
)
}
checkPermissionsCamera()
}, [setHasPermission])
return (
<View>
{showCamera && hasPermission && <div>Rest of code</div>}
</View>
)
}
Then you wouldn't bug the user with an infinite loop of popups until they grant camera permission.
But if you need permission to show your camera, why render the Camera component in the first place? Would it not be better to handle the permission request in the parent component, then once permission has been granted, render Camera?
This would also fit with your requirement:
I want to ensure that the rest of the code of the camera component is
not loaded until the camera use permissions have been accepted
Just don't render the Camera at all until permission has been granted.
Handling permissions in the parent would look something like this:
// Parent
const Parent = () => {
const handleTakePictureAsync = ({ uri, fieldphoto }) => {
setIsFieldPhoto(false)
}
const [hasCameraPermission, setHasCameraPermission] = useState(false)
const [isFieldPhoto, setIsFieldPhoto] = useState(false)
useEffect(() => {
const requestCameraPermission = () => {
Permissions.askAsync(Permissions.CAMERA).then(
({ status }) => {
setHasCameraPermission(status === 'granted')
}
)
}
if (!hasCameraPermission && isFieldPhoto) {
requestCameraPermission()
}
}, [setHasCameraPermission, hasCameraPermission, isFieldPhoto])
return (
<View>
{hasCameraPermission && isFieldPhoto && (
<Camera handleTakePictureAsync={handleTakePictureAsync} />
)}
<Button transparent onPress={() => setIsFieldPhoto(true)}></Button>
</View>
)
}
// Camera
export const Camara = ({ handleTakePictureAsync }) => {
return (
<View>
<div>Normal camera code</div>
</View>
)
}

Resources