How to find the number of views of the react native page? - reactjs

I'm making an app. I want to find the number of views of a forum application and posts shared in this application and send them to the firebase database? What path should I follow?

There are 2 ways to count post views.
Inside Post Feed List
When post posts are rendered inside scrollable components like ScrollView or FlatList, Use components like - https://www.npmjs.com/package/#skele/components .
This is a higher-order component that tracks if the child component is inside viewport then you can use this information to update the post view counts.
import { Viewport } from "#skele/components";
import { FlatList, View, Text } from "react-native";
const ViewportAwareView = Viewport.Aware(View);
function HomeScreen() {
const onViewportEnter = async () => {
// Logic to increase view counts here
};
const onViewportLeave = async () => {};
const renderItem = ({ item }) => {
return (
<ViewportAwareView
onViewportEnter={onViewportEnter}
onViewportLeave={onViewportLeave}
retainOnceInViewport={false}
>
<Text> .... </Text>
</ViewportAwareView>
);
};
return (
<Viewport.Tracker>
<FlatList scrollEventThrottle={16} renderItem={renderItem} />
</Viewport.Tracker>
);
}
Count View inside Post Detail Screen
When users visit post screen, increase post view count when screen mounted.
function postDetailsScreen() {
useEffect(() => {
// Logic to increase post view counts
}, []);
return <View> Post details here... </View>;
}

Related

Unable to prevent Flatlist from re-rendering (already using useCallback and memo)

