SectionList re-renders everything unexpectedly - react-native

I'm new to React-Native, currently working a project that displays photos and its related information. The project uses the standard SectionList(RN 0.55).
The problem I have is that each time I add a photo, all the subcomponent in the list will be re-rendered. And I've noticed a significant slow down when the list grows to 50 something.
I have the following setup:
I have a redux store which contains Data(basically a wrapper around photo information), each time user does some action, the Data will be copied, modified, then reassigned back to redux store.
Then I have a class like following to render SectionList
class PhotoList extends PureComponent {
render() {
<SectionList
sections={deriveData(this.props.data)}
extraData={this.state}
renderItem={this.onRenderItem}
>
}
onRenderItem(item) {
return <View>
// two nested level components to hold information
</View>
}
driveData(data) {
// do a lot of data transformation and calculate derived value
return derivedData;
}
}
// data is redux connected
My primary confusing point is that, when SectionList takes in section in my case, the data(as a whole) is already a new copy and is modified(since a new photo is added), so it causes SectionList to re-renders everything?
I want SectionList to only additionally render the photo I just added, any suggestions?

Can you change the data ("data transformation and calculate derived value") at action and update that on redux 'data'. Then change like the following.
class PhotoList extends PureComponent {
render() {
<SectionList
sections={this.props.data} // Directly call data
extraData={this.props.data}
renderItem={this.onRenderItem}
>
}
onRenderItem(item) {
return <View>
// two nested level components to hold information
</View>
}
}
If you use redux then you have to handle data updating on redux. Then only the result will get.

Related

How can we prevent mobile app crashes if cached data does not align with new reducers structure?

I am currently developing an e-commerce management mobile application with React Native, and Redux. We persist data using redux-persist. This brings a great experience with cached data which is a principle in offline-first development.
But there might be a bug that can happen in the mobile world environment.
Let's assume that I have a reducer called "products". That reducer is just an array with product objects. The user logs in and now the data in that reducer is persisted. Later, my development team decides to update the mobile app with a new structure on that "products" reducer. The user's app gets updated, and now the persisted/cached data doesn't align with the new "products" reducer structure which leads to the app crashing.
I may be wrong, but is this an actual bug that can exist? If so, what is a work around or solution?
Thanks!
This is a potential bug that can exist i agree because it has happened to me twice and i discovered something which might be helpfull to you. The cause of the crash is dependent on what is stored on disk and what your code uses let me explain.
//let say we have a reducer
function ProductReducer(
state={
products:[],
fetching:false
},action){
switch(){......}
}
// lets say we have a combine reducer
combineReducers({
products: ProductReducer
})
//let say we have a component that consums this products
function RenderProducts(){
const {products, fetching} = useSelector((store)=>({
products: store.products.products,
fetching: store.products.fetching,
}))
return (
<View>
{
fetching && (
<Text>
loading...
</Text>
)
}
{products.map((item)=>
<View>
<Text>
{item.name}
</Text>
</View>)
}
</View>
)
}
NOW OUR UPDATE LOOKS LIKE THIS
// Now lets new reducer be
function ProductReducer(
state={
productsList:[],
fetchingProduct:false
},action){
switch(){......}
}
// Now our Render component becomes
function RenderProducts(){
const {products, fetching} = useSelector((store)=>({
products: store.products.productsList,
fetching: store.products.fetchingProduct,
}))
return (
<View>
{
fetching && (
<Text>
loading...
</Text>
)
}
{products.map((item)=>
<View>
<Text>
{item.name}
</Text>
</View>)
}
</View>
)
}
Now the second code is going to cause a crash crash for one reason you will be trying to call .map on an undefined object, why is this happening the simple reason is illustrated below
// Intial store
const store = {
products:{
products:[], // array of products
fetching: false,
}
}
**This was our store stored on disk as JSON string
**
AFTER OUR UPDATE TO REDUCER our store remains the same and before we fetch data from our server so that our reducer can write our update to disk the render product component is trying to apply our update and thus the crash
SOLUTION
You can purge the store // you will not want to this because all stored data including tokens and vital inofrmaton will be lost and user will start app afresh
Understanding this problem you can now have several work arounds like dispatching an action to enforce your migration on disk before your components get rendered . this is what i mean
to apply update i will do this ...
function ProductReducer(
state={
productsList:[],
fetchingProduct:false
},action){
switch(action.type){
case 'MIGRATING':{
if(store.products){ // checking if products exist on the products
reducer this will handle updates and new installs
state = {...state,productsList:
state.products,fetchingProduct:state.fetching}
}
return state;
}
}
}
This will write changes to disk but make sure this runs before your components are rendered i will do this for all reducers
I will just write my component to always check if a data they are reading is available or else render empty data like '',[],{} for string, array, and object respectively

dynamic text change in react-native

