How to write conditional css with changable values - javascript

I have a Card looks like this
<Card.Header style={room.roomCapacity === room.students.length ? {backgroundColor: "#DC4C64"} : room.students.length === 0 ? {backgroundColor: '#14A44D'} : {backgroundColor: '#E4A11B'}} className={"text-center CardHeader"}>{room.roomNumber}</Card.Header>
But I want to write all styles in css. So how can i implement style={} function into css with these conditions?

You could use a memoization function with useMemo to get the correct color based on your conditions.
import { useMemo } from 'react';
const ExampleComponent = ({ room }) => {
const isRoomFull = room.roomCapacity === room.students.length;
const isRoomEmpty = room.students.length === 0;
const backgroundColor = useMemo(() => {
if (isRoomFull) {
return '#DC4C64';
}
if (isRoomEmpty) {
return '#14A44D';
}
return '#E4A11B';
}, [isRoomFull, isRoomEmpty]);
return (
<Card.Header
className="text-center CardHeader"
style={{
backgroundColor
}}
>
{room.roomNumber}
</Card.Header>
);
};

Related

Storing dynamically rendered page elements in state, and keeping variables functional?

I might have asked this question in a confusing way, so I apologize. But I have a map function that renders a lot of text elements dynamically and also styles them based on state. However, the calculations used to create the elements in the first place seems really expensive performance-wise. So I would like to render them once, and then store the created elements in state, and yet still have the styling rerender when it needs to.
I tried storing these mapped elements in an array, but the styling variables inside of each component are set to a single value when the component is stored. So rerendering the page doesn't change the styling of these components even if the initial variables used to set their styles in state have changed.
import React, {useState} from 'react';
import { Text, View, StyleSheet } from 'react-native';
export default function App() {
let [redText, setRedText] = useState(['This', 'That'])
let [blueText, setBlueText] = useState(['The', 'Other'])
let str = 'This That And The Other'
let arr = str.split(" ")
let componentsArr = null
function firstRender() {
componentsArr = []
componentsArr.push(arr.map((el) => {
return (
<View style={styles.container}>
<Text style={redText.includes(el)
? styles.redText
: blueText.includes(el)
? styles.blueText
: styles.blackText}>
{el}
</Text>
</View>
)
}))
return componentsArr
}
return (
<View style={styles.container}>
{componentsArr ? componentsArr : firstRender()}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
blackText: {
color: "black"
},
redText: {
color: "red"
},
blueText: {
color: "blue"
}
});
Let's say I have some code like this that adds an onPress event to each element that will automatically change it to red. How can I do that without mapping through and creating the View and Text components from scratch?
When it is initially pushed into the array, all of the styling variables are set in stone. Is there some way to preserve the ternary operations?
i'm not sure i understand well what you wanted to do but as i understood, every word in the text must manage it's own toggle color ? So here how i would go.
export const App = () => {
const [texts, setTexts] = useState(['This', 'That', 'And', 'The', 'Other']);
const renderTexts = () => {
return texts.map(text => (
<CustomTextColorToggle el={text} key={text} />
));
};
return (
<View style={styles.container}>
{renderTexts()}
</View>
);
}
// Here defaultColor is optional if you want to add some
// more logic
const CustomTextColorToggle = ({ defaultColor, el }) => {
const [color, setColor] = useState(defaultColor);
const styles = color === "red"
? styles.redText
: color === "blue"
? styles.blueText
: styles.blackText;
return (
<View style={styles.container}>
<Text style={styles}>
{el}
</Text>
</View>
);
};
Inside CustomTextColorToggle you can wrap the View with a Pressable to change the color using setColor
This sounds like a good use case for memoization. If you want to prevent rendering of the list and its elements, unless styles change, you need to apply this in two places, the <View/> wrapper containing el and the entire list itself. Below is how I would apply it.
import React, { useState, memo, useEffect } from "react";
import { Text, View, StyleSheet } from "react-native";
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
blackText: {
color: "black",
},
redText: {
color: "red",
},
blueText: {
color: "blue",
},
});
const stylesAreEqual = (prevProps, nextProps) => {
//we only compare styles since we assume el prop wont change
return (
prevProps.redText === nextProps.redText &&
prevProps.blueText === nextProps.blueText
);
};
//this component only re-renders if redText or blueText change.
//if el changes, it doesnt re-render. If you want this behavior to change,
//remove the stylesAreEqual function from the memo callback
const ViewItem = ({ el, redText, blueText }) => {
//responds to any styles passed into the component
const propStyle = redText.includes(el)
? styles.redText
: blueText.includes(el)
? styles.blueText
: styles.blackText;
//if you want to control the styles indiviually for each view item without needing
// to change redText, or blue Text props. It can managed locally here
const [style, setStyle] = useState(propStyle);
const onPressEvent = () => {
//write any change to style on a press
setStyle({ color: "black" });
};
useEffect(() => {
//if you want to respond to changed passed from a higher up component.
// though consider using useContext for this if your handling anything other
// than primary types (i.e strings, boolean, etc.)
setStyle(propStyle);
}, [propStyle]);
return (
<View style={styles.container}>
<Text onPress={onPressEvent} style={style}>
{el}
</Text>
</View>
);
};
const MemoViewItem = ({ el, redText, blueText }) =>
memo(
<ViewItem el={el} redText={redText} blueText={blueText} />,
stylesAreEqual
);
const MemoList = ({ arr, redText, blueText }) =>
//this means that unless the props passed into this component change,
// it will not re-render, even if a component above it does for any case.
memo(
<>
{arr.map((el) => {
return <MemoViewItem el={el} redText={redText} blueText={blueText} />;
})}
</>
);
export default function App() {
let [redText, setRedText] = useState(["This", "That"]);
let [blueText, setBlueText] = useState(["The", "Other"]);
let str = "This That And The Other";
let arr = str.split(" ");
let componentsArr = null;
function firstRender() {
componentsArr = [];
componentsArr.push(
<MemoList arr={arr} blueText={blueText} redText={redText} />
);
return componentsArr;
}
return (
<View style={styles.container}>
{componentsArr ? componentsArr : firstRender()}
</View>
);
}
Since I'm unsure if you want to change styles from onpress events, or from a general state coming from a higher component, I've included both in this example. In addition, depending on your use case, you can modify this above, and test where you need memoization or not since adding it does add extra overhead if its not necessary.
As a note though, the only way to prevent the list from re-rendering at all (only once on mount), is to manage the styles in a local component
In this case, removing redText and blueText from the App component, and removing the props on every component down the tree. From there, you can manage the styles inside the ViewItem component. Should this be the case, you can also remove the memo function. Below is an example.
import React, { useState, memo } from "react";
import { Text, View, StyleSheet } from "react-native";
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
blackText: {
color: "black",
},
redText: {
color: "red",
},
blueText: {
color: "blue",
},
});
const ViewItem = ({ el }) => {
//if you want to control the styles indiviually for each view item without needing
// to change redText, or blue Text props. It can managed locally here
const [redText, setRedText] = useState(["This", "That"]);
const [blueText, setBlueText] = useState(["The", "Other"]);
const style = redText.includes(el)
? styles.redText
: blueText.includes(el)
? styles.blueText
: styles.blackText;
//const [styles, setStyles] = useState({});
const onPressEvent = () => {
//write any change to style on a press
setRedText("Cool");
};
return (
<View style={styles.container}>
<Text onPress={onPressEvent} style={style}>
{el}
</Text>
</View>
);
};
//prevent list from re-rendering unless app prop changes
const MemoList = ({ arr }) => memo(
<>
{arr.map((el) => (
<ViewItem el={el} />
))}
</>
);
export default function App() {
let str = "This That And The Other";
let arr = str.split(" ");
let componentsArr = null;
function firstRender() {
componentsArr = [];
componentsArr.push(<MemoList arr={arr} />);
return componentsArr;
}
return (
<View style={styles.container}>
{componentsArr ? componentsArr : firstRender()}
</View>
);
}

