How to execute a function when some item renders in react native? - reactjs

I have a sectionlist of Contacts where I am displaying both device and online contacts of a user. The online contacts api doesnt give me all the contacts at once. So I have to implement some pagination. I am also fetching all device contacts and first page of online contacts and sorting them to show in sectionlist, but the problem is, to load more contacts, i have to keep track of the last item rendered in my state and in the render function I am calling pagination function to load more contacts. and then i am updating the state of fetched online contact. But its an unsafe operation, is there a better way to achieve this?
I want to execute a function when the specific item renders and it can update the state.
Here is some code: ContactList.tsx
import React, { Component } from "react";
import {
View,
StyleSheet,
SectionListData,
SectionList,
Text
} from "react-native";
import { Contact } from "../../models/contact";
import ContactItem from "./contact-item";
export interface ContactsProps {
onlineContacts: Contact[];
deviceContacts: Contact[];
fetchMoreITPContacts: () => void;
}
export interface ContactsState {
loading: boolean;
error: Error | null;
data: SectionListData<Contact>[];
lastItem: Contact;
selectedItems: [];
selectableList: boolean;
}
class ContactList extends Component<ContactsProps, ContactsState> {
private sectionNames = [];
constructor(props: ContactsProps, state: ContactsState) {
super(props, state);
this.state = {
loading: false,
error: null,
data: [],
lastItem: this.props.onlineContacts[this.props.onlineContacts.length - 1]
};
for (var i = 65; i < 91; ++i) {
this.sectionNames.push({
title: String.fromCharCode(i),
data: []
});
}
}
private buildSectionData = contacts => {
this.sort(contacts);
const data = [];
const contactData = this.sectionNames;
contacts.map(contact => {
const index = contact.name.charAt(0).toUpperCase();
if (!data[index]) {
data[index] = [];
contactData.push({
title: index,
data: []
})
}
data[index].push(contact);
});
for (const index in data) {
const idx = contactData.findIndex(x => x.title === index);
contactData[idx].data.push(...data[index]);
}
this.setState({
loading: false,
error: null,
lastItem: contacts[contacts.length - 1],
data: [...contactData]
});
};
private sort(contacts) {
contacts.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (b.name > a.name) {
return -1;
}
return 0;
});
}
componentDidMount() {
const contacts = [].concat(
this.props.deviceContacts,
this.props.onlineContacts
);
this.buildSectionData(contacts);
}
componentDidUpdate(
prevProps: Readonly<ContactsProps>,
prevState: Readonly<ContactsState>,
snapshot?: any
): void {
if (this.props.onlineContacts !== prevProps.onlineContacts) {
const from = this.props.itpContacts.slice(
prevProps.onlineContacts.length,
this.props.onlineContacts.length
);
this.buildSectionData(from);
}
}
renderItem(item: any) {
if (!!this.state.lastItem && !this.state.loading)
if (item.item.id === this.state.lastItem.id) {
this.setState({
loading: true
});
this.props.fetchMoreOnlineContacts();
}
return <ContactItem item={item.item} />;
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={this.state.data}
keyExtractor={(item, index) => item.id}
renderItem={this.renderItem.bind(this)}
renderSectionHeader={({ section }) =>
section.data.length > 0 ? (
<Text style={styles.sectionTitle}>
{section.title}
</Text>
) : null
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
sectionTitle: {
paddingBottom: 30,
paddingLeft: 25,
fontWeight: "bold",
fontSize: 20
}
});
export default ContactList;

Yeah after some thoughts I got the answer may be.
instead of calling fetchMoreContacts from renderItem I passed the lastItem as a prop to the ContactItem component.
and in the constructor I checked If the item is lastItem and called to fetchMoreContact.
and it worked!

Related

React native VirtualizedList Re-render while scroll the list

