React native section list not re-rendering when item changes - reactjs

I have a sectionList in my react native project. it does not re-render if item changes.
My code:
test.js
class Test extends React.Component {
started = false;
causeData=[];
showLess=false;
items = [];
_start = () => {
const { ws } = this.props;
this.showLess = false;
if (ws.causes.length) {
this.causeData = {
title: Language.causes,
key: "cause",
data: []
};
ws.causes.forEach(cause => {
let causeDetails = {
key: "cause_" + cause.id,
name: "",
value: cause.name,
sortIndex: cause.sortIndex,
progress: cause.progress
};
this.causeData.data.push(causeDetails);
if (this.causeData.data.length > 4) {
this.causeData.data = this.causeData.data.slice(0, 4);
}
});
this.items.push(this.causeData);
console.log("causeData", this.causeData);
}
}
}
_renderItem = ({ item }) => {
return (
<View>
<Text key={item.key} style={styles.text}>{`${item.name} ${
item.value
}`}</Text>
</View>
);
};
_renderSectionHeader = ({ section }) => {
const { ws } = this.props;
const showMore = ws.causes.length > 0 && !this.showLess;
return (
<View style={styles.sectionHeader}>
<Text key={section.key} style={styles.header}>
{section.title}
</Text>
{showMore && (
<Button
onPress={this._afterCauseAnswered}
title={Language.showMore}
data={this.items}
accessibilityLabel={Language.causeDoneAccessibility}
/>
)}
</View>
);
};
_keyExtractor = (item, index) => item.key;
_afterCauseAnswered = () => {
const { stepDone, ws } = this.props;
this.causeData.data = { ...ws.causes };
stepDone("showMoreAnswered");
this.showLess = true;
};
render = () => {
if (!this.started) {
this.started = true;
this._start();
}
return (
<View style={styles.container}>
<SectionList
sections={this.items}
extraData={this.items}
renderItem={this._renderItem}
renderSectionHeader={this._renderSectionHeader}
keyExtractor={this._keyExtractor}
/>
</View>
);
};
}
in my section list section header contain a button called showMore. At initial rendering it will only show 5 items, while clicking showMore it should display all List. This is my functionality. but while clicking showMore button it will not show entire list only shows 5 items that means the sectionList does not getting re-render. How to resolve this? i am new to react native. Any idea what am I missing ? Any help would be greatly appreciated!

Keep items and showLess in a state and after pressing Button call setState with the new values. It will rerender the SectionList. Also, if you want to display multiple items with a displayed list, you need to move showLess to the item element so each item knows how to display it.

You just need to rerender your screen using state and it's done
this.setState({})

Your SectionList should always read from state ... as it should be your single source of truth
and here's how:
class YourComponent extends React.Component {
state = {
items: [],
};
// This will be called after your action is executed,
// and your component is about to receive a new set of causes...
componentWillReceiveProps(nextProps) {
const {
ws: { causes: nextCauses },
} = nextProps;
if (newCauses) {
// let items = ...
// update yout items here
this.setState({ items });
}
}
}

Related

How to update prop values in Child class component when Parent class component state is changed? : React Native