React Native performance slow

I am new to React Native, I've created an Instagram Clone with customizations, but there got some issues.
FlatList Re-Renderring is slow.
When the like button is Pressed, FlatList is taking 2 Seconds to re-render. So I tried into Flipkart's RecyclerView Package, that is too taking 400-600 ms. I've came to know that Instagram and Facebook are built at React Native, but they don't take this much time on like. I guess something is wrong in my code.
I got Recycler View package from here
Re-Rendering is slow.
In those screens without any List, there is too an issue of slow re-rendering.
Material Top Tabs Navigation is slow.
I've found that React Navigations's Material Top navigation is working absolutely fine on swipe, but on button click, it is taking 2-4 Seconds.
Here is my code for feed page.
import React, { useEffect, useState, useRef } from 'react';
import { SafeAreaView, Pressable, AppRegistry, Text, View, Image, TouchableOpacity, StyleSheet, ImageBackground, ActivityIndicator, Platform } from 'react-native';
import { Pranah } from '../pranah/cust';
import { colors } from '../pranah/colors';
import { uni } from '../css/uni';
import axios from 'axios';
import base64 from 'react-native-base64';
import AsyncStorage from '#react-native-async-storage/async-storage';
import { DataProvider, LayoutProvider, RecyclerListView } from 'recyclerlistview';
import { UserHead } from './tminc/userhead';
import { AntDesign, FontAwesome5, Feather } from '#expo/vector-icons';
import { design } from './tminc/design';
import { WebBasedNavigation } from './tminc/widenav'
const style = StyleSheet.create({
web: {
width: uni.dev("100%", "100%", "40%"),
height: uni.dev("100%", "100%", uni.height - 50),
marginLeft: uni.dev(0, 0, 10 / 100 * uni.width)
}
});
const postDesign = {
width: uni.dev(95 / 100 * uni.width, 95 / 100 * uni.width, 35 / 100 * uni.width),
height: uni.dev(95 / 100 * uni.width, 95 / 100 * uni.width, 35 / 100 * uni.width),
backgroundColor: "#ededed",
borderRadius: 10,
}
const iconDynamicSizing = 25;
const iconDesign = StyleSheet.create({
icon: {
margin: 10
}
});
//POST PART IN PARTS
//USER HEAD
function ListHead(txt) {
return (
<>
<Text
style={{
fontSize: 35,
fontWeight: "bold",
margin: 20
}}
>{txt.txt}</Text>
</>
)
}
function MediaCont(obj) {
return (
<View
style={design.media}
>
<Image
source={{ uri: obj.url }}
defaultSource={{ uri: obj.url }}
style={postDesign}
/>
<View
style={design.mediaSnap}
>
<Text style={design.mediaCap}>{obj.caption.length > 20 ? `${obj.caption.substring(0, 20)}...` : obj.caption}</Text>
</View>
</View>
);
}
function TextCont(obj) {
return (
<View
style={design.textContParent}
>
<View
style={[postDesign, design.center]}
>
<Text
style={design.textMain}
>{obj.caption}</Text>
</View>
</View>
);
}
let layoutProvider = new LayoutProvider(
index => {
return index == 0 ? "HEAD" : "NORMAL";
},
(type, dim) => {
switch (type) {
case "NORMAL":
dim.height = uni.dev(uni.width + 150, uni.width + 150, 40 / 100 * uni.width + 150);
dim.width = uni.dev(uni.width, uni.width, 40 / 100 * uni.width);
break;
case "HEAD":
dim.height = 85;
dim.width = uni.dev(uni.width, uni.width, 40 / 100 * uni.width);
break;
}
}
);
function PostLikes(obj) {
let post = obj.postId;
let like = parseInt(obj.like);
let navigation = obj.screenNav;
let toprint;
if (like == 0) {
toprint = uni.lang("इसे पसंद करने वाले पहले व्यक्ति बनें", "Be first to like this.");
} else if (like == 1) {
toprint = uni.lang("एक व्यक्ति द्वारा पसंद किया गया", "Liked by one person");
} else {
like = String(like);
toprint = uni.lang(`${like} लोगो ने पसंद किया`, `${like} likes`);
}
return (
<>
<TouchableOpacity
onPress={() => {
navigation.push('LikeList', { postId: post });
}}
>
<Text
style={{
marginLeft: uni.dev(5 / 100 * uni.width, 5 / 100 * uni.width, 4 / 100 * uni.width),
fontWeight: "bold",
marginTop: 5
}}
>{toprint}</Text>
</TouchableOpacity>
</>
);
}
const headerComp = ({
title: uni.lang("सबकुछ ||", "Everything."),
type: "head"
});
export function Feed({ navigation }) {
const [List, setData] = useState([headerComp]);
const [FooterConst, setFoot] = useState(true);
const [start, setStart] = useState(0);
// navigation.setOptions({ tabBarVisible: false });
let dataProvider = new DataProvider((r1, r2) => {
return r1 !== r2;
}).cloneWithRows(List);
function fetchMore() {
AsyncStorage.getItem("mail")
.then((val) => {
let mail = val;
AsyncStorage.getItem("pass")
.then((value) => {
let pass = value;
// CONNECTING TO SERVER
axios({
method: 'post',
url: uni.bind('feed'),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: uni.string({
mail: mail,
pass: base64.encode(pass),
start: start
})
})
.then((resp) => {
if (resp.status == 200) {
let page = resp.data;
/*
SERVER RETURNS
nomore | followernull | error | invalid | {json data}
*/
if (uni.logic(page) === "error") {
uni.Error();
} else if (uni.logic(page) === "followernull" || uni.logic(page) === "nomore") {
//SET FOOTER
setFoot(false);
} else if (uni.logic(page) === "invalid") {
//SIGNOUT
uni.signOut(navigation);
} else {
setStart(start + 20);
setData(
[
...List,
...page
]
);
}
} else {
uni.Error();
}
})
.catch((e) => {
uni.Error();
});
})
.catch((e) => { uni.signOut(navigation) })
})
.catch(() => { uni.signOut(navigation) })
}
function PostAction(obj) {
let index = obj.in;
function addRemoveLike() {
let temp = List;
temp[index].liked = temp[index].liked === "true" ? "false" : "true";
// console.warn(temp[index]);
setData([...temp]);
//SAVING LIKE ON SERVER
AsyncStorage.getItem("mail")
.then((val) => {
let mail = val;
AsyncStorage.getItem("pass")
.then((value) => {
let pass = value;
// CONNECTING TO SERVER
axios({
method: 'post',
url: uni.bind('like'),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: uni.string({
mail: mail,
pass: base64.encode(pass),
post: String(obj.id)
})
})
.then((resp) => {
if (resp.status == 200) {
let page = resp.data;
/*
SERVER RETURNS
true | error | invalid
*/
if (uni.logic(page) === "error") {
uni.Error();
} else if (uni.logic(page) === "invalid") {
uni.signOut(navigation);
}
} else {
uni.Error();
}
})
.catch((e) => { uni.Error() });
})
.catch((e) => { uni.signOut(navigation) })
})
.catch(() => { uni.signOut(navigation) })
}
return (
<>
<View
style={design.postActionParent}
>
<TouchableOpacity
onPress={() => {
// console.warn(likeRef.current);
// console.warn(likeRef.current);
addRemoveLike();
}}
><AntDesign name={obj.liked === "true" ? "heart" : "hearto"} size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
<TouchableOpacity onPress={() => {
navigation.push('Comment', { postId: obj.id });
}}><FontAwesome5 name="comment" size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
<TouchableOpacity><Feather name="send" size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
<TouchableOpacity><AntDesign name="retweet" size={iconDynamicSizing} color="black" style={iconDesign.icon} /></TouchableOpacity>
</View>
<View
style={design.underLinePrnt}
>
<View style={design.underline}></View>
</View>
</>
);
}
function TextPost(params) {
let item = params.data;
let index = params.in;
return (
<>
<UserHead dp={item.dp} name={item.name} user={item.username} />
<Pressable onLongPress={() => { alert('null') }}><TextCont caption={item.caption} /></Pressable>
<PostLikes like={item.fav} postId={item.id} screenNav={navigation} />
<PostAction liked={item.liked} in={index} id={item.id} />
</>
);
}
function MediaPost(params) {
let item = params.data;
let index = params.in;
return (
<>
<UserHead dp={item.dp} name={item.name} user={item.username} />
<MediaCont url={item.url} caption={item.caption} />
<PostLikes like={item.fav} postId={item.id} screenNav={navigation} />
<PostAction liked={item.liked} in={index} id={item.id} />
</>
);
}
function ListItem(type, data, index) {
let item = data;
return item.type === "head" ? <ListHead txt={item.title} /> : item.type === "text" ? <TextPost data={item} in={index} /> : <MediaPost data={item} in={index} />;
}
useEffect(function () {
// let tmp = List.push(json);
// setData([
// ...List,
// ...json
// ]);
navigation.setOptions({ tabBarVisible: uni.isPC() == true ? false : true })
fetchMore();
}, []);
function footerComp() {
return FooterConst == true ? (
<>
<ActivityIndicator size={"large"} color={colors.primary} />
<Pranah.br height={20} />
</>) : (
<>
<Text
style={{
textAlign: "center",
width: "100%",
fontSize: 20,
fontWeight: "bold",
paddingBottom: 13
}}
>{uni.lang("सूची का अंत", "End of Posts")}</Text>
</>
);
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#FFFFFF" }}>
<ImageBackground
style={{
width: "100%",
height: "100%"
}}
source={require('../assets/background_mobile.png')}
>
<Pranah.stb />
<Pranah.pranahHead nav={navigation} />
<View
style={{ width: "100%", height: "100%", flexDirection: "row" }}
>
<View
style={style.web}
>
<RecyclerListView
dataProvider={dataProvider}
rowRenderer={ListItem}
layoutProvider={layoutProvider}
extendedState={{ List }}
renderFooter={footerComp}
onEndReached={fetchMore}
/>
</View>
<WebBasedNavigation navigation={navigation} />
</View>
</ImageBackground>
</SafeAreaView>
);
}
There were lags in iOS and Web too but those were acceptable.
I know, I've done very wrong with AsyncStorage, please tell me a short way to do that too.
Thanks in advance.
In your case, I don't know why you are using another package when react-native contains a built-in component called as FlatList which is backed by virtualised rendering.
Make this changes
rowRenderer={() => ListItem()}
renderFooter={() => footerComp()}
Check the () => arrow function this will assign the method only once on the initial render. You need to provide a https://reactnative.dev/docs/flatlist#keyextractor prop to create a unique ID for all the rendered items (will be used when you want to do some action like remove element or update).
With this simple change, you should see a lot of performance improvement for the initial render & for each re-render.
Do the same for the props that accept a function as a param.
IDK why are you storing the value on async storage they should be store in a local variable like the useState hook. Keep an eye on the API call if you do frequent API calls or on each re-render surely it will reduce the app performnce.
My Opinion
React & React Native are fast by default but developers use a lot of anti-pattern code and make the application slow and complain RN is slow.
Here you can find some of the common things which cause performance issues in react native.
https://reactnative.dev/docs/performance