I have Virtualized List initial render record up to 30 ,while render the data list automatically re render 2 to 4 times and also the new data added to the list
while rendering multi times we can't able to do any action like touch or navigate to another screen
My Code
class HomeDetails extends PureComponent {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = data => {
return data.length;
};
_renderItem =({item,index}) => {
console.log(
'Rerendring',
item.accountId,
moment().format('MM/DD/YY hh:mm:ss a'),
);
return (
<View key={index} style={{height: 50, flexDirection: 'row'}}>
<Text>{`${item.accountId} ${moment().format(
'MM/DD/YY hh:mm:ss a',
)}`}</Text>
</View>
);
}
render(){
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={val => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
)
}
}
const mapStateToProps = ({post, auth, common}) => {
const {
responseRecord,
postLoading,
refreshData,
} = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);
..........................................................................
1.For initial 30 record rendering its re-render more that 2 times
2.when add more records its re-render more than 4 to 6 times
3.tried with purecomponent but no luck
code deployed in snack
https://snack.expo.dev/#pandianvpsm/cd5737
Internal, React's PureComponent implements the shouldComponentUpdate method and compares previous props values and new props or state values to avoid unnecessary re-renders.
This works well for primitive type values like numbers, strings, and booleans.
For referential types values (objects and arrays), this comparison is not always accurate. This is the behavior you have. this.props.responseRecord is an array of objects (referential types).
We can solve this problem by implementing our own componentShouldUpdate method as below:
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord.length
}
Full code below
class HomeDetails extends React.Component {
constructor(props) {
super(props);
this.cellRefs = {};
this.flatListRef = React.createRef();
}
/** Trigger component rerender only new elements added */
shouldComponentUpdate(nextProps, nextState) {
return this.props.responseRecord.length !== nextProps.responseRecord;
}
getItem = (data, index) => {
if (index in data) {
return {
key: `${data[index].id} - ${index}`,
id: data[index].id,
accountId: data[index].accountId,
displayName: data[index].displayName,
fullName: data[index].fullName,
};
}
};
keyExtractor(item, index) {
return `${item.id} - ${index}`;
}
getItemCount = (data) => {
return data.length;
};
_renderItem = ({ item, index }) => {
console.log(
"Rerendring",
item.accountId,
moment().format("MM/DD/YY hh:mm:ss a")
);
return (
<View key={index} style={{ height: 50, flexDirection: "row" }}>
<Text>{`${item.accountId} ${moment().format(
"MM/DD/YY hh:mm:ss a"
)}`}</Text>
</View>
);
};
render() {
return (
<VirtualizedList
onScroll={this.onScrollHandler}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
scrollEventThrottle={16}
ref={this.flatListRef}
horizontal={false}
decelerationRate="normal"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
data={this.props.responseRecord}
pagingEnabled={true}
scrollToOverflowEnabled={false}
renderItem={this._renderItem}
keyExtractor={this.keyExtractor}
getItemCount={this.getItemCount}
getItem={this.getItem}
windowSize={21}
progressViewOffset={20}
initialNumToRender={15}
maxToRenderPerBatch={15}
updateCellsBatchingPeriod={100}
onEndReached={(val) => {
return this.props.getExtraData(2, 1);
}}
onEndReachedThreshold={0.1}
refreshing={this.props.postLoading}
extraData={this.props.refreshData}
disableIntervalMomentum={false}
removeClippedSubviews={true}
onRefresh={() => {
return this.props.getExtraData(1, 1);
}}
ItemSeparator={this.ItemSeparator}
ListFooterComponent={this.renderFooter}
/>
);
}
}
const mapStateToProps = ({ post, auth, common }) => {
const { responseRecord, postLoading, refreshData } = post;
return {
responseRecord,
postLoading,
refreshData,
};
};
const mapDispatchToProps = {
getExtraData,
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeDetails);

React.js error: The service worker navigation preload request was cancelled before 'preloadResponse' settled