I have a parent class component called CardView.js which contains a child class component called Tab.js (which contains a FlatList).
When a button is pressed in CardView.js, a modal appears with various options. A user chooses an option and presses 'OK' on the modal. At this point the onOKHandler method in the parent component updates the parent state (tabOrderBy: orderBy and orderSetByModal: true). NOTE: These two pieces of state are passed to the child component as props.
Here is what I need:
When the onOKHandler is pressed in the parent, I need the child component to re-render with it's props values reflecting the new state values in the parent state. NOTE: I do not want the Parent Component to re-render as well.
At the moment when onOKHandler is pressed, the child component reloads, but it's props are still showing the OLD state from the parent.
Here is what I have tried:
When the onOKHandler is pressed, I use setState to update the parent state and then I use the setState callback to call a method in the child to reload the child. The child reloads but its props are not updated.
I have tried using componentDidUpdate in the child which checks when the prop orderSetByModal is changed. This does not work at all.
I have tried many of the recommendations in other posts like this - nothing works! Where am I going wrong please? Code is below:
Parent Component: CardView.js
import React from "react";
import { View } from "react-native";
import { Windows} from "../stores";
import { TabView, SceneMap } from "react-native-tab-view";
import { Tab, TabBar, Sortby } from "../components";
class CardView extends React.Component {
state = {
level: 0,
tabIndex: 0,
tabRoutes: [],
recordId: null,
renderScene: () => {},
showSortby: false,
orderSetByModal: false,
tabOrderBy: ''
};
tabRefs = {};
componentDidMount = () => {
this.reload(this.props.windowId, null, this.state.level, this.state.tabIndex);
};
reload = (windowId, recordId, level, tabIndex) => {
this.setState({ recordId, level, tabIndex });
const tabRoutes = Windows.getTabRoutes(windowId, level);
this.setState({ tabRoutes });
const sceneMap = {};
this.setState({ renderScene: SceneMap(sceneMap)});
for (let i = 0; i < tabRoutes.length; i++) {
const tabRoute = tabRoutes[i];
sceneMap[tabRoute.key] = () => {
return (
<Tab
onRef={(ref) => (this.child = ref)}
ref={(tab) => (this.tabRefs[i] = tab)}
windowId={windowId}
tabSequence={tabRoute.key}
tabLevel={level}
tabKey={tabRoute.key}
recordId={recordId}
orderSetByModal={this.state.orderSetByModal}
tabOrderBy={this.state.tabOrderBy}
></Tab>
);
};
}
};
startSortByHandler = () => {
this.setState({showSortby: true});
};
endSortByHandler = () => {
this.setState({ showSortby: false});
};
orderByFromModal = () => {
return 'creationDate asc'
}
refreshTab = () => {
this.orderByFromModal();
this.child.refresh()
}
onOKHandler = () => {
this.endSortByHandler();
const orderBy = this.orderByFromModal();
this.setState({
tabOrderBy: orderBy,
orderSetByModal: true}, () => {
this.refreshTab()
});
}
render() {
return (
<View>
<TabView
navigationState={{index: this.state.tabIndex, routes: this.state.tabRoutes}}
renderScene={this.state.renderScene}
onIndexChange={(index) => {
this.setState({ tabIndex: index });
}}
lazy
swipeEnabled={false}
renderTabBar={(props) => <TabBar {...props} />}
/>
<Sortby
visible={this.state.showSortby}
onCancel={this.endSortByHandler}
onOK={this.onOKHandler}
></Sortby>
</View>
);
}
}
export default CardView;
Child Component: Tab.js
import React from "react";
import { FlatList } from "react-native";
import { Windows } from "../stores";
import SwipeableCard from "./SwipeableCard";
class Tab extends React.Component {
constructor(props) {
super(props);
this.state = {
currentTab: null,
records: [],
refreshing: false,
};
this.listRef = null;
}
async componentDidMount() {
this.props.onRef(this);
await this.reload(this.props.recordId, this.props.tabLevel, this.props.tabSequence);
}
componentWillUnmount() {
this.props.onRef(null);
}
//I tried adding componentDidUpdate, but it did not work at all
componentDidUpdate(prevProps) {
if (this.props.orderSetByModal !== prevProps.orderSetByModal) {
this.refresh();
}
}
getOrderBy = () => {
let orderByClause;
if (this.props.orderSetByModal) {
orderByClause = this.props.tabOrderBy;
} else {
orderByClause = "organization desc";
}
return orderByClause;
};
async reload() {
const currentTab = Windows.getTab(this.props.windowId, this.props.tabSequence, this.props.tabLevel);
this.setState({ currentTab });
let response = null;
const orderBy = this.getOrderBy();
response = await this.props.entity.api.obtainRange(orderBy);
this.setState({ records: response.dataList })
}
refresh = () => {
this.setState({ refreshing: true }, () => {
this.reload(this.props.recordId, this.props.tabLevel, this.props.tabSequence)
.then(() => this.setState({ refreshing: false }));
});
};
renderTabItem = ({ item, index }) => (
<SwipeableCard
title={"Card"}
/>
);
render() {
if (!this.state.currentTab) {
return null;
}
return (
<>
<FlatList
ref={(ref) => (this.listRef = ref)}
style={{ paddingTop: 8 }}
refreshing={this.state.refreshing}
onRefresh={this.refresh}
data={this.state.records}
keyExtractor={(item) => (item.isNew ? "new" : item.id)}
/>
</>
);
}
}
export default Tab;