React Bottleneck TextInput Inputfield

I've got a big react app (with Redux) here that has a huge bottleneck.
We have implemented a product search by using product number or product name and this search is extremely laggy.
Problem: If a user types in some characters, those characters are shown in the InputField really retarded. The UI is frozen for a couple of seconds.
In Internet Explorer 11, the search is almost unusable.
It's a Material UI TextField that filters products.
What I already did for optimization:
Replaced things like style={{
maxHeight: 230,
overflowY: 'scroll',
}} with const cssStyle={..}
Changed some critical components from React.Component to React.PureComponent
Added shouldComponentUpdate for our SearchComponent
Removed some unnecessary closure bindings
Removed some unnecessary objects
Removed all console.log()
Added debouncing for the input field (that makes it even worse)
That's how our SearchComponent looks like at the moment:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Downshift from 'downshift';
import TextField from '#material-ui/core/TextField';
import MenuItem from '#material-ui/core/MenuItem';
import Paper from '#material-ui/core/Paper';
import IconTooltip from '../helper/icon-tooltip';
import { translate } from '../../utils/translations';
const propTypes = {
values: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
legend: PropTypes.string,
helpText: PropTypes.string,
onFilter: PropTypes.func.isRequired,
selected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
isItemAvailable: PropTypes.func,
};
const defaultProps = {
legend: '',
helpText: '',
selected: '',
isItemAvailable: () => true,
};
const mapNullToDefault = selected =>
(selected === null || selected === undefined ? '' : selected);
const mapDefaultToNull = selected => (!selected.length ? null : selected);
class AutoSuggestField extends Component {
shouldComponentUpdate(nextProps) {
return this.props.selected !== nextProps.selected;
}
getLegendNode() {
const { legend, helpText } = this.props;
return (
<legend>
{legend}{' '}
{helpText && helpText.length > 0 ? (
<IconTooltip helpText={helpText} />
) : (
''
)}
</legend>
);
}
handleEvent(event) {
const { onFilter } = this.props;
const value = mapDefaultToNull(event.target.value);
onFilter(value);
}
handleOnSelect(itemId, item) {
const { onFilter } = this.props;
if (item) {
onFilter(item.label);
}
}
render() {
const { values, selected, isItemAvailable } = this.props;
const inputValue = mapNullToDefault(selected);
const paperCSSStyle = {
maxHeight: 230,
overflowY: 'scroll',
};
return (
<div>
<div>{this.getLegendNode()}</div>
<Downshift
inputValue={inputValue}
onSelect={(itemId) => {
const item = values.find(i => i.id === itemId);
this.handleOnSelect(itemId, item);
}}
>
{/* See children-function on https://github.com/downshift-js/downshift#children-function */}
{({
isOpen,
openMenu,
highlightedIndex,
getInputProps,
getMenuProps,
getItemProps,
ref,
}) => (
<div>
<TextField
className="searchFormInputField"
InputProps={{
inputRef: ref,
...getInputProps({
onFocus: () => openMenu(),
onChange: (event) => {
this.handleEvent(event);
},
}),
}}
fullWidth
value={inputValue}
placeholder={translate('filter.autosuggest.default')}
/>
<div {...getMenuProps()}>
{isOpen && values && values.length ? (
<React.Fragment>
<Paper style={paperCSSStyle}>
{values.map((suggestion, index) => {
const isHighlighted = highlightedIndex === index;
const isSelected = false;
return (
<MenuItem
{...getItemProps({ item: suggestion.id })}
key={suggestion.id}
selected={isSelected}
title={suggestion.label}
component="div"
disabled={!isItemAvailable(suggestion)}
style={{
fontWeight: isHighlighted ? 800 : 400,
}}
>
{suggestion.label}
</MenuItem>
);
})}
</Paper>
</React.Fragment>
) : (
''
)}
</div>
</div>
)}
</Downshift>
</div>
);
}
}
AutoSuggestField.propTypes = propTypes;
AutoSuggestField.defaultProps = defaultProps;
export default AutoSuggestField;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.0/umd/react-dom.production.min.js"></script>
It seems, that I did not find the performance problem as it still exists. Can someone help here?

