How to prevent dispatch action to re-render screen - redux? - javascript

I use redux to dispatch an action it's a trigger 'true/false',
So i have a bottom sheet modal contain 'mini-player' when i open it grow height modal to contain the player controller.
in the player controller when i press to a 'play/pause icon' i dispatch an action to set pause from false to true or opposite
but the problem is when action dispatching it's back modal to the bassed height "mini-player"
Gif
So how can i solve it?
in other words, prevent re-render the modal?
Code
Player controller
<View>
<View style={{paddingHorizontal: 10}}>
<Video
....
paused={this.props.isPause} // redux store
....
/>
</View>
{!this.props.isPause ? (
<Button
onPress={() => this.props.isPauseTrigger(true)}> // redux action
<Icon name="md-pause" style={styles.iconColor} />
</Button>
) : (
<Button
onPress={() => this.props.isPauseTrigger(false)}> // redux action
<Icon name="md-play" style={styles.iconColor} />
</Button>
)}
</View>
Mini-Player Component I used 'react-native-reanimated-bottom-sheet'
const renderHeader = () => {
const animatedBackgroundOpacity = Animated.sub(
1,
animatedHeaderContentOpacity,
);
return (
<View onPress={onHeaderPress} key={'header-container'}>
<Animated.View
intensity={100}
tint={'default'}
style={[
styles.headerContentContainer,
{
opacity: animatedHeaderContentOpacity,
},
]}>
<View>
....
{props.isPause ? ( // Redux State
<TouchableOpacity
onPress={() => {
props.isPauseTrigger(false); // Redux action
console.log('should play?', props.isPause);
}}
style={styles.headerActionButton}>
<Ionicons color="#282A36" name="ios-play" size={30} />
</TouchableOpacity>
) : (
<TouchableOpacity
onPress={() => {
props.isPauseTrigger(true); // Redux action
console.log('should pause?', props.isPause);
}}
style={styles.headerActionButton}>
<Ionicons color="#282A36" name="ios-pause" size={30} />
</TouchableOpacity>
)}
</View>
</Animated.View>
</View>
);
};
const renderContent = () => {
return (
<Player />
);
};
What I tried
When i change paused property in <Video /> to use local state 'inside controller player component', It works well without any issue like in GIF But I want to change the icon when i minimize the bottom player.
so it's not listening to the redux state because I use local state

React Re-renders components based on changes in props or states. You can use componentShouldUpdate lifecycle method to prevent default behavior.
Based on how you handle mini-player open/closed state there could be other solutions as well.

What is your parent component?
How are you controling the open/close of the modal and if it works with state and all you need is the butto n icon play/pause you can lift the state, i.e you dont need to store in redux.
So on parent:
State: player: "playing"
Func updatePlayer(mode) this.setState(player:mode)
And in your modal comp wherever you are done with it and have a func to closeModal() just have this.props.updatePlayer(this.state.playerState)
Which will update state on the parent.

Related

Modal not updating to new item in array,firebase/react native state