How to update the screen by rendering the new elements found in the search

I am trying to implement a search screen and the function works fine but i want to update the screen when an item that match the query is found in the array of notes.
What I have is a search bar that get the query and when the state is changed the componentDidUpdate will filter through an array of notes, and the notes that contain that query will be added to the new array called filteredNotes. What I am trying to do is to update the screen, each time an item is found or to implement an enter button so it wont use the processing power by rendering each time a character is typed, either way will do. Any help would be greatly appreciated.
Below is the entire component.
export default class MyComponent extends React.Component {
_isMounted = false;
state = {
search: '',
notes: [],
};
async componentDidMount() {
await this.getItems();
}
componentDidUpdate(prevProps) {
this._isMounted = true;
if (this._isMounted) {
if (this.state.search !== prevProps.search) {
let filteredNotes = this.state.notes.filter(el => el.name.includes(this.state.search));
this.state.notes = filteredNotes;
}
}
}
componentWillUnmount() {
this._isMounted = false;
}
async getItems() {
this.setState({ loadingNotes: true });
const notesObj = await getAllNotes();
this.getNotes(notesObj);
this.setState({ loadingNotes: false })
}
getNotes(notesObj) {
for (let i = 0; i < notesObj.length; i++) {
let note = {
name: notesObj[i].note.name,
}
this.state.notes.push(note);
}
}
render() {
const { search } = this.state;
const { notes } = this.state;
const { navigate } = this.props;
return (
<View>
<Searchbar
placeholder="Search"
onChangeText={(query) => { this.setState({ search: query }); }}
value={search}
/>
<NoteFlatList
data={notes.sort((a, b) => (a.name > b.name) ? 1 : -1)}
navigate={navigate}
/>
</View>
);
}
}

How to set the properties of a button child elements when it is clicked in react native