Hi I am looking for the solution to change text dynamically.
I am writing code to show processing results on screen.
After some googling, I found there is a code to update text dynamically as follows.
But I would like to update text without any internal event. I want to change text from outside of the class. But I don't know how to implement it as I am a javascript and react-native beginner. There are other classes to process some functions so that I need to show the updated results using Results class which is an another component of the screen.
How can I deliver 'result' to Results class and how to update it dynamically and automatically?
class Results extends Component {
constructor() {
super()
this.state = {
log: 'Processing results'
}
}
updateText = (result) => {
this.setState({log: result})
}
render() {
return (
<View>
<Text onPress = {this.updateText}>
{this.state.log}
</Text>
</View>
)
}
}
This sounds to me that props can solve your problem.
Basically when you try to render Results class, pass along the value as a prop like below:
<Results dynamicText='HI' />
Then, from your Results class, access this external value via this.props.dynamicText as below
class Results extends Component {
render() {
return (
<View>
<Text>
{this.props.dynamicText}
</Text>
</View>
)
}
}
In addition to what #Issac answered, you can also hook up your current class to Redux and dispatch actions from another class to force state changes.
React Native and ReactJS has a different concept of how classes react to each other. Most other languages use inheritance based interactions to affect changes in classes other than itself. React itself is more composition based where changing the value/state/variable of one class requires either a state change or a prop change. The caveat to that us using Redux, which utilizes an overarching Store where any component that's connected to it can pull values or dispatch actions to change values.

React Native (Redux) FlatList jumping to top of list when onEndReached called

I have a FlatList in ReactNative which pulls a list of articles from an API. When the end of the list is reached on scrolling, a further page of articles is pulled from the API and appended to the articles list in a Redux reducer.
The FlatList is set up as:
render() {
return(
<FlatList data={this.props.articles.articles} // The initial articles list via Redux state
renderItem={this.renderArticleListItem} // Just renders the list item.
onEndReached={this.pageArticles.bind(this)} // Simply calls a Redux Action Creator/API
onEndReachedThreshold={0}
keyExtractor={item => item.id.toString()}/>
)};
The Redux 'articles' state object is mapped to the component using the React Redux connect function. The relevant reducer (some items removed for brevity) looks like:
/// Initial 'articles' state object
const INITIAL_STATE = {
articles: [],
loading: false,
error: '',
pageIndex: 0,
pageSize: 10
};
export default(state = INITIAL_STATE, action) => {
switch(action.type){
// The relevant part for returning articles from the API
case ARTICLES_SUCCESS:
return { ...state, loading: false, articles: state.articles.concat(action.payload.items),
pageIndex: action.payload.pageIndex, pageSize: action.payload.pageSize}
default:
return state;
}
}
The payload is a simple object - the articles are contained in the action.payload.items array, and there is additional paging information as well in the payload (not important to this problem).
Everything is fine with the API returning the correct data.
The problem is that when the end of the FlatList is reached on scrolling, the API is called, the new articles are pulled fine and appended to the this.props.articles state object and to the FlatList, BUT the FlatList jumps/scrolls back to the first item in the list.
I think the problem is probably with how I am returning the state in the Redux reducer but I'm not sure why.
Is using concat the correct way to return the new state with the new articles appended?
It might be too late to answer this but I experienced exact same problem having my data served from redux to the component and managed to fix the jumping effect by making my component a normal Component rather than PureComponent allowing me to use shouldComponentUpdate life-cycle hook method. This how the method should look like for you component:
shouldComponentUpdate(nextProps) {
if (this.props.articles === nextProps.articles) {
return false;
}
return true;
}
This method (if implemented) should return a boolean value indicating whether the component should update or not. What i did here was to prevent the component from re-rendering in case the contents of articles hasn't changed.
Hope this would be helpful for you or others who might have faced similar problem.
You need to add to listItem styles height and width. Freezing and jumping maybe because list tried to calculate sizes for all items even if they dont display.
And you can find answer in: https://github.com/facebook/react-native/issues/13727
Using FlatList as component and adding data as props re-renders whole content, so it scrolls to top. I recommend using it directly inside your page and changing its data using this.state, not props

Accessing redux store via child component in react native