React state passes from one component to the next inconsistently

I have two components: LeagueSelect and TeamSelect.
All I'm trying to do right now is pass the checkedLeagues state from LeagueSelect to TeamSelect.
It's currently setup to have the checkboxes in TeamSelect be checked if the corresponding league is checked.
The issue: the state passes from LeagueSelect to TeamSelect inconsistently.
This is a video of what it looks like:
https://streamable.com/2i06g
When a box is unchecked, the state updates 'in team', as you can see in the console.log, but, when you try to check the same box again the state does not update in team.
I initially tried to implement this with redux, thought this issue was a redux issue, moved to directly passing state to the child component, and realized that the issue must be somewhere else.
This is my LeagueSelect component:
import React, { Component } from 'react';
import { View, Text, Modal, TouchableHighlight, FlatList, Button } from 'react-native'
import { loadLeagues } from '../actions'
import { connect } from 'react-redux'
import Check from './CheckBox'
import axios from "axios"
import { loadCards, loadTeams, changeLeagues } from '../actions'
import { Icon } from 'native-base'
import TeamSelect from './TeamSelect'
class LeagueSelect extends Component {
constructor(props) {
super(props)
this.state = {
modalVisible: false,
checked: [],
checkedLeagues: [],
checkMessage: ''
}
}
setModalVisible(visible) {
this.setState({modalVisible: visible})
if(this.state.checked.length === 0) {
this.props.league.map(
(v, i) => {
this.state.checked.push(true)
this.state.checkedLeagues.push(v.acronym)
}
)
}
this.setState({ checkMessage: '' })
}
changeCheck = (index, acronym) => {
//local variable to create query param
firstString = []
//adds to local variable if not checked, takes out variable if checked
if(!this.state.checkedLeagues.includes(acronym)) {
firstString.push(acronym)
} else {
firstString.filter(v => { return v !== acronym})
}
//adds leagues that could be in the current state that were not just passed in
this.state.checkedLeagues.map(
(v, i) => {
if(v !== acronym) {
firstString.push(v)
}
}
)
//updates checked leagues state
//makes api call with parameters set
//prevents all leagues being unselected
if(acronym === this.state.checkedLeagues[0] && firstString.length === 0) {
this.setState({ checkMessage: `Don't you want something to look at?` })
} else {
if(!this.state.checkedLeagues.includes(acronym)){
this.state.checkedLeagues[this.state.checkedLeagues.length] = acronym
this.setState({ checkedLeagues: this.state.checkedLeagues })
} else {
newChecked = this.state.checkedLeagues.filter(v => { return v !== acronym})
this.setState({checkedLeagues: newChecked})
}
//updating the check
this.state.checked[index] = !this.state.checked[index]
this.setState({ checked: this.state.checked })
queryString = []
firstString.map(
(v, i) => {
if (queryString.length < 1) {
queryString.push(`?league=${v}`)
} else if (queryString.length >= 1 ) {
queryString.push(`&league=${v}`)
}
}
)
axios.get(`http://localhost:4000/reports${queryString.join('')}`)
.then(response => {
this.props.loadCards(response.data)
})
}
}
render() {
return (
<View style={{ position: 'relative'}}>
<Text
style={{
paddingTop: 8,
paddingLeft: 5,
fontSize: 15
}}
>Leagues</Text>
<View
style={{
flexDirection:"row",
}}
>
{this.props.league === null ?'' : this.props.league.map(
(v, i) => {
return(
<View
key={i}
style={{
alignSelf: 'flex-end',
flexDirection:"row",
top: 4,
}}
>
<Check
checked={this.state.checked[i]}
index={i}
value={v.acronym}
changeCheck={this.changeCheck}
/>
<Text
style={{
paddingLeft: 23,
}}
>{v.acronym}</Text>
</View>
)
}
)}
</View>
<Text
style={{
paddingLeft: 10,
paddingTop: 12,
fontStyle: 'italic',
color: '#F4AF0D'
}}
>{this.state.checkMessage}</Text>
<TeamSelect checkedLeagues={this.state.checkedLeagues}/>
</View>
</View>
);
}
}
export default LeagueSelect
This is my TeamSelect component:
import React, { Component } from 'react'
import { Text, View } from 'react-native'
import { connect } from 'react-redux'
import { loadTeams, loadLeagues } from '../actions'
import Check from './CheckBox'
class TeamSelect extends Component {
constructor(props) {
super(props)
this.state = {
checked: [],
checkedTeams: [],
setOnce: 0
}
}
render() {
console.log('in team', this.props.checkedLeagues)
return(
<View>
{
this.props.team === null ?'' : this.props.team.map(
(v, i) => {
return(
<View key={i}>
<Check
checked={ this.props.checkedLeagues.includes(v.league.acronym) ? true : false }
index={i}
value={v.team_name}
changeCheck={this.changeCheck}
/>
{ v.team_name === undefined ? null :
<Text>{v.team_name}</Text>}
</View>
)
}
)
}
</View>
)
}
}
export default TeamSelect
this.setState({ checkedLeagues: this.state.checkedLeagues })
Statements like these can cause issues as you are mutating and setting the state to the same object. The reference to checked leagues doesn't get updated and react may not trigger a render. Use this instead
this.setState({ checkedLeagues: [...this.state.checkedLeagues] })
But this whole approach to the problem is wrong, you should use one leagues object that has a checked property to it, and pass it down.
make you league object look like this,
const leagues = [
{
acronym: 'abc',
checked: false,
teams: [ ...array of teams here ]
},
...
]
When you pass it down to TeamSelect, you can map it like this
const { leagues } = this.props
{leagues && leagues.map((league, i) => league.teams.map((team, j) (
<View key={team.team_name}>
<Check
checked={league.checked}
index={i + i * j}
value={team.team_name}
changeCheck={() => this.changeCheck(i, j)}
/>
{team.team_name && <Text>{team.team_name}</Text>}
</View>)))}
Same with leagueSelect, you can map leagues like this:
const { leagues } = this.state
{leagues.map((league, i) => (
<View
key={league.acronym}
style={{
alignSelf: 'flex-end',
flexDirection:"row",
top: 4,
}}>
<Check
checked={league.checked}
index={i}
value={league.acronym}
changeCheck={this.changeCheck}
/>
<Text
style={{
paddingLeft: 23,
}}
>{league.acronym}</Text>
</View>
)
)}
Note: leagues have to be copied from props to state for you to mutate it. I just typed this so it will need some changes before it runs, it's just meant to show you the "react way" of coding this.
https://reactjs.org/docs/lifting-state-up.html

