I'm really confused on how to add (and delete) View and other such components during runtime,
for example in vanilla JavaScript you can use document.querySelector('query-here').appendChild(element);
but how do I achieve the same thing using react native? for example:
<Pressable onPress={()=>{addElement(element)}}>
<View>
//add elements here
</View>
I know how to achieve it directly like this:
<View>
{
[...Array(23)].map((el, index) => {
return(
<View key={index}>
<Text>added new element</Text>
</View>
)});
}
</View>
could someone please point me in the right direction?
#cakelover here how you can add item and remove items based on component's state.
import { Button } from 'react-native';
const [loader, setLoader] = React.useState(false); //donot show loader at initial
const showLoader = isShowLoader => { // based on this function you can add or remove single loader from UI
setLoader(isShowLoader);
}
return (
<View>
{loader && <LoaderComponent/>}
<Button
onPress={() => setLoader(!loader)}
title="Toggle Loader Component"
color="#841584"
/>
</View>
)
If you want to add or remove multiple same components like list you should use arrays of items for that.
I'm not sure but maybe you could try something like this
export default function App() {
const [num, setNum] = useState(() => 0);
const [renderTasks, setRenderTasks] = useState(()=>taskcreate(0));
function taskcreate()
{
let i=num;
setNum(i+1);
return(
<View>
{
[...Array(i)].map((el, index) => {
return (
<View key={index}>
<Text>hello there</Text>
</View>
)
})
}
</View>
)
}
return (
<View style={styles.container}>
<Pressable style={{ height: 50, width: 50, backgroundColor: 'orange' }} onPress={() => { setRenderTasks(taskcreate()) }}></Pressable>
{ renderTasks }
</View>
);
}
Related
Problem:
When using a ScrollView in ReactNative for horizontal pagination it re-renders all children, but I would like to keep the state values of certain local input fields and local variables of children components.
In the code below, if I were in the middle of updating a TextInput within the NotesSection but wanted to swipe back to the BatchSection to review some metadata, the code re-renders NotesSection and resets a local state holding the text value.
Diagnosis:
I'm very new to React and React Native, but my best guess here is that this happens due to the parent state variable "horizontalPos" which takes an integer to reflect what page is in focus.
This is simply used in the ProductHeader component to highlight a coloured bottomBorder showing the user a kind of small "menu" at the top of the screen.
The "horizontalPos" state can be updated in 2 ways:
First one is simply when clicking the wanted header (TouchableOpacity) within ProductHeader which triggers a state change and uses useRef to automatically move the ScrollView.
Second option is when the user swipes on the ScrollView. Using OnScroll to run a function "handleHorizontalScroll" which in turn sets the "horizontalPos" state using simple maths from the contentOffset.x.
Question / Solution:
If "horizontalPos" state was INSIDE ProductHeader I suspect this would solve the issue but I can't wrap my mind around how to do this as I don't believe it's possible to pass a function through to the child based on a change in the parent component.
I'm dependent on registering the OnScroll on the main ScrollView and the remaining components likewise have to be inside the main ScrollView but I don't want them to re-render every time the "horizontalPos" state updates.
Code:
const ProductScreen = (props) => {
const [horizontalPos, setHorizontalPos] = useState(0)
const scrollRef = useRef()
const toggleHorizontal = (page) => {
setHorizontalPos(page)
scrollRef.current.scrollTo({x:page*width, y:0, animated:false})
}
const handleHorizontalScroll = (v) => {
const pagination = Math.round(v.nativeEvent.contentOffset.x / width)
if (pagination != horizontalPos){
setHorizontalPos(pagination)
}
}
const ProductHeader = () => {
return(
<View style={styles.scrollHeaderContainer}>
<TouchableOpacity style={[styles.scrollHeader, horizontalPos == 0 ? {borderColor: AppGreenDark,} : null]} onPress={() => toggleHorizontal(0)}>
<Text style={styles.scrollHeaderText}>Meta Data</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.scrollHeader, horizontalPos == 1 ? {borderColor: AppGreenDark,} : null]} onPress={() => toggleHorizontal(1)}>
<Text style={styles.scrollHeaderText}>{"Notes"}</Text>
</TouchableOpacity>
</View>
)
}
return (
<View style={styles.container}>
<ProductHeader/>
<ScrollView
ref={scrollRef}
decelerationRate={'fast'}
horizontal={true}
showsHorizontalScrollIndicator={false}
snapToInterval={width}
onScroll={handleHorizontalScroll}
scrollEventThrottle={16}
disableIntervalMomentum={true}
style={{flex: 1}}
>
<View style={[styles.horizontalScroll]}>
<View style={styles.mainScrollView}>
<BatchSection/>
</View>
<ScrollView style={styles.notesScrollView}>
<NotesSection/>
</ScrollView>
</View>
</ScrollView>
</View>
)
}
As you outlined, updating horizontalPos state inside ProductScreen will cause a whole screen to re-render which is not an expected behavior.
To avoid this scenario, let's refactor the code as below:
function debounce(func, timeout = 500){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
class ProductHeader extends React.Component {
state = {horizontalPos:0 }
toggleHorizontal = (page) => {
this.setState({horizontalPos:page});
this.props.onPositionChange(page);
};
render () {
const {horizontalPos} = this.state
return (
<View style={styles.scrollHeaderContainer}>
<TouchableOpacity
style={[
styles.scrollHeader,
horizontalPos == 0 ? { borderColor: AppGreenDark } : null,
]}
onPress={() => this.toggleHorizontal(0)}
>
<Text style={styles.scrollHeaderText}>Meta Data</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.scrollHeader,
horizontalPos == 1 ? { borderColor: AppGreenDark } : null,
]}
onPress={() => this.toggleHorizontal(1)}
>
<Text style={styles.scrollHeaderText}>{"Notes"}</Text>
</TouchableOpacity>
</View>
);
}
};
const ProductScreen = (props) => {
const scrollRef = useRef();
const productHeaderRef = useRef()
let horizontalPos = 0;
const handleHorizontalScroll = (v) => {
const pagination = Math.round(v.nativeEvent.contentOffset.x / width);
if (pagination != horizontalPos) {
productHeaderRef.current?.toggleHorizontal(pagination)
}
};
const debouncedHorizontalScroll= debounce(handleHorizontalScroll,500)
const onPositionChange = (page) => {
horizontalPos = page;
scrollRef.current.scrollTo({ x: page * width, y: 0, animated: false });
};
return (
<View style={styles.container}>
<ProductHeader onPositionChange={onPositionChange} ref={productHeaderRef} />
<ScrollView
ref={scrollRef}
decelerationRate={"fast"}
horizontal={true}
showsHorizontalScrollIndicator={false}
snapToInterval={width}
onScroll={debouncedHorizontalScroll}
scrollEventThrottle={16}
disableIntervalMomentum={true}
style={{ flex: 1 }}
>
<View style={[styles.horizontalScroll]}>
<View style={styles.mainScrollView}>
<BatchSection />
</View>
<ScrollView style={styles.notesScrollView}>
<NotesSection />
</ScrollView>
</View>
</ScrollView>
</View>
);
};
I hope this will stop the whole screen from rerendering and maintaining pagination.
I'm having a trouble in a flatlist that a button increments the value but doesn't show if I don't refresh or hit ctrl-s after incrementing value. Basically I cannot see the value change without pressing ctrl-s or going to another page and returning to the same page.
<FlatList
data={store}
renderItem={({ item }) => {
return (
<View style={styles.itemCountView}>
<TouchableOpacity style={styles.up}
onPress={() => item.itemCount++}>
<MaterialIcons name="arrow-drop-up" size={36} color="#ddd"/>
</TouchableOpacity>
<Text style={styles.itemCountText}>{item.itemCount}</Text>
</View>
)
}}
/>
I can increment the value, if I save or go to another page and come back the value changes and i can see it but there must be a way to see it change.
Any help is appreciated
As per the Documentation
extraData: https://reactnative.dev/docs/flatlist#extradata
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
I think you'r not saving the changes you doing, basically you need to update the the store variable each time you increment an item, something like:
<FlatList
data={store}
renderItem={({ item }) => {
return (
<View style={styles.itemCountView}>
<TouchableOpacity style={styles.up}
onPress={() => {
const newStoreData = [...store]
const itemIndex = newStoreData.findIndex(item)
const newItem = {...item,itemCount:item.itemCount ++}
newStoreData[itemIndex] = newItem
setStoreData(newStoreData) // state or redux?
}
}>
<MaterialIcons name="arrow-drop-up" size={36} color="#ddd"/>
</TouchableOpacity>
<Text style={styles.itemCountText}>{item.itemCount}</Text>
</View>
)
}}
/>
You can try this approach here:
import {useState} from 'react';
import { Text, View, FlatList, TouchableOpacity } from 'react-native';
const store = [{
itemCount: 1
},
{
itemCount: 2
},
{
itemCount: 3
}]
export default function App() {
return (
<View style={{
flex: 1,
padding: 140,
}}>
<FlatList
data={store}
renderItem={({ item }) => ( <Item item={item}/> )}
/>
</View>
);
}
const Item = ({item})=> {
const[count, setCount] = useState(item.itemCount);
return (
<View>
<TouchableOpacity
style={{
backgroundColor: 'black',
padding: 10,
margin: 10
}}
onPress={() => setCount(count+1)}>
<Text style={{
color: 'white'
}}>{count}</Text>
</TouchableOpacity>
</View>
)
}
This will set the state for each item in the Flatlist.
How can I add a text input in React Native with the click of a button? For example, I would press the "+" button and it would add a text input at the bottom of the View.
here is the code i found in another page but when i click on the plus nothing happen
var textInput = []
let addTextInput = (key) => {
textInput.push(<TextInput key={key} style={{color:'red',borderColor:'red'}}/>);
{ textInput }
}
let orgServiceandWorkHours = (
<View>
<Button title='+' onPress={() =>
addTextInput(textInput.length)} />
{textInput.map((value, index) => {
return value
})}
</View>
)
Heres a full example (https://snack.expo.dev/#heytony01/mature-carrot) and the code below.
export default function App() {
const [numTextInputs,setNumTextInputs] = React.useState(0);
return (
<View style={styles.parent}>
{/* Button */}
<TouchableOpacity onPress={()=>setNumTextInputs(val=>val+1)} style={styles.buttton}>
<Text style={styles.text}> Add TextInput </Text>
</TouchableOpacity>
<ScrollView style={{flex:1}} >
{[...Array(numTextInputs).keys()].map(key=>{
return <TextInput key={key} placeholder="Here Man" style={{width:200,height:100,borderColor:"blue",margin:10,borderWidth:1}}/>
})}
</ScrollView>
</View>
);
}
const styles = StyleSheet.create({
parent:{flex:1,justifyContent:"center",alignItems:"center"},
textInput:{width:"80%",height:"5%",backgroundColor:"lightgray",margin:20,borderRadius:10,textAlign:"center"},
buttton:{width:"80%",height:"12%",backgroundColor:"lightblue",borderRadius:10,
justifyContent:"center",alignItems:"center"
},
text:{fontSize:30,fontFamily:"bold",color:"black"},
})
I'm having problems with passing props and this whole stuff. Apparently i did something wrong maybe someone can tell me what should i fix there.
So these are states and callbacks that i use:
const [isVisible, setIsVisible] = useState(false);
const visibleCallBack = () => {
setIsVisible(false);
};
const [budynekText, setBudynekText] = useState("budynek");
const budynekCallBack = (propFromChild) => {
setBudynekText(propFromChild);
};
isVisible should decide whether to render or not a certain component:
<TouchableOpacity onPress={() => setIsVisible(true)}>
<View style={styles.budynekContainer}>
<Text style={styles.budynekTekst}>{budynekText}</Text>
{isVisible ?
<Rozwijana callBack={visibleCallBack} budCallBack={budynekCallBack}/>
: null}
</View>
Here I'm passing these callback functions as a props to "Rozwijana",
"Rozwijana" looks like that:
export const Rozwijana = ({callBackProp, budCallBackProp}) => {
return (
<View style={styles.rozwijanaPoz}>
<Pusty/>
<Box name={"budynek 1"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
<Box name={"budynek 2"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
<Box name={"budynek 3"} callBackProp={callBackProp} budCallBackProp={budCallBackProp}/>
</View>
);
}
Then i pass these callbacks down to "Box" component:
const Box = ({name, callBackProp, budCallBackProp}) => {
return (
<>
<TouchableHighlight onPress={() => {callBackProp(); budCallBackProp(name);}}>
<View style={styles.inputBox}>
<Text style={styles.testText}>{name}</Text>
</View>
</TouchableHighlight>
</>
);
}
The result is, that when i click first time at "budynek" button it works as expected, it opens my "list" but after that, when i try to click any of "Boxes" nothing happens. Is there something wrong? If so, how do i go about making this work? Thanks
I am trying to change the style of individual TouchableOpacity components that have been returned from a map function.
Here is the component:
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<React.Fragment key={id}>
<TouchableOpacity
style={styles.button}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
Let TouchableOpacity = TO.
The map function returns about 30 TOs with unique IDs. When I click the TOs, I can see their unique ID in the console log. I want to know how I can modify the style of an individual TO.
Here is my render function which uses the functional component Example.
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList() }} />
</View>
</ScrollView>
</View>
);
}
What I have tried:
referencing this stackoverflow post, I tried to create a function which changed the style of the TO when it is clicked. But the result of this changed all the TOs in the UI since of the way it is mapped.
I tried something like the following.
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
let buttonStyle = this.state.pressed ? styles.button : styles.buttonClicked
return (
<React.Fragment key={id}>
<TouchableOpacity
style={buttonStyle}
onPress={() => console.log(id)}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
</React.Fragment>
);
})}
</View>
);
};
But as previously stated, this changed all of the Touchable Opacitys. Is there a way to only change one?
Thanks
Edit - to show entire class
class Page extends Component {
constructor(props) {
super(props)
}
MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
);
}
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id}/>
);
})}
</View>
);
};
render() {
return (
<View style={styles.body}>
<ScrollView>
<View style={styles.column}>
<this.Example props={{ listExample: this.getList()}} />
</View>
</ScrollView>
</View>
);
}
}
It is easier to separate the component inside map to a separate component and then handle style changes on press there
const MyButton = ({ onButtonPressed = () => {} }) => {
const [isPressed, setIsPressed] = useState(false);
const onPressed = () => {
setIsPressed(!isPressed);
onButtonPressed();
}
return (<TouchableOpacity style={isPressed ? styles.pressedButton: styles.button}
onPress={onPressed}>
<Image source={require('example.jpg')} />
</TouchableOpacity>
)
}
so you can use in the map like this
Example = ({ props }) => {
return (
<View>
{props.listExample.map(({ id }) => {
return (
<MyButton key={id} />
);
})}
</View>
);
};