react native conditional rendering with section list and flatlist - react-native

I have two different components one is CategoryList which is a flatlist and regionlist is a section list. I would like to display the CategoryList first and when item clicked the regionlist will show however I m not sure why it is not working. (I use context to store the state)
{!isToggle ? (
<CategoryList></CategoryList>
) : (
<RegionList style={styles.regionListStyle}></RegionList>
)}
I also create a button to see if it is a problem about the context but it is not.
const ToggleContext = createContext(true);
export const useToggle = () => {
return useContext(ToggleContext);
};
export function ToggleProvideData({children}) {
const [isToggle, setToggle] = useState(true)
return <ToggleContext.Provider value={{isToggle,setToggle}}>
{children}
</ToggleContext.Provider>;
}
I just wonder conditional render is it not working for flatlist?
UPDATE: I tried create a state to store the useContext isToggle but it only appears for like 1 sec

I guess isToggle as a boolean variable, hence you can use the below code for rendering conditionally
{ (isToggle === true) &&
<CategoryList></CategoryList>
}
{ (isToggle === false) &&
<RegionList style={styles.regionListStyle}></RegionList>
}

Related

Display contentful <ForwardRef /> converted elements to react native

I have the data from contentful, the data is formatted like this and is converted using #contentful/rich-text-react-renderer with this conversion options. By calling the codes below to perform the conversion
import { documentToReactComponents } from '#contentful/rich-text-react-renderer';
const convert = (document: any) => {
const nodes = documentToReactComponents(document?.fields?.html, contentfulToReactnative);
if (nodes && Array.isArray(nodes)) {
const newNodes = nodes.map((component, index) => {
if (index + 1 === nodes.length) {
return component;
}
return <View key={uuid()}>{component}</View>;
});
return newNodes;
}
return nodes;
};
with that, I got arrays of <ForwardRef /> like this
[<ForwardRef />, <ForwardRef(Text) allowFontScaling={false}></ForwardRef(Text)>]
the question is, how should I display ForwardRefs in react native. By doing {elements} (elements is the variable holding that array) won't work.
I don't know yet if these ForwardRefs are the things to display.

Update state more efficiently in React native?