Using React-Hooks, how do I prevent a component created from Array.map from re-rendering if one of it's siblings changes state?

I've created a grid of components using array.map. Using console.log I'm able to see that each component is re-rendering whenever one of the components is changing state. When I have a grid of 50x50, this becomes noticeably slow.
import React, { useState } from 'react';
function Cell({ cell, cellState, updateBoard }) {
console.log('cell rendered')
const CellStyle = {
display: 'inline-block',
width: '10px',
height: '10px',
border: '1px green solid',
background: cellState ? 'green' : 'purple'
};
function handleClick(e) {
updateBoard(cell, !cellState)
}
return (
<span
style={CellStyle}
onClick={handleClick}
/>
)
}
function App() {
console.log('board rendered')
const initialState = new Array(10).fill().map(() => new Array(10).fill(false));
let [board, setBoard] = useState(initialState);
function updateBoard(cell, nextState) {
let tempBoard = [...board];
tempBoard[cell[0]][cell[1]] = nextState;
setBoard(tempBoard)
}
return (
<div style={{ display: 'inline-block' }}>
{board.map((v, i, a) => {
return (
<div
key={`Row${i}`}
style={{ height: '12px' }}
>
{v.map((w, j) =>
<Cell
key={`${i}-${j}`}
cell={[i, j]}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
)}
</div>
)
}
)}
</div>
)
}
export default App;
When I click on one of the components, I want the parent state to update, and the clicked component to update and re-render. Since the rest of the components aren't changed, I don't want the other components to re-render. How do I accomplish this using React-Hooks?
There is few things that would greatly improve performance:
using memo()
const MemoizedCell = memo(Cell);
/*...*/
<MemoizedCell
/*...*/
/>
not passing new references to <Cell /> every time
You are passing cell={[i, j]} - it creates new Array every time you invoke it(!) that means that props of Cells has been changed - why would it not render again then?
Same with passing updateBoard={updateBoard} - you are creating new function every time <App /> renders. You need to memorize it and use old state in function.
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
you are creating initialState every render - move it above (outside) <App /> or create it inside useState as function (and use const instead of let here).
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
final solution:
import React, { useState, memo, useCallback } from "react";
import ReactDOM from "react-dom";
function Cell({ i, j, cellState, updateBoard }) {
console.log(`cell ${i}, ${j} rendered`);
const CellStyle = {
display: "inline-block",
width: "10px",
height: "10px",
border: "1px green solid",
background: cellState ? "green" : "purple"
};
function handleClick(e) {
updateBoard([i, j], !cellState);
}
return <span style={CellStyle} onClick={handleClick} />;
}
const MemoizedCell = memo(Cell);
function App() {
console.log("board rendered");
const [board, setBoard] = useState(() =>
new Array(10).fill().map(() => new Array(10).fill(false))
);
const updateBoard = useCallback(
(cell, nextState) => {
setBoard(oldBoard => {
let tempBoard = [...oldBoard];
tempBoard[cell[0]][cell[1]] = nextState;
return tempBoard;
});
},
[setBoard]
);
return (
<div style={{ display: "inline-block" }}>
{board.map((v, i, a) => {
return (
<div key={`Row${i}`} style={{ height: "12px" }}>
{v.map((w, j) => (
<MemoizedCell
key={`${i}-${j}`}
i={i}
j={j}
cellState={board[i][j]}
updateBoard={updateBoard}
/>
))}
</div>
);
})}
</div>
);
}
export default App;
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Categories

Resources