I am using Redux with React Native and I'm having problem in accessing a specific index of the redux store array with the key.
Here's a rough idea of what I'm trying to do. I have a ParentComponent that accesses the reduxStore for a state data and data contains an array of id,title in it.
class ParentComponent extends Component {
dataArray() {
return Object.keys(this.props.data).map(key => this.props.data[key])
}
/*
Consider the dataArray has elements which are an array of `id` & `title`
*/
render() {
return (
<ScrollView style={styles.fragmentContainer}>
{this.dataArray().map((element) => {
return
<ChildComponent
key={element.id}
id={element.id} />
})}
</ScrollView>
)
}
}
Now I wanna access title of each element of the array via the ChildComponent. This is what I'm trying to do to access it:
class ChildComponent extends Component {
render(
return (
<View>
<Text>{this.props.data[this.props.id].title}</Text>
</View>
)
)
}
Obviously the above doesn't work. It's just an idea of what I'm trying to do. I am unable to access the redux store data using a given key. I tried to find out how one can access it but I found no solution. Is my method of accessing the state for the ChildComponent wrong. Can anyone refer me to how I can achieve this?
NOTE: I specifically want to access the state via the ChildComponent. If that's not how it's supposed to work like, please refer me to some other way or some documentation that explains how this is supposed to be done.
If your child component is a class component, you can import connect, set up your reducer in mapStateToProps and bind them in your export.
You can also send the reducer prop value as props from parent to child component. Something like this <Child value={this.props.myData} />. Now in your child component you can refer it as this.props.value. If you use this approach, it doesn't matter if your child is a class component or not. Don't forget to also instance value in your child component creation.
Hope it helps.
First of all I don't see you passing a data prop from your parent to child. Thus you won't get this.props.data in your child component.
I don't understand why would you want to extract title that way when you can pass it as a prop just like you passed id.
If you specifically want to access redux store, then you use connect from react-redux.
Here's a link to see how to do that
http://redux.js.org/docs/basics/UsageWithReact.html

State Management with Multiple Inputs (Lists)

I'm trying to figure out the best place to manage the state for lists of input in react-native but haven't found any good, thorough examples or clear guidance and I can see a few options.
For simplicity not including specifics about the tool for managing state, as my understanding is how the state is stored doesn't impact the component where it's managed.
Scenario
A screen component that receives an array of items as props to be displayed in a List of ListItems. Each ListItem includes a input, for simplicity imagine a switch (boolean).
Use cases include an array of form questions or settings to be displayed in a list and allowing user input. Pseudocode:
class SettingsView extends Component {
render () {
return (
<View>
<List style={styles.list}>
{this.props.inputArray.map((item, index) => (
<ListItem
title={item.title}
isSwitched={item.value}
key={index}
onSwitchChange = {this.onChange}
/>
))}
</List>
</View>
);
}
}
Option 1
Based on the Thinking in React page, one option that comes to mind is managing state at the Screen (SettingsView) level by creating an array of (inputArray).length in the SettingsView constructor state and have the onChange function update that array based on key.
Option 2
The second option I see is having each ListItem manage the state it's displaying. This makes sense from an encapsulation perspective to me, but then less so for managing of the state, given that the onSwitchChange function is in the SettingsView and I'm not as clear how this would work.
Are there other options I'm not considering? Admit that experience in React/RN is limited and def coming from a more object mindset like iOS's list datasource model.
Note: Another option is having the entire inputArray in state, instead of passed as props. My understanding is that state should be minimized, so was designing that only the inputs to each item in inputArray should be in the state. i.e. Form Labels (i.e. questions) are props not state.
Option 1 would be the better choice, there is this concept "Smart Components and Dumb Components"
Smart Components: typically holds the state of all the child components associated with it, it also defines the functions that is passed down to child components to modify its state.
Dumb Components: These are components that receives props which includes data and functions they typically don't have their own state and relies heavily on the parent component.
The problem is that when you create a component you need to decide whether it's smart or dumb, usually I associate a screen to a smart component, in your example it would be the SettingsView(smart) that will hold the state and function and it's children will be the dumb components but this is really application specific decision because you might have a SettingsView that are dynamic based on context and so it would be much better to make it a dumb component let's use your example above
Since Settings View relies on this.props.inputArray passed from a parent
component(I will call this ParentComponent) you couldn't modify
inputArray directly in SettingsView what you could do is pass another prop from
ParentComponent to SettingsView which is a function that modifies inputArray
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
inputArray: [],
};
this.onSwitchChange = this.onSwitchChange.bind(this); // when passing fn
}
onSwitchChange(index) { // index will come from the child component
// do some logic here based on the index then update the state
this.setState('inputArray' updatedState); // updatedState is just an example variable
}
render() {
return (
<View>
<SettingsView
onSwitchChange={(index) => () => this.onSwitchChange(index)}
inputArray={this.state.inputArray}
/>
</View>
);
}
/*
(index) => () => this.onSwitchChange(index)
this is a function that will return another function
we need this because we want to delay the execution of onSwitchChange
and capture index and associate it with that method, typically this
is used if were passing function to child component(SettingsView) which will be used as
a handler for events.
*/
}
class SettingsView extends Component {
render () {
return (
<View>
<List style={styles.list}>
{this.props.inputArray.map((item, index) => (
<ListItem
title={item.title}
isSwitched={item.value}
key={index}
onSwitchChange={this.props.onSwitchChange}
/>
))}
</List>
</View>
);
}
}
This example might be pointless because you could just use SettingsView as the parent of ListItem and other components but since SettingsView state is now managed by ParentComponent it is now a dumb component and can be used in other screens that have the specific state that SettingsView needs to operate. the general goal is to create as many dumb components as possible the reason being is that these type of components are highly reusable because you just need to pass them props to properly work.