I have the following code which is available in all elements of a List and i will like to toggle the Icon active property when the button is clicked preferable using someFunction() method. I need help
import React from 'react';
export class QuestionsHelpers
{
username = '';
constructor(username)
{
this.username = username;
}
static renderQuestionContent(questionData, props, stateHandler)
{
/...///code
let status = questionData.user_liked === "1";
<Button transparent onPress={()=>someFunction()}>
<Icon active={status} name="thumbs-up"/>
<Text>Vote</Text>
}
}
below is where i call the method renderQuestion
questionList(props)
{
return this.state.data.map(function (questionData, index)
{
return QuestionsHelpers.renderQuestionContent(questionData, props, null)
}
);
}
and then the whole thing is inside the render method
render()
{
let data = <View/>;
if(this.state.data !== [])
{
data = this.questionList(this.props, null);
}
let spinner = this.state.loading === true? (<Spinner color='#FF7F00'/>) : (<Button title='Load more' onPress={()=>this.loadInitialState().done()}/>);
return (
<Content>
{data}
{spinner}
</Content>
);
}
You can use state for the button somewhere. For example:
class Doge extends React.Component {
state = {
status: false,
}
render() {
return (
<Button transparent={() => this.setState({ status: !this.state.status})>
<Icon active={this.state.status} name="thumbs-up" />
<Text>Vote</Text>
</Button>
);
}
}

forceUpdate warning on Grid

I am getting the warning below when I force update on a grid like this:
proxyConsole.js:56 Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
Not sure how to amend where to put forceUpdate() so that it behaves correctly.
const quoteList = [];
class App extends Component {
constructor() {
super();
this.state = {
response: {},
endpoint: "http://127.0.0.1:4001"
};
}
componentDidMount() {
const { endpoint } = this.state;
const socket = socketIOClient(endpoint);
this.theGrid.forceUpdate();
socket.on("FromAPI", data => this.setState({ response: data }));
}
cellRenderer ({ columnIndex, key, rowIndex, style }) {
return (
<div
key={key}
style={style}
>
{quoteList[rowIndex][columnIndex]}
</div>
)
}
render() {
const { response } = this.state;
if(Object.keys(response).length !== 0)
{
//console.log(response);
const row = symbolRowDictionary[response.ticker];
var column = 0;
switch(response.type)
{
case 'bid':
column = 1;
break;
case 'ask':
column = 3;
break;
case 'last':
column = 5;
break;
default:
console.log('Unknown type');
}
quoteList[row][column] = response.size;
quoteList[row][column + 1] = response.price;
this.theGrid.forceUpdate();
}
return (
<div style={{ textAlign: "center" }}>
{
<ul><li>Quote: {response.ticker} {response.type} {response.price} {response.size}</li></ul>
}
<div>
</div>
<Grid
ref={(ref) => this.theGrid = ref}
cellRenderer={this.cellRenderer}
columnCount={7}
columnWidth={75}
height={quoteList.length * 20}
rowCount={quoteList.length}
rowHeight={20}
width={800}
/>
</div>
);
}
}
export default App;
I would move quoteList to component state as following:
const endpoint: "http://127.0.0.1:4001";
class App extends Component {
constructor() {
super();
this.state = {
response: {},
quoteList = [];
};
}
componentDidMount() {
const socket = socketIOClient(endpoint);
let _this = this;
let {quoteList} = this.state;
socket.on("FromAPI", data => {
if(Object.keys(response).length !== 0) {
const row = symbolRowDictionary[response.ticker];
var column = 0;
switch(response.type)
{
case 'bid':
column = 1;
break;
case 'ask':
column = 3;
break;
case 'last':
column = 5;
break;
default:
console.log('Unknown type');
}
quoteList[row][column] = response.size;
quoteList[row][column + 1] = response.price;
_this.setState({ response: data, quoteList: quoteList })
}
}));
}
cellRenderer ({ columnIndex, key, rowIndex, style }) {
let {quoteList} = this.state;
return (
<div
key={key}
style={style}
>
{quoteList[rowIndex][columnIndex]}
</div>
)
}
render() {
const { response, quoteList } = this.state;
return (
<div style={{ textAlign: "center" }}>
{
<ul><li>Quote: {response.ticker} {response.type} {response.price} {response.size}</li></ul>
}
<div>
</div>
<Grid
ref={(ref) => this.theGrid = ref}
cellRenderer={this.cellRenderer}
columnCount={7}
columnWidth={75}
height={quoteList.length * 20}
rowCount={quoteList.length}
rowHeight={20}
width={800}
/>
</div>
);
}
}
export default App;
When quoteList is updated, its length will be updated accordingly and these changes will be passed to the child component Grid
EDIT
As you do not understand why does this code run, I will explan a little bit. If you don't know about React lifecycle, please take a look at this illustration
quoteList is initialized as an empty array in constructor
render is called the first time. cellRenderer will be bound to this component, or this point to App component.
After component is mounted, componentDidMount will be triggered. Here is when we should fetch data from API. Within the function block (data => {...}) , this is pointing to the function itself. So before fetching the API, I have to assign _this = this to be able to call setState.
setState is executed, render will be triggered again and since quoteList is changed. Your App will be rendered differently according to new quoteList. Means the new quoteList now is passed to your Grid. Grid also re-render.
With this flow, you don't need to forceUpdate.
After much experimentation and some online docs, I was able to solve it by putting this.theGrid.forceUpdate like this. There are no warnings, and render and rendercell are kept "pure"
componentDidMount() {
const socket = socketIOClient(endpoint);
socket.on("FromAPI", data =>
{
this.setState({ response: data });
this.theGrid.forceUpdate();
}
);
}

react-virtualized Infinite scroller issues with dynamic height