my current issue with my react native app is that when a user wants to open a lesson (from the lessons array with each object being a lesson with a title,description,img url etc)to make it bigger through a modal, its state does not update. What i Mean by this is that the books title,description,and other attributes won't change if you press on a new lesson. What would be the solution to this?
export default function Learn() {
const [modalVisible, setModalVisible] = useState(false);
const [lessons,setLessons] = useState()
useEffect(() => {
async function data() {
try {
let todos = []
const querySnapshot = await getDocs(collection(db, "lessons"));
querySnapshot.forEach((doc) => {
todos.push(doc.data())
});
setLessons(todos)
console.log(lessons)
}
catch(E) {
alert(E)
}
}
data()
}, [])
return (
<View style={learnStyle.maincont}>
<View>
<Text style={{fontSize:28,marginTop:20}}>Courses</Text>
<ScrollView style={{paddingBottom:200}}>
{lessons && lessons.map((doc,key) =>
<>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
setModalVisible(!modalVisible);
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Image source={{
uri:doc.imgURL
}} style={{width:"100%",height:300}}/>
<Text style={{fontWeight:"700",fontSize:25}}>{doc.title}</Text>
<Text style={{fontWeight:"700",fontSize:16}}>{doc.desc}</Text>
<Pressable
style={[styles.button, styles.buttonClose]}
onPress={() => setModalVisible(!modalVisible)}
>
<Text style={styles.textStyle}>Hide Modal</Text>
</Pressable>
</View>
</View>
</Modal>
<LessonCard setModalVisible={setModalVisible} title={doc.title} desc={doc.desc} img1={doc.imgURL} modalVisible={modalVisible}/>
</>
)}
<View style={{height:600,width:"100%"}}></View>
</ScrollView>
</View>
</View>
)
}
What it looks like:
**image 1 is before you press the modal and the 2nd one is after
**the main issue though is that if you press cancel and press on another lesson the modal that opens has the the same state(title,imgurl,anddesc) as the first lesson and does not change.
The problem is that you create a lot of modal windows through the map function, I suggest making one window and passing the key as a parameter and using it to search for a specific array of data that is shown to the user (photo, title, etc.)
The problem is that all 3 Modals are controlled by the one state variable. So when the code sets modalVisible to true, all 3 modals are being opened at once.
You can fix this in a few ways, but a simple way would be to move the Modal and its state into the LessonCard component. This way each modal will have its own state that's only opened by its card. So the loop in Learn will just be:
{lessons && lessons.map((doc,key) => (
<LessonCard lesson={doc} key={key} />
)}
Adding to address question in comments
LessonCard should not accept setModalVisible or modalVisible props. The
const [modalVisible, setModalVisible] = useState(false);
should be inside LessonCard, not Learn. That way each Card/Modal pair will have its own state.
Additionally, although React wants you to pass the key into LessonCard in the map function, LessonCard should not actually use the key prop for anything. See https://reactjs.org/docs/lists-and-keys.html#extracting-components-with-keys
So, the LessonCard declaration should just be something like
export default function LessonCard({lesson}) {

Pass function from one screen to another in react native

Im trying to pass a function handleNewFavourite (which updates my favouriteList state array) from my HomeScreen to my DetailsScreen via navigation params but Im getting the following error: Non-serializable values were found in the navigation state
How should I pass functions that modified the state between different stack screens?
HomeScreen code:
<FlatList
data={checkCategory()}
renderItem={({item}) => (
<TouchableOpacity
onPress={() =>
navigation.navigate('Details', {
item,
handleNewFavourite,
})
}>
<LessonCard lesson={item} />
</TouchableOpacity>
)}
/>
DetailScreen code:
const LessonDetails = ({lesson, handleNewFavourite}: LessonProps) => {
const [favourite, setFavourite] = useState<boolean>(lesson.favourite);
return (
<LessonDetailsContainer>
<LessonDetailsInfoContainer>
<LessonDetailsCategoryHead>
<LessonDetailsCategory>{lesson.category}</LessonDetailsCategory>
<TouchableOpacity
onPress={() => {
setFavourite(!favourite);
handleNewFavourite(lesson);
}}>
<LessonDetailsFavouriteIcon>
{favourite ? '❤️' : '🤍'}
</LessonDetailsFavouriteIcon>
</TouchableOpacity>
</LessonDetailsCategoryHead>
<LessonDetailsTitle>{lesson.title}</LessonDetailsTitle>
<LessonDetailsAuthor>{lesson?.author}</LessonDetailsAuthor>
</LessonDetailsInfoContainer>
<LessonDetailsCardImage
source={{
uri: lesson.image,
}}
/>
<LessonDetailsContentContainer>
<LessonDetailsDescriptionText>
{lesson.content}
</LessonDetailsDescriptionText>
</LessonDetailsContentContainer>
</LessonDetailsContainer>
);
};
export default LessonDetails;
For situation like this, you should learn global state management. ( Context API - Redux etc. )
I think you are disrupting in the wrong way the parameters passed to DetailScreen it should be something like this:
const LessonDetails = ({route}: LessonProps) => {
const {lesson, handleNewFavourite} = route.params;
// The rest of your component here
}
As the documentation here suggested. But as #omerfarukose mentioned is not a bad idea to use state management in this particular scenario

React-Native Nested Navigation is causing 2 menus to appear

I am currently running into an issue trying to make a Shopping List app with React-Native. I am using React Navigation to handle the screens but I have run into an issue where if I click the Float Action Button on the Home Screen it will pop up the "I am the AddShoppingListForm", However when I open the next screen and click the button again that menu pops up as well as the menu that should pop up at the screen saying "I am the AddShoppingListItemForm" and they overlap each other.
How would I go about making sure the correct menu opens on the correct screen? I have the "isVisible" currently being stored in Redux to make it easier to change the value depending on how nested I am on the components, But I am not sure if this is correct way to go about doing this.
I tried to make a Overlay component that could act as a wrapper for the Menu component that gets passed there but I am not sure that is the correct way to go about doing that. Any advice would be greately appreciated.
Here is my snippets of code.
HomeScreen:
export default function HomeScreen({ navigation }) {
const isVisible = useSelector(selectOverlayVisiblity);
const dispatch = useDispatch();
const toggleOverlay = () => {
dispatch(
setOverlayVisible(!isVisible)
)
};
return (
<View style={styles.container}>
{/* Shopping List */}
<ShoppingList navigation={navigation} />
<PopupMenu menu={<AddShoppingListForm />} isVisible={isVisible} />
{/* Action Button */}
<ActionButton buttonColor="rgba(231,76,60,1)" onPress={toggleOverlay} />
</View>
);
}
ListScreen:
export default function ShoppingListDetailScreen({ navigation }) {
const currentlistitems = useSelector(selectCurrentList);
const isVisible = useSelector(selectOverlayVisiblity);
const dispatch = useDispatch();
const toggleOverlay = () => {
dispatch(
setOverlayVisible(!isVisible)
)
};
return (
<View style={styles.container}>
{currentlistitems.map(function (item) {
return <ListItemComponent key={item.id} title={item.title} avatar_url={item.avatar_url} />;
})}
<PopupMenu isVisible={isVisible} menu={<AddShoppingListItemForm />} />
{/* Action Button */}
<ActionButton buttonColor="rgba(231,76,60,1)" onPress={toggleOverlay} />
</View>
);
}
Popup Menu:
export default function PopupMenu({ navigation, isVisible, menu }) {
const dispatch = useDispatch();
const toggleOverlay = () => {
dispatch(setOverlayVisible(!isVisible));
};
// console.log('MENU >>>', menu);
return (
<View style={styles.container}>
{/* Overlay */}
{Platform.OS === "web" ? (
<WebModal isVisible={isVisible} menu={menu} />
) : (
<MobileModal isVisible={isVisible} menu={menu} />
)}
{/* End of Overlay */}
</View>
);
}
ShoppingListForm:
function AddShoppingListForm() {
const route = useRoute();
console.log('AddShoppingListLitem >>', route.name);
return (
<View>
{route.name === 'Home' &&
<Text style={{color: "#fff"}}>I am the AddShoppingListForm</Text>
}
</View>
)
}
ShoppingListItemForm:
function AddShoppingListItemForm() {
const route = useRoute();
console.log('AddShoppingListLitem >>', route.name);
return (
<View>
{route.name === 'List' &&
<Text>I am the ShoppingListItemForm</Text>
}
</View>
)
}
Here are example images so you can see what I mean:
I have fixed the issue I was having by removing the isVisible from Redux and rather using useState to just store the isVisible in that specific screen instead of across the app.

React native: rendering conditional component based on state value change in Modal

I have a requirement of showing a tab like behavior inside a react native Modal
I have started by creating a state variable selectedSub which is initialized with value 'from'
Now my modal has 2 TouchableHighlight as below
_availabiltyModal() {
return (
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'from' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'from' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'from' && styles.activeText}>From</RkText>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'to' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'to' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'to' && styles.activeText}>To</RkText>
</TouchableHighlight>
{this._renderPicker()}
)}
These two are responsible to change the state param selectedSub as required.
Based on this state param i am conditionally showing another component i made and imported as below
_renderPicker() {
if (this.state.selectedSub == 'from') {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.from }} />
} else {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.to }} />
}
}
I have called this function in the Modal below the TouchableHighlight's
Now as per the RN docs when ever the state variable is updated with this.setState() method the component should re-render. Everything else is working fine (TouchableHighlight styles changing) and also the updates to the state are reflecting in the console. But the _renderPicker function does not return the changed view and is always stuck on the view which was set when the parent was initialized as pointed earlier.
Could somebody help me point the problem here which i might have been overlooking. Also on the side note all this calls are actually directly made outside the main render() method. Could that be a possible issue
------update------
here is the complete reference
render() {
return({this._availabiltyModal()}
<View style={appStyles.tagsWrapper}>
{this.state.week.map((day, i) => {
return (
<TouchableHighlight
key={i}
activeOpacity={0.9}
style={[appStyle.mr10, appStyle.mb10]}
onPress={() => {
this.setModalVisible(true, day);
}}>
<Text style={appStyle.tag}>{day}</Text>
</TouchableHighlight>
)
})}
</View>
)
}
Move the _renderPicker method inside the render() method like...
render() {
...
{this._renderPicker()}
...
}
Looking at the code of the MODAL component from react-native
render(): React.Node {
....
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
this.props.children
);
return (
<RCTModalHostView
....>
<View style={[styles.container, containerStyles]}>
{innerChildren}
</View>
</RCTModalHostView>
);
}
The state you are changing are of the component that use the modal component thats render its children through the upper function.. when the state update it only re render the component whose state is updated.. so somewhere down rendering child inside component it does not get re render until force re render is applied.
Here is an helpful article to further explain how exactly this functions, forcefully re-rendering the child component