I have an issue with my React application (with Redux Saga), I'm getting the console error:
The service worker navigation preload request was cancelled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle.
I see this error on the console only on Chrome, not in Firefox or Edge.
This error does not affect my application.
The following steps reproduce the error:
1. Main page upload.
2. Go to movie details page.
3. Go back to main page.
Main.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { mainActions } from '../../store/actions/actions';
import './Main.scss';
import { MoviesList, SearchPanel } from '../../components';
const propTypes = {};
const defaultProps = {};
class Main extends Component {
constructor(props) {
super(props);
this.handleSearchTextChange = this.handleSearchTextChange.bind(this);
this.handleLoadMoreButtonClick = this.handleLoadMoreButtonClick.bind(this);
this.handleMovieClick = this.handleMovieClick.bind(this);
this.handleFavoriteMovieClick = this.handleFavoriteMovieClick.bind(this);
}
componentDidMount() {
this.handleComponentDidMount();
}
handleComponentDidMount() {
const { moviesList } = this.props;
if (!moviesList || moviesList.length <= 0) {
this.getMovies(null, false);
}
}
handleLoadMoreButtonClick() {
this.getMovies(null, false);
}
handleMovieClick(e) {
if (e.target.className === 'movie') {
this.props.history.push(`/details/${e.currentTarget.dataset.id}`);
}
}
handleSearchTextChange(e) {
const { pageNumber, favoriteMoviesList } = this.props;
this.props.onSearchTextChange({
searchText: e.target.value,
pageNumber: pageNumber,
favoriteMoviesList: favoriteMoviesList
});
}
handleFavoriteMovieClick(e) {
const { id, name, posterId } = e.currentTarget.dataset;
const { moviesList, favoriteMoviesList } = this.props;
this.props.onUpdateFavoriteMovies({
updatedMovie: { id: id, name: name, posterId: posterId },
favoriteMoviesList: favoriteMoviesList,
moviesList: moviesList
});
}
getMovies(updatedSearchText, isSearchChange) {
const { searchText, pageNumber, favoriteMoviesList } = this.props;
this.props.onLoadMovies({
pageNumber: pageNumber,
favoriteMoviesList: favoriteMoviesList,
updatedSearchText: isSearchChange ? updatedSearchText : searchText,
isSearchChange: isSearchChange
});
}
render() {
const { searchText, isLoadingMoreMovies, isPager, moviesList } = this.props;
return (
<div className="main-area">
<SearchPanel
searchText={searchText}
onSearchTextChange={this.handleSearchTextChange}
/>
<MoviesList
pageName='movies'
moviesList={moviesList}
isLoadingMoreMovies={isLoadingMoreMovies}
isPager={isPager}
onLoadMoreClick={this.handleLoadMoreButtonClick}
onMovieClick={this.handleMovieClick}
onFavoriteMovieClick={this.handleFavoriteMovieClick}
/>
</div>
);
}
}
Main.propTypes = propTypes;
Main.defaultProps = defaultProps;
const mapStateToProps = (state) => {
return {
searchText: state.main.searchText,
pageNumber: state.main.pageNumber,
isLoadingMoreMovies: state.main.isLoadingMoreMovies,
isPager: state.main.isPager,
moviesList: state.main.moviesList,
favoriteMoviesList: state.main.favoriteMoviesList
};
};
const mapDispatchToProps = (dispatch) => {
return {
onLoadMovies: (request) => dispatch(mainActions.loadMovies(request)),
onSearchTextChange: (request) => dispatch(mainActions.searchTextChange(request)),
onUpdateFavoriteMovies: (request) => dispatch(mainActions.updateFavoriteMovies(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Main);
Details.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { detailsActions, mainActions } from '../../store/actions/actions';
import './Details.scss';
import { ActorsList, ButtonClick, CrewsList, FeaturesList, PageTitle, ProductionsList, Rating, Trailer } from '../../components';
import movieUtils from '../../utils/movie.utils';
const propTypes = {};
const defaultProps = {};
class Details extends Component {
constructor(props) {
super(props);
this.handleBackClick = this.handleBackClick.bind(this);
this.handleFavoriteMovieClick = this.handleFavoriteMovieClick.bind(this);
this.isFavorite = false;
}
componentDidMount() {
this.handleComponentDidMount();
}
handleComponentDidMount() {
if (this.props.moviesList.length <= 0) {
this.handleBackClick();
return;
}
const movieId = this.props.match.params.id;
if (!movieId) {
this.handleBackClick();
return;
}
this.props.onLoadMovieDetails(movieId);
this.updateIsFavorite(movieId);
}
numberWithCommas(number) {
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
updateIsFavorite(movieId) {
this.isFavorite = this.props.favoriteMoviesList.findIndex(movie => parseInt(movie.id) === parseInt(movieId)) > -1;
}
handleBackClick() {
this.props.history.push(`/`);
}
handleFavoriteMovieClick() {
const { movie, moviesList, favoriteMoviesList } = this.props;
this.props.onUpdateFavoriteMovies({
updatedMovie: { id: movie.id, name: movie.title, posterId: movie.poster_path },
favoriteMoviesList: favoriteMoviesList,
moviesList: moviesList
});
this.updateIsFavorite(movie.id);
}
render() {
const { movie, youtubeKey, credits } = this.props;
if (!movie) {
return null;
}
const { adult, poster_path, budget, genres, homepage, imdb_id, original_language, original_title,
overview, popularity, production_companies, production_countries, release_date, revenue, runtime, spoken_languages,
status, tagline, title, video, vote_average, vote_count } = movie;
const genresText = genres.map(genre => genre.name).join(', ');
const countriesText = production_countries.map(country => country.name).join(', ');
const languagesText = spoken_languages.map(language => language.name).join(', ');
const featuresList = [
{ item: 'Release Date', value: release_date },
{ item: 'Budget', value: `$${this.numberWithCommas(budget)}` },
{ item: 'Revenue', value: `$${this.numberWithCommas(revenue)}` },
{ item: 'Length', value: `${runtime} minutes` },
{ item: 'Popularity', value: popularity },
{ item: 'Original Title', value: original_title },
{ item: 'For Adults', value: adult ? 'Yes' : 'No' },
{ item: 'Original Language', value: original_language },
{ item: 'Spoken Languages', value: languagesText },
{ item: 'Countries', value: countriesText },
{ item: 'Status', value: status },
{ item: 'Is Video', value: video ? 'Yes' : 'No' }
];
const linksList = [];
if (homepage) {
linksList.push({ id: 1, name: 'Homepage', url: homepage });
}
if (imdb_id) {
linksList.push({ id: 2, name: 'IMDB', url: `https://www.imdb.com/title/${imdb_id}` });
}
const actorsList = movieUtils.removeDuplicates(credits ? credits.cast ? credits.cast : null : null, 'name');
const crewsList = movieUtils.removeDuplicates(credits ? credits.crew ? credits.crew : null : null, 'name');
return (
<div>
<section className="details-area">
<PageTitle
pageName='details'
pageTitle='Details'
/>
<ul className="details-content">
<li className="details-left" style={{ backgroundImage: `url('https://image.tmdb.org/t/p/original${poster_path}')` }}></li>
<li className="details-right">
<h2>{title} ({release_date.substr(0, 4)})</h2>
<p className="genres">{genresText}</p>
<p className="description short">{tagline}</p>
<Rating
rating={vote_average}
votesCount={this.numberWithCommas(vote_count)}
/>
<p className="description full">{overview}</p>
<div className="extra">
<FeaturesList
featuresList={featuresList.slice(0, 5)}
linksList={null}
isFavorite={this.isFavorite}
onFavoriteMovieClick={this.handleFavoriteMovieClick}
/>
{youtubeKey && <Trailer
youtubeKey={youtubeKey}
/>}
</div>
</li>
<div className="extra-features">
<FeaturesList
featuresList={featuresList.slice(5, featuresList.length)}
linksList={linksList}
isFavorite={null}
onFavoriteMovieClick={null}
/>
<ProductionsList
productionsList={production_companies}
/>
</div>
</ul>
</section>
<section className="actors-area">
<PageTitle
pageName='actors'
pageTitle='Cast'
/>
<ActorsList
actorsList={actorsList}
/>
</section>
<section className="crew-area">
<PageTitle
pageName='crew'
pageTitle='Crew'
/>
<CrewsList
crewsList={crewsList}
/>
</section>
<ButtonClick
buttonText={'Back'}
buttonTitle={'Back'}
isLoading={false}
onClick={this.handleBackClick}
/>
</div>
);
}
}
Details.propTypes = propTypes;
Details.defaultProps = defaultProps;
const mapStateToProps = (state) => {
return {
movie: state.details.movie,
youtubeKey: state.details.youtubeKey,
credits: state.details.credits,
moviesList: state.main.moviesList,
favoriteMoviesList: state.main.favoriteMoviesList
};
};
const mapDispatchToProps = (dispatch) => {
return {
onLoadMovieDetails: (movieId) => dispatch(detailsActions.loadDetails(movieId)),
onUpdateFavoriteMovies: (request) => dispatch(mainActions.updateFavoriteMovies(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Details);
What I already looked in:
Getting The service worker navigation preload request was cancelled before 'preloadResponse' settled
https://learn.microsoft.com/en-us/answers/questions/108004/getting-the-service-worker-navigation-preload-requ.html
https://support.google.com/mail/thread/4055804?hl=en
https://love2dev.com/pwa/service-worker-preload/
I tried to put this on Details.jsx page, but it didn't work:
self.addEventListener('fetch', event => {
event.respondWith(async function () {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse; // Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response; // Else try the network.
return fetch(event.request);
}());
});
self.addEventListener('activate', event => {
event.waitUntil(async function () {
// Feature-detect
if (self.registration.navigationPreload) { // Enable navigation preloads!
console.log('Enable navigation preloads!');
await self.registration.navigationPreload.enable();
} return;
})();
});
How can I solve this issue? Thanks.
Had same error, even my iframe wasn't loading..whatever video you are using from youtube use nocookie/embed in url. It's working for me.
Try changing https://www.youtube.com/watch?v=i8eBBG46H8A to
https://www.youtube-nocookie.com/embed/i8eBBG46H8A
Hope nocookie & embed helps..!!

Duplicate asynchronous component twice render the last

I create the same component (Recipes) or Screen twice, sending different properties.
<Recipes setRecipes = {this.setRecipes} id="1" />
<Quantity setQuantity = {this.setQuantity} />
<Recipes setRecipes = {this.setRecipes2} id="2" />
<Quantity setQuantity = {this.setQuantity} />
The properties-functions are the following. Only states change.
setRecipes = (recipe) => {
this.setState({recipe:recipe})
}
setRecipes2 = (recipe2) => {
this.setState({recipe2:recipe2})
}
In mty component called Recipes, I get my local database (I use pouchdb), my recipes and products asicronically.
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, ScrollView } from 'react-native';
import style from './Styles/styleNew';
import PouchDB from 'pouchdb-react-native';
PouchDB.plugin(require('pouchdb-find'));
const gThis = null;
const db = new PouchDB('X')
export default class Recipes extends Component {
constructor(props) {
super(props);
gThis = this;
this.state = {
setRecipes: this.props.setRecipes,
id: this.props.id,
recipeID: null,
options: [ ],
};
}
getRecipes() {
let data = []
db.find({
selector: {
type: {
"$eq": this.state.id,
},
owner: {
"$in": ['user']
},
},
}).then(function (recipes) {
recipes = recipes.docs
for (let i = 0; i < recipes.length; i++) {
recipe = recipes[i]
let current = {
id: recipe._id,
name: recipe.name,
recipe: recipe,
}
data.push(current)
}
gThis.setState({ options: data })
}).catch(function (err) {
console.warn(err.toString())
});
}
componentDidMount() {
this.getRecipes()
}
handleBackPress = () => {
this.props.navigation.goBack();
}
defineRecipe(recipe, index) {
this.setState({ recipeID: recipe._id });
this.state.setRecipes(recipe)
}
render() {
return (
<View style={{ flex: 1 }}>
<Text style={style.listText}>RECIPES {this.state.id} </Text>
<ScrollView style={style.listScroll}>
{this.state.options.length >0 &&
this.state.options.map((item, index) => {
key = index + '-' + this.state.id
//key = new Date().toISOString()
console.log(key)
return (
<TouchableOpacity
style={this.state.recipeID === item.id ? style.listOptionSelected : style.listOption}
onPress={() => { this.defineRecipe(item.recipe, index);}}
key={key} >
<Text style={style.listOptionText}>{item.name}</Text>
</TouchableOpacity>
);
})
}
</ScrollView>
</View>
);
}
}
But the second Screen Recipe is rendered twice.
Render 1
Render 2
As you can see, the content is replaced.
I think it might have something to do with you gThis which I assume is a global this?
A better way to try and access this inside your class members is to either use lexical arrow functions in your class like this:
getRecipes = () => {
// implementation
}
Or bind your class members to this in the constructor as follows
constructor(props) {
super(props);
gThis = this;
this.state = {
id: this.props.id,
recipeID: null,
options: [ ],
};
this.getRecipes = this.getRecipes.bind(this);
this.defineRecipe = this.defineRecipe.bind(this);
}
On a different node, you have babel transpiling your ES6 code from as far as I can see.
I would strongly recommend replacing your for loops with es6 variants such as a map or forEach for readability
const options = recipes.map(recipe => {
const { _id: id, name } = recipe;
data.push({
id,
name,
recipe,
});
});
this.setState({ options });

ReactJS simple hover works critical slow

Here's whole class . This example works. No error in ts compiler no errors in browser logs .
import * as React from "react";
import { Label } from "../../components/label/label";
import IApp from "../../globals/interfaces";
import { Tap } from "../../globals/types";
import Services from "../../services/services";
import { HeaderI, HeaderStateI } from "./interface";
import { getMenuStyle, getMyStyle } from "./style";
export class Header extends React.Component<HeaderI, HeaderStateI, any> {
public add = Services.addElement;
private myRef: React.RefObject<HTMLDivElement>;
private myDOM: Element | Text;
constructor(args: any) {
super(args);
this.state = {
enabledComponent: true,
visibility: true,
debugView: false,
elements: [],
myStyle: getMyStyle(),
menuStyle: getMenuStyle(),
menuItemStyle: [getMenuStyle(), getMenuStyle()],
menuIsOpen: false,
myEvent: null,
};
this.myRef = React.createRef();
this.add = this.add.bind(this);
}
// Override func
public componentDidMount() {
this.myDOM = this.myRef.current;
}
public render() {
if ( this.state.visibility === true ) {
return (
<div ref={this.myRef} style={this.state.myStyle} >
<Label myStyle={this.state.menuStyle}
name="headerName"
text="MENU"
onClick={this.clickEvent.bind(this)}
onMouseEnterHandler={this.hoverIn.bind(this)}
onMouseLeaveHandler={this.hoverOut.bind(this)} />
{this.state.elements.map((element: React.ReactElement<any>, index) => {
return <span ref={this.myRef} key={index} style={this.state.menuItemStyle[index]}
onClick={this.clickMenuItem.bind(this)}>{element}</span>;
})}
</div>
);
}
}
// Override func
public componentDidUpdate(prevProps: any, prevState: any) {
// Typical usage (don't forget to compare props):
}
// Override func
public componentWillUpdate(nextProps: any, nextState: any) {
// We should not call setState !
// if (nextState.open == true && this.state.open == false) {
// this.props.onWillOpen();
// }
}
private adaptCss(e: CustomEvent) {
// DEMO for css changes :
// Collect (this of class instance)
const self = e.detail.data.self;
// Make any changes in css
// Collect base or initial css
const myStyle = getMyStyle();
// Make changes
myStyle.background = "yellow";
// Setup state and nothing more
self.setState({
myStyle,
});
}
private printMe() {
// console.log("Layout Header is active and update is on");
}
private clickEvent(event: MouseEvent | TouchEvent) {
if (this.state.menuIsOpen === false) {
const myKey = "header.01";
const element1Args: IApp.NewElementArgsI = {
key: myKey,
onClick: null,
myStyle: null,
content: "HOME",
hoverIn: ((e) => this.hoverIn(e, myKey)),
hoverOut: ((e) => this.hoverOut(e, myKey)),
};
const myKey2 = "header.02";
const element2Args: IApp.NewElementArgsI = {
key: myKey2,
onClick: null,
myStyle: null,
content: "ABOUT",
hoverIn: ((e) => this.hoverIn(e, myKey2)),
hoverOut: ((e) => this.hoverOut(e, myKey2)),
};
this.add(element1Args);
this.add(element2Args);
// Set new state for menu
this.setState(
{menuIsOpen: !this.state.menuIsOpen},
);
} else {
// Menu is already visible , delete menu items
this.setState (
{
menuIsOpen: !this.state.menuIsOpen,
elements: [],
},
);
}
}
private clickMenuItem(event: MouseEvent | TouchEvent) {
const t = event.target as HTMLDivElement;
// Also possible to call event.target.textContent !
switch (t.textContent) {
case "HOME":
this.props.provide({instruction: "show_home"});
break;
case "ABOUT":
this.props.provide({instruction: "show_about"});
break;
default:
console.warn("No case for cleckMenuItem in bodyCOntent class!");
}
}
private hoverIn = (e: Tap, id: any) => {
const styleArrayCopy = JSON.parse(JSON.stringify(this.state.menuItemStyle));
this.state.elements.forEach((element: React.ReactElement<any>, index: number) => {
if (id === element.key) {
styleArrayCopy[index].color = "red";
} else {
styleArrayCopy[index].color = "initial";
}
});
if (this.state.elements.length === 0) {
const test = getMenuStyle();
test.color = "lime";
this.setState({
menuItemStyle: styleArrayCopy,
menuStyle: test,
});
} else {
this.setState({
menuItemStyle: styleArrayCopy,
});
}
}
private hoverOut = (e: Tap, id: any) => {
this.state.elements.forEach((element: React.ReactElement<any>, index: number) => {
if (id === element.key) {
const styleArrayCopy = JSON.parse(JSON.stringify(this.state.menuItemStyle));
styleArrayCopy[index].color = "initial";
this.setState({
menuItemStyle: styleArrayCopy,
});
}
});
}
}
I get style like that :
export function getMenuStyle(): IApp.MyMinimumCssInterface {
return {
display: "block",
background: "#445566",
height: "30px",
width: "100%",
textAlign: "center",
color: "inherits",
} as IApp.MyMinimumCssInterface;
}
On hover in and out i have ~400ms delay . If i moving up/down there is no hover effect. This is so bad. What will be when i add large assets add more code...
I detect very slow executing in not just this example even most simple example from reactJS tutorials ?!
I am also interested in React.ReactElement Object instance . Is it possible for OverRide some func - Like updateDid ?
To missunderstund from comment This is from React site :
The style attribute accepts a JavaScript object with camelCased
properties rather than a CSS string. This is consistent with the DOM
style JavaScript property, is more efficient, and prevents XSS
security holes. For example:
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
I use it the same principle.
Updated , now looks like :
// Render
{this.state.elements.map((element: React.ReactElement<any>, index) => {
return <span ref={this.myRef} key={index} style={this.getStyle(element, index)} onClick={this.clickMenuItem.bind(this)}>{element}</span>;
})}
private hoverIn = (e: Tap, id: any) => {
const local: boolean[] = [];
this.state.elements.forEach((element: React.ReactElement<any>, index: number) => {
if (id === element.key) {
local.push(true);
} else {
local.push(false);
}
});
Any explanation ??
I finally found solution for very slow work flows.
There is no connections with React at all.
I have global.css who's loading on begin. First i noticed that only text delay.
I create text shadow and i most suspect on shadow but it was transition.
This code is most expensively code was :
-webkit-transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
-moz-transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
-o-transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
transition: color 300ms cubic-bezier(0.42, 0, 0.58, 1);
I remove this part of code.
Maybe this answer will help somebody .

React native delete multiple items from state array

I have a directory which stores images taken using the camera. For saving images I am using RNFS. I am using react-native-photo-browser.
The gallery itself doesn't have any options to delete the items from the gallery. So I am working to achieve it
export default class GridGallery extends React.Component{
static navigationOptions = {
title: 'Image Gallery',
};
constructor(props) {
super(props)
this.state = {
filesList : [],
mediaSelected: [],
base64URI: null,
galleryList: []
}
}
componentDidMount(){
FileList.list((files) => {
if(files != null) {
this.fileUrl = files[0].path;
files = files.sort((a, b) => {
if (a.ctime < b.ctime)
return 1;
if (a.ctime > b.ctime)
return -1;
return 0;
});
this.setState({
filesList: files
});
}
console.warn(this.state.filesList);
this.getFiles();
});
}
getFiles(){
//console.warn(this.state.filesList);
const ArrFiles = this.state.filesList.map((file) =>
({ caption : file.name, photo : file.path })
);
//console.warn(ArrFiles);
this.setState({ galleryList : ArrFiles });
}
onActionButton = (media, index) => {
if (Platform.OS === 'ios') {
ActionSheetIOS.showShareActionSheetWithOptions(
{
url: media.photo,
message: media.caption,
},
() => {},
() => {},
);
} else {
alert(`handle sharing on android for ${media.photo}, index: ${index}`);
}
};
handleSelection = async (media, index, isSelected) => {
if (isSelected == true) {
this.state.mediaSelected.push(media.photo);
} else {
this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);
}
console.warn(this.state.mediaSelected);
}
deleteImageFile = () => {
const dirPicutures = RNFS.DocumentDirectoryPath;
//delete mulitple files
console.warn(this.state.mediaSelected);
this.state.mediaSelected.map((file) =>
// filepath = `${dirPicutures}/${file}`
RNFS.exists(`${file}`)
.then( (result) => {
console.warn("file exists: ", result);
if(result){
return RNFS.unlink(`${file}`)
.then(() => {
console.warn('FILE DELETED');
let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })
})
// `unlink` will throw an error, if the item to unlink does not exist
.catch((err) => {
console.warn(err.message);
});
}
})
.catch((err) => {
console.warn(err.message);
})
)
}
renderDelete(){
const { galleryList } = this.state;
if(galleryList.length>0){
return(
<View style={styles.topRightContainer}>
<TouchableOpacity style={{alignItems: 'center',right: 10}} onPress={this.deleteImageFile}>
<Image
style={{width: 24, height: 24}}
source={require('../assets/images/ic_delete.png')}
/>
</TouchableOpacity>
</View>
)
}
}
goBack() {
const { navigation } = this.props;
navigation.pop;
}
render() {
const { galleryList } = this.state;
return (
<View style={styles.container}>
<View style={{flex: 1}}>
<PhotoBrowser
mediaList={galleryList}
enableGrid={true}
displayNavArrows={true}
displaySelectionButtons={true}
displayActionButton={true}
onActionButton={this.onActionButton}
displayTopBar = {true}
onSelectionChanged={this.handleSelection}
startOnGrid={true}
initialIndex={0}
/>
</View>
{this.renderDelete()}
</View>
)
}
}
An example list of images:
[
{
photo:'4072710001_f36316ddc7_b.jpg',
caption: 'Grotto of the Madonna',
},
{
photo: /media/broadchurch_thumbnail.png,
caption: 'Broadchurch Scene',
},
{
photo:
'4052876281_6e068ac860_b.jpg',
caption: 'Beautiful Eyes',
},
]
My aim is whenever the item from state galleryList is removed I need to refresh the component, so the deleted image will be removed from the gallery. So When I try to use filter the galleryList it deleting other images instead of other images:
let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })
MCVE -> This is a minified version of my code, you can see the images are deleting randomly
Problem
let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })
Since setState is async, this.state.galleryList will not be updated in each iteration of your map function, so the final updated state will only have one item filtered out instead of all selected items.
Solution
You can use the callback version of setState which uses the updated state instead:
this.setState(prevState => ({
galleryList : prevState.galleryList.filter(item => item.photo !== file),
}));
Alternative solution
Instead of calling setState in every iteration, you can call it outside of your map function instead (though setState updates will be batched anyway so no significant performance improvement):
this.setState(prevState => ({
galleryList : prevState.galleryList.filter(item => !prevState.mediaSelected.includes(item.photo)),
}));
Other problems with your code
this.state.mediaSelected.push(media.photo);
} else {
this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);
You are directly mutating your state here. Do this instead:
this.setState(prevState => ({
mediaSelected: prevState.mediaSelected.concat(media.photo)
}));
this.setState(prevState => ({
mediaSelected: prevState.mediaSelected.filter(e => e != media.photo)
}));

Resources