I'm building a checklist app with multiple tabs. It works but when the list grows larger, it's not performing very snappy when I want to check 1 item for instance. I have the feeling this is because the entire state (consisting of all items in all tabs) is updated, when I just want to update 1 item. The tabs and items are generated dynamically (ie, at compile-time I don't know how many tabs there will be). Any idea how this could be done more efficiently?
This is the (stripped down) state provider:
export default class InpakStateProvider extends React.Component {
state = {projectName: " ", tabs: [{name: " ", items: [{checked: false, name: " "}]}]};
DeleteItem = (categoryname: string, itemname: string) => {
let stateTabs = this.state.tabs;
var tab = stateTabs.find((tab) => tab.name == categoryname);
if(tab){
let index = tab.items.findIndex(el => el.name === itemname);
tab.items.splice(index, 1);
}
this.setState({projectName: this.state.projectName, tabs: stateTabs})
};
CheckItem = (categoryname: string, itemname: string) => {
var tab = this.state.tabs.find((tab) => tab.name == categoryname);
if(tab){
let index = tab.items.findIndex(el => el.name === itemname);
tab.items[index] = { ...tab.items[index], checked: !tab.items[index].checked };
}
this.setState({projectName: this.state.projectName, tabs: this.state.tabs});
};
ClearChecks = () => {
let stateTabs = this.state.tabs;
stateTabs.forEach((tab) => {
let tabItems = [...tab.items];
tabItems.forEach((item) => item.checked = false);
});
this.setState({projectName: this.state.projectName, tabs: stateTabs})
}
render(){
return (
<Context.Provider
value={{
projectName: this.state.projectName,
tabs: this.state.tabs,
DeleteItem: this.DeleteItem,
CheckItem: this.CheckItem,
ClearChecks: this.ClearChecks,
}}
>
{this.props.children}
</Context.Provider>
);
}
}
The issue here is that all list components are being re-rendered upon updating the state. My advice is to move the state of checked inside of the list item component. Or if you don't want to do that, I advise you to read about React memoization.
If you go for the memoziation approach if you update the state, and the props of the list item didn't change, this will not re-render the unchanged components, it will only trigger the re-render for the components with the prop checked that has changed.
Here's the documentation for memoization if it helps: https://reactjs.org/docs/react-api.html.
Also, on another note, always go for FlatLists instead of using map. You won't notice a big difference with a small dataset, but performance takes a big hit with mid-large datasets.

React Native State Management Question - when does useState hook load?

I have a FlatList of items that has a "remove" button next to it.
When I click the remove button, I am able to remove the item from the backend BUT the actual list item is not removed from the view.
I am using useState hooks and it was to my understanding that the component re-renders after setState happens.
The setState function is used to update the state. It accepts a new
state value and enqueues a re-render of the component.
https://reactjs.org/docs/hooks-reference.html
What am I missing with how state is set and rendering?
I don't want to use the useEffect listener for various reasons. I want the component to re-render when the locations state is updated....which I am pretty sure is happening with my other setStates....not sure if I am totally missing the mark on what setState has been doing or if it's something specific about setLocations().
const [locations, setLocations] = useState(state.infoData.locations);
const [locationsNames, setLocationsNames] = useState(state.infoData.names]);
...
const removeLocationItemFromList = (item) => {
var newLocationsArray = locations;
var newLocationNameArray = locationsNames;
for(l in locations){
if(locations[l].name == item){
newLocationsArray.splice(l, 1);
newLocationNameArray.splice(l, 1);
} else {
console.log('false');
}
}
setLocationsNames(newLocationNameArray);
setLocations(newLocationsArray);
};
...
<FlatList style={{borderColor: 'black', fontSize: 16}}
data={locationNames}
renderItem={({ item }) =>
<LocationItem
onRemove={() => removeLocationItemFromList(item)}
title={item}/> }
keyExtractor={item => item}/>
UPDATED LOOP
const removeLocationItemFromList = (item) => {
var spliceNewLocationArray =locations;
var spliceNewLocationNameArray = locationsNames;
for(f in spliceNewLocationArray){
if(spliceNewLocationArray[f].name == item){
spliceNewLocationArray.splice(f, 1);
} else {
console.log('false');
}
}
for(f in spliceNewLocationNameArray){
if(spliceNewLocationNameArray[f] == item){
spliceNewLocationNameArray.splice(f, 1);
} else {
console.log('false');
}
}
var thirdTimesACharmName = spliceNewLocationNameArray;
var thirdTimesACharmLoc = spliceNewLocationArray;
console.log('thirdTimesACharmName:: ' + thirdTimesACharmName + ', thirdTimesACharmLoc::: ' + JSON.stringify(thirdTimesACharmLoc)); // I can see from this log that the data is correct
setLocationsNames(thirdTimesACharmName);
setLocations(thirdTimesACharmLoc);
};
This comes down to mutating the same locations array and calling setState with the same array again, which means that the FlatList which is a pure component will not re-render since the identity of locations has not changed. You could copy the locations array to newLocationsArray first (similarly with the newLocationNameArray) to avoid this.
var newLocationsArray = locations.slice();
var newLocationNameArray = locationsNames.slice();

Can I use multiple or nested elements in the Label of a Picker Item in React Native?

I'm using React Native with NativeBase and would like to make the labels of my Picker more complicated than just one plain string of text.
But is it even possible to pass elements as the label, say multiple child elements wrapped in a single top-level element?
Or do Pickers only support plain text as labels?
As requested by bennygenel, here's a version of what I've tried:
export default class ThingPicker extends React.Component {
render() {
const {
initialThing,
things,
onThingChanged,
} = this.props;
const orderedThings = things.sort();
return (
<Picker
selectedValue={initialThing}
onValueChange={onThingChanged}>
{buildThingItems(orderedThings)}
</Picker>
);
}
}
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = th === "BMD" ? (<Text key={th} label={"foo"} value={"bar"}}>Hello</Text>)
: (<Picker.Item key={th} label={th} value={th} />);
return it;
});
}
Yes! It is possible, it just might not look very "right" for React/JSX code. Just create the elements you need and assign them to the label field:
function buildThingItems(orderedThings) {
let items = orderedThings.map(th => {
const it = (<Picker.Item
key={th}
label={currency === "BMD" ? (<Text>Hello</Text>) : th}
value={th} />);
return it;
});
}

How to programmatically switch a switch in React Native?

I make us several Switch Components in one view. When I switch on one switch, I want all others to switch off. Currently, I set the boolean value property via the state. This results in changes happen abruptly because the switch is just re-rendered and not transitioned.
So how would you switch them programmatically?
EDIT 2: I just discovered that it runs smoothly on Android so it looks like an iOS-specific problem.
EDIT: part of the code
_onSwitch = (id, switched) => {
let newFilter = { status: null };
if (!switched) {
newFilter = { status: id };
}
this.props.changeFilter(newFilter); // calls the action creator
};
_renderItem = ({ item }) => {
const switched = this.props.currentFilter === item.id; // the state mapped to a prop
return (
<ListItem
switchButton
switched={switched}
onSwitch={() => this._onSwitch(item.id, switched)}
/>
);
};