I'm building a react native app where a post has comments. I only want to
show the comments when the user clicks on load comments.... The problem
is how do I handle the state for each post (there are multiple posts). I tried
this but it's not working (renderPost is a loop):
const renderPost = ({ item, index}) => {
let fetchComments = false;
return (
<View style={[t.mB6]}>
<View style={[t.roundedLg, t.overflowHidden, t.shadow, t.bgWhite, t.hAuto]}>
<TouchableOpacity
key={item.id}
onPress={() => {
fetchComments = true;
}}>
<Text style={[t.fontBold, t.textBlack, t.mT2, t.mL4, t.w1_2]}>
load comments...
</Text>
</TouchableOpacity>
</View>
{ fetchComments ? <Comments postId={item.id}/> : null }
</View>
)
}
In the code above I set let fetchComments to true when the user clicks on load comments....
renderPost is a functional component that doesn't have its own render and its own state, you may resolve this passing a function that changes state through renderPost props in its Father React.Component.
Example:
//imports
class FatherComponentWithState extends React.component{
state={
fetchComments:false,
//(...OTHERSTUFFS)
}
setFetchComments = () =>{
this.setState({fetchComments:true})
}
render(){
return(
//(...FatherComponentStuffs)
{new renderPost({
setFetchComments: this.setFetchComments,
fetchComments:this.state.fetchComments,
//(...renderPostOtherStuffs like item, index)
})}
//(...FatherComponentStuffs)
)}}
The renderPost function will receive it with something like this:
const renderPost = (props) =>{
let fetchComments = props.fetchComments;
let setFetchComments = props.setFetchComments;
let item = props.item
let index = props.index
//...renderPost return remains the same
}
P.S.: If you have multiple renderPosts, you can use fetchComments as an array of booleans and set the state to true passing an index as parameter of the setFetchComments function.
Related
I wanna render between displaying a start and stop button based on pressing the buttons. That means if the start button gets pressed the stop button should be showed and vice versa. In the standard case the stop button should be rendered. With the following code I get an error because status can be read only so I need some other way to set the status.
const SampleComponent = () => {
const startButton = () => {
return <Component1 />;
};
const stopButton = () => {
return <Component2 />;
};
const status = stopButton;
return (
<View>
<Pressable
onPress={() => {
if (status == stopButton) {
status = startButton;
} else {
status = stopButton;
}
}}
>
{status}
</Pressable>
</View>
);
};
export default SampleComponent;
I need a React Native way to do this. I tried to study the docs and also other material but I just don't get it.
React way of doing this would be to use state hook.
You could use a boolean state variable and toggle it on button click.
Solution
const SampleComponent = () => {
const StartButton = () => {
return <Component1 />;
};
const StopButton = () => {
return <Component2 />;
};
const [status, setStatus] = useState(false);
return (
<View>
<Pressable
onPress={() => {
setStatus(!status);
}}
>
{status ? <StopButton /> : <StartButton />}
</Pressable>
</View>
);
};
export default SampleComponent;
Remember to give components uppercase letter.
const [number,setNum] = useState(0); I get this error when I want to add and change it(setNum(number+1)). My Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. What can i to solve this?
const App = ()=>{
const [text,setText] = useState('');
const [todo,setToDo] = useState([]);
const [number,setNum] = useState(0);
const renderToDoCard = ({item})=>{
setNum(number+1)
return(
<TouchableHighlight
onLongPress={() => handleLongPress(item)}>
<ToDoCard todo={item} number={number}/>
</TouchableHighlight>
)
}
const handleLongPress = item => {
setToDo(todo.filter(i => i !== item));
return Alert.alert('Silindi');
};
return(
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor='#102027'/>
<View style={styles.head_container}>
<Text style={styles.title}>Yapılacaklar</Text>
<Text style={styles.title}>{todo.length}</Text>
</View>
<View style={styles.body_container}>
<FlatList data={todo} renderItem={renderToDoCard} />
</View>
<View style={styles.bottom_container}>
<ToDoInput todo={todo} setToDo={setToDo} text={text} setText={setText}/>
</View>
</SafeAreaView>
)
}
You've created an infinite update loop.
The problem is in how you're updating your number state inside renderToDoCard
const renderToDoCard = ({item}) => {
setNum(number + 1); // This is the problem, remove this line
return (
<TouchableHighlight onLongPress={() => handleLongPress(item)}>
<ToDoCard todo={item} number={number} />
</TouchableHighlight>
);
};
When renderToDoCard renders you update the state of your App component so it rerenders App which renders renderToDoCard which updates the state of your App component so it rerenders App which renders renderToDoCard...
This process repeats until the max update depth is reached.
Simply remove setNum(number + 1); and that problem is fixed.
It seems to me from your code that all you use your number state for is to keep track of the current item index so you can pass this to the ToDoCard component. The FlatList's renderItem also provides access to the current item index which you could pass to the number prop of ToDoCard
renderItem({ item, index, separators });
https://reactnative.dev/docs/flatlist#required-renderitem
So you could instead do something like this
const renderToDoCard = ({item, index}) => {
return (
<TouchableHighlight onLongPress={() => handleLongPress(item)}>
<ToDoCard todo={item} number={index} />
</TouchableHighlight>
);
};
Alternative you can add a key to each item in todo and use that instead of the index.
I am trying to create a React Native e-commerce app where the featured products are shown, but then the user can view a list of categories via a sheet popping up from the bottom, which will load the products from said category.
I have managed to create such a bottom sheet using react-native-btr's BottomSheet. However, the function to show/hide the component (simply toggling a boolean in state) needs to be available to the component itself (to handle the back button and backdrop presses).
This is the component code:
const TestNav = (props, ref) => {
const [visible, setVisible] = useState(false);
const toggleVisible = () => {
setVisible(!visible);
};
useImperativeHandle(ref, () => toggleVisible());
return (
<BottomSheet
visible={visible}
//setting the visibility state of the bottom shee
onBackButtonPress={toggleVisible}
//Toggling the visibility state on the click of the back botton
onBackdropPress={toggleVisible}
//Toggling the visibility state on the clicking out side of the sheet
>
<View style={styles.bottomNavigationView}>
<View
style={{
flex: 1,
flexDirection: 'column',
}}
>
{DummyData.map((item) => {
return (
<Button
key={item.id}
title={item.name}
type="clear"
buttonStyle={styles.button}
onPress={() => console.log(item.name)}
/>
);
})}
</View>
</View>
</BottomSheet>
);
};
export default React.forwardRef(TestNav);
And here is the code for the screen where it's being used (it's called ChatScreen as I'm using it as a testing ground since I haven't implemented that feature yet)
import React, { useRef } from 'react';
import {SafeAreaView,StyleSheet,View,Text} from 'react-native';
import TestNav from '../components/TestNav';
import { Button } from 'react-native-elements';
const ChatScreen = () => {
const childRef = useRef(null);
const toggleBottomNavigationView = () => {
if (myRef.current) {
childRef.current.toggleVisible;
}
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<Text
style={{
fontSize: 20,
marginBottom: 20,
textAlign: 'center',
}}
>
Content goes here
</Text>
<Button
onPress={() => toggleBottomNavigationView()}
title="Show Bottom Sheet"
/>
<TestNav ref={childRef} />
</View>
</SafeAreaView>
);
};
export default ChatScreen;
However, this code has somehow triggered an infinite loop, as I get this message:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
How do I go about fixing this?
I think the issue lies with how you define the imperative handle. Hook callbacks are called each time the component renders and so () => toggleVisible() is called each render and creates a render loop. It should be passed a callback that returns the imperative functions/values to be made available to callees.
const toggleVisible = () => {
setVisible(visible => !visible);
};
useImperativeHandle(ref, () => ({
toggleVisible,
}));
In ChatScreen you then need to invoke the function. I'll assume the myRef in your snippet was a typo since it's not declared in the component and the usage appears to be similar to the guard pattern.
const toggleBottomNavigationView = () => {
childRef.current && childRef.current.toggleVisible();
// or childRef.current?.toggleVisible();
};
This is my function for rendering items in a flatlist
renderItem = ({ item }) => {
var integer = Number(item.key)
return (
<View>
<Text style={styles.row}>
{item.text}
</Text>
<View style={{flexDirection:'row'}}>
{this.createButtonYes(integer)}
{this.createButtonNo(integer)}
{this.answer(this.state.buttonStates[integer])}
</View>
<Text >__</Text>
</View>
)
}
And the problem I am facing is the function this.answer is not being called when the state of buttonStates changes
answer = (val) => {
if(val){
return(
<Text style={{fontSize:20}}>YES</Text>
)
}else if(val==false){
return(
<Text style={{fontSize:20}}>NO</Text>
)
}else{
return(
<Text style={{fontSize:20}}>Not Answered</Text>
)
}
}
I assumed that every time the state changes the function would be called but that does not seem to be the case, so does anyone have a solution? What I want is whenever the buttons are pressed the state will change and then this.answer will take the changed state and display what it has to accordingly.
Thanks
EDIT:
Code for the button:
buttonYesHelp = num =>{
const newItems = [...this.state.buttonStates];
newItems[num] = true;
return newItems
}
createButtonYes = (num) => {
return(
<TouchableOpacity style={styles.buttonYes}
onPress =
{
()=> {{this.setState({ buttonStates:this.buttonYesHelp(num) })}}
}>
<Text style={styles.buttonTextStyle}>YES</Text>
</TouchableOpacity>
)
}
num is the index of the thing I want to change in the list
EDIT:
I have tried multiple different things but the problem I keep running into is that when I render the button I want it to react to a state variable but it never seems to change based on the state even when the state is changing.
For example, in this.answer I assumed that it would return the text based on the state of buttonStates but it seems to only account for the initial state and nothing after
I was able to achieve this in a different piece of code with identical syntax but for some reason this is not working
I have scroll view in my HomeScreen with my measures that is array of Measure component.
<ScrollView>{measures}</ScrollView>
My Measure component looks like this:
class Measure extends Component {
render() {
return (
<View key={this.props.keyval}>
<TouchableOpacity onPress={deleteItem(this.props.val.measure_id)}>
<Text style={styles.measure}>
ID: {this.props.val.measure_id}
</Text>
</TouchableOpacity>
</View>
);
}
deleteItem(id) {
// delete item
);
}
}
My question is, how to notify parent component HomeScreen that Measure was deleted to reload scroll view items? Or Maybe you have better idea how to:
display measures
delete one in onPress item
reload items in scroll view
Thans for any advices
In your case it should be something like this:
class HomeScreen extends Component {
state = {
measures: []
};
handleDelete = (id) => {
// item was deleted
}
render() {
const { measures } = this.state;
const measuresList = measures.map(measure => (
<Measure
key={measure.measure_id}
onDelete={this.handleDelete}
val={measure}
/>
));
return (
<ScrollView>{measuresList}</ScrollView>
);
}
}
class Measure extends Component {
render() {
return (
<View key={this.props.keyval}>
<TouchableOpacity onPress={deleteItem(this.props.val.measure_id)}>
<Text style={styles.measure}>
ID: {this.props.val.measure_id}
</Text>
</TouchableOpacity>
</View>
);
}
deleteItem(id) {
const { onDelete } = this.props;
// delete item
onDelete(id); //will call parent method.
}
}
I recommend using FlatList as it render only those items which are visible. Much better in terms of performance, especially in big lists taken from API.
Example:
<FlatList
data={measures}
keyExtractor={item => item.id}
renderItem={({ item }) => <Measure
id={item.id}
anyOtherNeededPropsKey={anyOtherNeededPropsValue}
/>}
/>
Pass an additional property onDelete to the Measure and call it in deleteItem method