how to add pull to refresh with redux react native

I wanna add pull to refresh but i dont know what to call from from _Refresh(). I have action, constants and reducers in another page. How can i recall the api.
thanks for help in advance.
I wanna add pull to refresh but i dont know what to call from from _Refresh(). I have action, constants and reducers in another page. How can i recall the api.
thanks for help in advance.
class HomeworkList extends Component {
constructor(props){
super(props);
this.state = {
getHW : null,
refreshing: false,
appState: AppState.currentState,
months : ["Jan","Fev","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
}
}
_onRefresh() {
this.setState({refreshing: true});
}
componentDidMount(){
this.props.getHomework();
}
render() {
const {homework,isFetching} = this.props.homework;
if(isFetching){
return(
<View>
<ActivityIndicator
color = '#bc2b78'
size = "large"
/>
</View>
)
}
else{
return (
<ScrollView style={styles.container} refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
/>
}>
<View style={styles.filterView}>
<View style={{flexDirection:'row'}}>
<Icon size={20} name="ios-options" color="#000000" /><Text style={[{color:"#333333"},mainStyles.postTitle]}> FILTER BY</Text>
</View>
<View>
<ScrollView
horizontal={true}
showsHorizontalScrollIndicator={false}
>
{
this.state.months.map((item,i)=>{
return(
<TouchableHighlight key={i} style={styles.filterItem} onPress={() => {}} underlayColor={"#de0000"}><Text >{item}</Text></TouchableHighlight >
)
})
}
</ScrollView>
</View>
</View>
<View style={[styles.titleView,mainStyles.coloredBackground]}>
<TouchableOpacity
>
<Text style={styles.title}>
THIS MONTH
</Text>
</TouchableOpacity>
</View>
<View style={styles.viewPadding}>
{
homework.length ? (
homework.map((item, index) => {
return(
<TouchableOpacity
onPress={() => this.props.navigate('Chat', { title: item })}
key={item.id}
>
<Text style={[styles.listItems,{borderColor:randomHex()}]}>
{item.date}
</Text>
</TouchableOpacity>
)
})
):null
}
</View>
</ScrollView>
);
}
}
}
function mapDispatchToProps(dispatch){
return{
getHomework: () => dispatch(fetchHomeworkFromApi()),
getNaviagationName:()=>dispatch(getNaviagationName())
}
}
function mapStateToProps(state){
return{
homework: state.homework
}
}
export default connect(mapStateToProps,mapDispatchToProps)(HomeworkList);
Dispatch a refresh action inside the refresh callback.
This action should then dispatch any number of other actions to "re fresh" like fetchHomeworkFromApi and getFreshData. Don't re-fetch things that make no sense to refresh.
Inside your reducers make sure that when fresh data arrives, it replaces the old. (Avoid situations where new data is appended to old data)
Very simple, assuming you have API call defined in your redux action, hooked up with redux thunk, something like below
// Action.js
const reduxAction = () => {
return async dispatch => {
dispatch({ type: IS_LOADING });
//Call backend API
const result = axios.xxx();
dispatch({ type: GOT_RESULT, payload: result });
}
}
The above is the very standard API calling from redux, nothing to do with pull to refresh yet.
Now since we have a redux Action for our component, and that you have RefreshControl, you need to update your `_
_onRefresh() {
this.props.reduxAction();
}
As for your RefreshControl, instead of using localState, use the loading state from your redux, something like below
<RefreshControl
refreshing={this.props.refreshing} //Use the one from redux, not component state
onRefresh={this._onRefresh.bind(this)}
/>

Categories

Resources