I'm building a to do list app as part of a coding course, using Firebase Realtime Database and React Native with Expo.
I have no problems rendering the to do list, and in this case clicking a checkbox to indicate whether the task is prioritized or not.
However, each time I click on the checkbox to change the priority of a single task in the to do list, the entire Flatlist re-renders.
Each task object is as follows:
{id: ***, text: ***, priority: ***}
Task Component: (It consists of the text of the to do (task.text), and also a checkbox to indicate whether the task is prioritized or not). I've wrapped this component in React.memo, and the only props passed down from Todolist to Task are the individual task, but it still re-renders every time. (I left out most of the standard imports in the code snippet below)
import { CheckBox } from '#rneui/themed';
const Task = ({
item,
}) => {
console.log(item)
const { user } = useContext(AuthContext);
const onPressPriority = async () => {
await update(ref(database, `users/${user}/tasks/${item.id}`), {
priority: !item.priority,
});
};
return (
<View
style={{ flexDirection: 'row', alignItems: 'center', width: '95%' }}
>
<View
style={{ width: '90%' }}
>
<Text>{item.text}</Text>
</View>
<View
style={{ width: '10%' }}
>
<CheckBox
checked={item.priority}
checkedColor="#00a152"
iconType="material-community"
checkedIcon="checkbox-marked"
uncheckedIcon={'checkbox-blank-outline'}
onPress={onPressPriority}
/>
</View>
</View>
)}
export default memo(Task, (prevProps, nextProps) => {
if (prevProps.item !== nextProps.item) {
return true
}
return false
})
To Do List parent component: Contains the Flatlist which renders a list of the Task components. It also contains a useEffect to update the tasks state based on changes to the Firebase database, and the function (memoizedOnPressPriority) to update the task.priority value when the Checkbox in the task component is clicked. Since memoizedOnPressPriority is passed a prop to , I've tried to place it in a useCallback, but is still re-rendering all items when the checkbox is clicked. (I left out most of the standard imports in the code snippet below)
export default function Home2() {
const { user } = useContext(AuthContext);
const [tasks, setTasks] = useState([]);
useEffect(() => {
if (user) {
return onValue(ref(database, `users/${user}/tasks`), (snapshot) => {
const todos = snapshot.val();
const tasksCopy = [];
for (let id in todos) {
tasksCopy.push({ ...todos[id], id: id });
}
setTasks(tasksCopy);
});
} else {
setTasks([]);
}
}, [user]);
const renderItem = ({ item }) => (
<TaskTwo
item={item}
/>
);
return (
<View style={styles.container}>
<FlatList
data={tasks}
initialNumToRender={5}
windowSize={4}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
);
}
Could anyone let me know what I'm doing wrong, and how I can prevent the entire Flatlist from re-rendering each time I invoke the memoizedOnPressPriority function passed down to the Task component from the TodoList parent component? Any help is much appreciated!
The flamegraph for the render is below:
Update: I moved the prioritize function (memoizedOnPressPriority) into the Task component and removed the useCallback - so it's not being passed as a prop anymore. The re-render still happens whenever I press it.
Update 2: I added a key extractor , and also a custom equality function into the memoized task component. Still keeps rendering!
I'm not familiar with Firebase Realtime Database, but if I understand the logic correctly, the whole tasks array is updated when one item changes, and this is what is triggering the list update.
Fixing the memo function
Wrapping the Task component in memo does not work because it performs a shallow comparison of the objects. The objects change each time the data is updated because a new tasks array with new objects is created, so the references of the objects are different.
See this post for more details.
To use memo, we have to pass a custom equality check function, that returns true if the component is the same with new props, like so:
export default memo(Task, (prevProps, nextProps) => {
if (prevProps.item.id === nextProps.item.id && prevProps.item.priority === nextProps.item.priority ) {
return true;
}
return false;
})
Note that is the text is modifiable, you'll want to check that too.
Alternative solution : read data from the Task component
This solution is recommended and takes full advantage of Firebase Realtime Database.
To update only the component that is updated, you need to pass an array of ids to your flatlist, and delegate the data reading to the child component.
It's a pattern I use with redux very often when I want to update a component without updating the whole flatlist.
I checked the documentation of Firebase Realtime Database, and they indeed encourage you to read data at the lowest level. If you have a large list with many properties, it's not performant to receive the whole list when only one item is updated. Under the hood, the front-end library manages the cache system automatically.
//TodoList parent Component
...
const [tasksIds, setTasksIds] = useState([]);
useEffect(() => {
if (user) {
return onValue(ref(database, `users/${user}/tasks`), (snapshot) => {
const todos = snapshot.val();
// Build an array of ids
const tasksIdsFromDb = todos.map((todo) => todo.id);
setTasksIds(tasksCopy);
});
} else {
setTasksIds([]);
}
}, [user]);
...
// keep the rest of the code and pass tasksIds instead of tasks to the flatlist
const Task = ({ taskId, memoizedOnPressPriority }) => {
const [task, setTask] = useState(null)
const { user } = useContext(AuthContext);
useEffect(() => {
if (user) {
// retrieve data by id so only the updated component will rerender
// I guess this will be something like this
return onValue(ref(database, `users/${user}/tasks/${taskId}`), (snapshot) => {
const todo = snapshot.val();
setTask(todo);
});
} else {
setTask(null);
}
}, [user]);
if (task === null) {
return null
}
// return the component like before

Need to re-render component after state is set to true

In my react app, I have a page that allows the user to "add" exercises to a personal list. The option to add an exercise is included in one "page" and the ability to see added exercises is view in a other. Side by side showing the two pages, notice 'Your Exercises' is empty. What I am trying to do is display these selected exercises. The exercises themselves are loaded via mapping function, from an array of objects. each object has a "selected" field and are set as "false" by default. My "add" button in the exercises page changes the state value of each component to "true", so now what I want to do is get these "true" values and render them on the page where it should show your exercises.
//Render your exercises
import React from "react";
import ExListItem from "./ExListItem";
// import selectedExercises from "../../../selectedExercises";
import exercises from "../../../exercises";
const selectedExerciseList = exercises
.filter(function (item) {
return item.selected === true;
})
.map(function ({name, reps}) {
return {name, reps};
});
// MAP HERE
function createSelectedExercises(exercises) {
return <ExListItem name={exercises.name} reps={exercises.reps} />;
}
function ExerciseList() {
return (
<ul data-tag="channelList" id="exercise-list" class="list-group mb-3">
{selectedExerciseList.map(createSelectedExercises)}
</ul>
);
}
export default ExerciseList;
Shouldnt this map the exercises?
You may need to use React state to accomplish this! You can do so with the following:
import React, { useState, useEffect } from 'react'
const Component = () => {
const [exercises, setExercises] = useState([])
useEffect(() => {
setExercises(exerciseFilterFunction(exercises)) // this will cause the re-render
}, [])
return (
<div>{exercises.map(e => <div>{e.name}</div>)}</div>
)
}

How to go back to previous scroll place when navigation to go back?

I got a productList page and product details page
In the product list page I got a scroll handler on the flatList which load more data when the end of the list is reached.
The product list component is something like this
let productList = useSelector(state => state.productReducer.productList);
const [numbersOfItem, setNumberOfItem] = useState(20);
const [itemList, setItemList] = useState(productList);
const handleLoadMoreData = async () => {
setLoadingMoreData(true);
try {
let requestNumber = numbersOfItem + 20;
setNumberOfItem(requestNumber);
await dispatch(
fetchProductList(
categoryTitle,
subCategoryName,
activeTab,
requestNumber,
),
);
} catch {
console.log('error');
} finally {
setLoadingMoreData(false);
}
};
<FlatList
data={itemList}
numColumns={2}
nestedScrollEnabled
columnWrapperStyle={styles.flatItemColumn}
scrollEnabled
scrollEventThrottle={16}
snapToAlignment="start"
decelerationRate={'fast'}
showsHorizontalScrollIndicator={false}
renderItem={renderIndividualItem}
keyExtractor={(item, index) => String(index)}
onEndReached={() => handleLoadMoreData()}
/>
After Scrolling for a while when I see a product I wanna explore I will goes into the product details page
Inside the product details page when I click navigation.goBack().
The scroll position in the productList is go back to the place of 20th item. I think its because the default display is setNumberOfItem is set to 20.
How can I change this behavior.
Adding an Api Call in a focus callBack in the screen that you want to go back to, can help solve this problem.(ProductList)
React.useEffect(() => {
fetchData();//func to get the data
const willFocusSubscription = props.navigation.addListener('focus', () => {
fetchData();//call it here as well
});
return willFocusSubscription;
}, []);
Use theinitialScrollIndex prop of the FlatList component to order the index where you want to start the FlatList. And find out how to know from which screen you come from when your productList page get focused

Load Components Dynamically React Js with load more button

I'm new to React Js, so I can't find a solution to my problem by myself, please help me.
I'm working on a website with a blog page, blogs should be displayed dynamically on the page. When page loads I want it to have 4 blogs, and underneath there will be button, so when the user clicks it, React should render and display the rest of the blogs.
My code so far looks like this:
import { blogs} from "./blogs";
import { Blog} from "./Blog";
function BlogList() {
const cardComponent = blogs.slice(0,6).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>{cardComponent}</div>
)
}`````
**This code lets me display 6 blogs when the page is loaded, what I want to do is add "Load More" button under these already loaded 6 blogs, when the user clicks the button it should render and display another 4 blogs from "blogs", and again have Load More button.** Any help will be greatly appreciated,
Thank you.
Your code shows a fixed amount of blogs (6). Instead of hardcoding the amount of visible blogs, you need to store it in a variable that you can change later. We will use useState for this. You also need to change the amount of posts based on a button press, so a button and an action is also needed.
function BlogList() {
// Starting number of visible blogs
const [visibleBlogs, setVisibleBlogs] = useState(6)
// Set the visible blogs to the current amount + 4
// eg. if there are 10 visible post, clicking again will show 14.
const handleClick = () => {
setVisibleBlogs(prevVisibleBlogs => prevVisibleBlogs + 4)
}
const cardComponent = blogs.slice(0, visibleBlogs).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>
{cardComponent}
<button type="button" onClick={handleClick}>
See more
</button>
</div>
)
}
I hope it helps.
You can do it this way:
function BlogList() {
const [maxRange, setMaxRange] = useState(6);
const loadMore = useCallback(() => {
setMaxRange(prevRange => prevRange + 4);
},[])
const cardComponent = blogs.slice(0, maxRange).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>
{cardComponent}
<button onClick={loadMore}>Load More</button>
</div>
)
}
So you can just maintain the maximum number of currently displayed Blogs in state and increment it when the button gets clicked.
I used useCallback so that a new function doesn't get created when the component re-renders.

How to properly update/re-render a component that is not a child React Native?

I'm using a react-navigation. More specifically, I have a materialTabNavigator nested inside of a drawerNavigator. Each tab is in itself a stackNavigator. I have a button in homeScreen, that navigates to makePost.js. There I take in information and store it to Async storage using a simple wrapper.
In Posts.js there's a FlatList displaying each post as a component. The data for the FlatList is initially set correctly after making a request from Async Storage. The problem is that this only happens when the app is first opened. I have tried many different approaches to solve this. The only way so far I've found is to continuously setState in ComponentDidUpdate() in Posts.js. Obviously this is problematic, because it re-renders constantly. I can set a flag to stop is from rendering, but then it will not re-render again.
Ultimately, what I'd like to happen is that when I hit the user is done entering their information and is ready to make a post, they hit the button in makePost.js, and the data in the FlatList of Posts.js is update.
I've tried to pass parameters using navigation, does not work, parameters get lost somewhere, probably because of the nested navigators.
I could really used some guidance on the proper way to accomplish this.
( Navigators; not sure why this is forcing to one line )
---drawer
--tabNav
-home
homeScreen.js
makePost.js
-posts
posts.js
-messages
--drawer1
--drawer2
//Posts.js
export default class Posts extends React.Component {
state = {
rows: [
{id: 0, text: "dog"},
],
}
componentDidMount() {
this.loadState();
}
loadState = () => {
var value = store.get('posts').then((res => {
if (res === null) {
res = [{id: 0, text: "default"}]
} else {
res = res
}
this.setState({rows: res})
}))
}
componentDidUpdate() {
this.loadState();
}
renderItem = ({item}) => {
return (
<BoardTab style={styles.row} />
)}
render() {
return (
<View style={styles.view}>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<BoardScreenFooter />
</View>
);
}
And Posts.js button looks like this:
<TouchableOpacity
onPress={ () => {
this._onPressButton
this.storeFunc(this.state.newPost)
const retval = this.state.rows
this.props.navigation.navigate('Board',
{rowsID: retval});
}
}>
<Icon
reverse
name='md-camera'
type='ionicon'
color='green'
size={12}
/>
</TouchableOpacity>
storeFunc(newObj) {
newObj.id = newObj.id + 1
store.push('posts', newObj)
store.get('posts').then((res) => {
this.setState({rows: res})
})
}
Rapidly, i would say: use Redux. It alloq you to have global state in your app, which mean you can access the state anywhere (And also set them anywhere)
When opening the app, you get the data from the AsyncStore into the Redux store. You listen to the redux state (Which will be a props in your component) and display your list. When modifying your list in the other tab, you need to do 2 things:
Store the new data in the AsyncStorage
Update the state in the redux store. Since Posts.js will be listening at the redux store (as a props), it will re-render each time your data will change
A simple way to re-render a React-Navigation screen view on navigating to it:
All credit goes to Andrei Pfeiffer, Jul 2018, in his article: "Handle Tab changes in React Navigation v2" https://itnext.io/handle-tab-changes-in-react-navigation-v2-faeadc2f2ffe
I will reiterate it here in case the above link goes dead.
Simply add a NavigationEvents component to your render function with the desired listener prop:
render() {
return (
<View style={styles.view}>
<NavigationEvents
onWillFocus={payload => {
console.log("will focus", payload);
this.loadState();
}}
/>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<PostScreenFooter />
</View>
);
}

Resources