I am trying to mock Facebook feed like scroller in my application with react-virtualized. I am following the reference from here. I am trying to load two feeds at a time and after that loadMoreRows would be called to fetch the next two. I have hardcoded my feed size to 10 for testing purpose. It works well till 4th feed. Then I am not able to move after that smoothly. rowRenderer is triggering the numbers again and again which results in vibration effect on the screen. If I somehow move to the 10th feed and I scroll back, rowRenderer starts from 0 again. I assume it is due to varying height. Similar to the reference, I have used CellMeasurerCache and CellMeasurer to find the dynamic height and width and passing that to list.
class Scroller extends React.Component {
_cache = new CellMeasurerCache({ defaultHeight: 100, fixedWidth: true });
_resizeAllFlag = false;
_mostRecentWidth = 0;
constructor(props) {
super(props);
this.state = {
localCache: []
}
}
componentDidMount(){
this._loadData(0);
}
componentDidUpdate(prevProps, prevState) {
console.log(this._list);
if(this._resizeAllFlag){
this._resizeAllFlag = false;
this._cache.clearAll();
this._recomputeRowHeights();
} else if(this.state.localCache !== prevState.localCache) {
this._cache.clear(index, 0);
this._recomputeRowHeights(index);
}
}
._loadData = (offset, callback) => {
//Loads data from server and sets it in this.state.localCache
}
_recomputeRowHeights = (index) => {
if (this._list) {
console.log('Recomputing');
this._list.recomputeRowHeights(index);
}
}
_isRowLoaded = ({ index }) => {
return !!this.state.localCache[index];
}
_loadMoreRows = ({ startIndex, stopIndex }) => {
this._loadData(startIndex, (() => promiseResolver));
let promiseResolver;
return new Promise((resolve) => {
promiseResolver = resolve;
});
}
rowRenderer = ({ index, key, style, parent }) => {
const row = this.state.localCache[index];
let content;
if (row) {
content = (<Feed data={row}/>);
} else {
content = (<CustomLoader />);
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
{content}
</CellMeasurer>);
}
_setListRef = (ref) => {
this._list = ref;
this._registerList(ref);
};
_resizeAll = () => {
this._resizeAllFlag = false;
this._cache.clearAll();
if (this._list) {
this._list.recomputeRowHeights();
}
};
render() {
const { localCache } = this.state;
return (
<div className="flex_grow">
<InfiniteLoader
isRowLoaded={this._isRowLoaded}
loadMoreRows={this._loadMoreRows}
rowCount={10}
>
{({ onRowsRendered, registerChild }) =>
<AutoSizer disableHeight>
{({ width, height }) => {
if (this._mostRecentWidth && this._mostRecentWidth !== width) {
this._resizeAllFlag = true;
setTimeout(this._resizeAll, 0);
}
this._mostRecentWidth = width;
this._registerList = registerChild;
return (
<List
deferredMeasurementCache={this._cache}
overscanRowCount={1}
ref={this._setListRef}
height={height}
onRowsRendered={onRowsRendered}
rowCount={10}
rowHeight={this._cache.rowHeight}
rowRenderer={this.rowRenderer}
width={width}
/>
)
}
}
</AutoSizer>}
</InfiniteLoader>
</div>
);
}
}
Update
I might have deleted style props in content that is being passed. As per #Adrien's suggestion, I added it. My issues were not solved after adding style props.
rowRenderer = ({ index, key, style, parent }) => {
const row = this.state.localCache[index];
let content;
if (row) {
content = (<Feed style={style} data={row}/>);
} else {
content = (<CustomLoader style={style}/>);
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
{content}
</CellMeasurer>);
}
And my Feed component
class Feed extends React.Component {
constructor(props) {
super(props);
}
render() {
const { style } = this.props;
return (
<div className="flex_grow" style={style}>
{/* Feed related JSX */}
</div>
);
}
}
My components seems to be overlapping. What could have been wrong?
AnswerGist:
https://gist.github.com/beb4/cc91f4e9b8982d172613cff248090769
You forgot to pass the rowRenderer style in your content component. According to the docs, this style is mandatory to position your row.
Corrected rowRenderer
rowRenderer = ({ index, key, style, parent }) => {
const row = this.state.localCache[index];
let content;
if (row) {
content = (<Feed data={row} style={style} />); // <== HERE
} else {
content = (<CustomLoader style={style} />); // <== AND HERE
}
return (
<CellMeasurer
cache={this._cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
width={this._mostRecentWidth}
>
{content}
</CellMeasurer>
);
}

Resources