Include TabBarIOS on each page of React native app? - javascript

In my react native application, I define TabBarIOS tabs in a HomePage component. Upon clicking various tabs, functions are called that load components from other classes that are associated with these tabs. However, if I use a navigator within these components to push a new screen, the TabBarIOS disappears (as expected), and I can't figure out how to rework this so it remains visible on all child screens of these components. I've included relevant code; any advice would be greatly appreciated!
TabBarIOS rendering:
render: function() {
return (
<TabBarIOS
tintColor="white"
barTintColor="#cee4dc">
<Icon.TabBarItemIOS
title="PROFILE"
selected={this.state.selectedTab === TABS.profile}
iconName="ios-profile"
selectedIconName="ios-profile"
onPress={() => {
this.setState({
selectedTab: TABS.profile,
});
}}>
{this._renderProfile()}
</Icon.TabBarItemIOS>
<Icon.TabBarItemIOS
title="HOME"
selected={this.state.selectedTab === TABS.home}
iconName="ios-home"
selectedIconName="ios-home"
onPress={() => {
this.setState({
selectedTab: TABS.home,
});
}}>
{this._renderHome()}
</Icon.TabBarItemIOS>
<Icon.TabBarItemIOS
title="FEED"
selected={this.state.selectedTab === TABS.feed}
iconName="ios-feed"
selectedIconName="ios-feed"
onPress={() => {
this.setState({
selectedTab: TABS.feed,
});
}}>
{this._renderFeed()}
</Icon.TabBarItemIOS>
</TabBarIOS>
);
},
Rendering specific tab components:
_renderFeed: function(){
return(
<Feed navigator = {this.props.navigator} />
);
},
_renderHome: function(){
return(
<Home navigator = {this.props.navigator} />
);
},
_renderProfile: function(){
return(
<Profile navigator = {this.props.navigator} />
);
},
Navigator to next component in HomePage upon ListView rowPressed:
rowPressed: function(hunt) {
this.props.navigator.push({
title: "Item",
component: ItemOverview,
passProps: {
item: item,
}
});
},

I think you want Tabbar will show in every view, right?
You need a router for help it more efficient.
Try to use the react-native-router-flux to support you, and tabbar has be supported in this router solution.

If you want the native iOS tab bar to persist on every view then it needs to be higher in the view hierarchy than the navigator, so basically instead of having Navigator -> TabBarIOS you would have a new Navigator in each _render* function making your view hierarchy something like this:
TabBarIOS
Navigator
Feed
Navigator
Home
Navigator
Profile
If you don't think this is a good solution, I'd suggest you take a look at NavigationExperimental. It's poorly documented at the moment, so the examples are probably your best bet.

Related

ReactJS: Warning: Cannot update a component (`x`) while rendering a different component (`y`) with global popup notifier

i've portal which basically notifies a user with a popup if there are some new data.
Now my problem is that on first render (when i reload the page or first render of UseToastComponent with toasts.length>0) i get
Warning: Cannot update a component (`UseToastComponent`) while rendering a different component (`Layout`). To locate the bad setState() call inside `Layout`...
Now i've tried diffrent tecniques but couldn't solve it.
I've UseToastComponent
imported in _app.js like this :
<QueryClientProvider client={queryClient}>
<UseToastComponent settings={{ autoClose: false }} />
{getLayout(<Component {...pageProps} />)}
</QueryClientProvider>
Let's look at my UseToastComponent
return (
<>
{loaded &&
ReactDOM.createPortal(
<div className="mb-6 mr-6">
{toasts.map((toast) => (
<PopupNotification
key={toast.id}
...
...
/>
))}
</div>,
document.getElementById(portalId)
)}
</>
);
Now toasts is global state that is beign updated every x sec in Layout component as it's global
How i update toast (global state) in layout comp
data.data.documents.forEach((doc) => {
addToast({
...
...
});
});
For any more information ask me, thanks
EDIT:
can it be because i update a state in layout?
if (dayjs(data.data.documents[0].createdAt).isAfter(firstTimeCreated)) {
setFirstTimeCreated(data.data.documents[0].createdAt);
}
data.data.documents.forEach((doc) => {
addToast({
...
...
});
});
EDIT 1 : working example https://codesandbox.io/p/sandbox/reverent-monad-76jwg5
In the layout, add the if inside an useEffect:
useEffect(() => {
if (data && data.status === 200 && !isLoading) {
if (data.data.results.length > 0) {
data.data.results.forEach((v) => {
addToast({ circle: v.gender, text: v.name.title });
});
}
}
})
Don't know the exact reason, but with the useEffect, next will wait for the Layout to render, to add a toast, becausing adding a toast makes the useToastComponent rerender, and they cannot rerender at the same time, or you will that error

React: Manage State of Menu Items and Routes

I'm new to React. I have written a simple React application using Semantic UI, with two menu items, each corresponding to a specific page. I'm not sure how to make each page have its own state, and remember it when navigating the menu.
In index.js:
ReactDOM.render(
<Main />,
document.getElementById('root')
);
In Main.jsx:
export default class Main extends Component {
state = {
activePageId: 1
};
handleSelectPage = (e, { id: selectedPageId }) => {
this.setState({ activePageId: selectedPageId })
}
getActivePage() {
return (
<Page id={this.state.activePageId} />
)
}
render () {
return (
<Grid divided padded >
<Grid.Column width={2}>
<Menu vertical inverted pointing fluid>
<Menu.Item
id={1}
name='Page 1'
active={this.state.activePageId === 1}
onClick={this.handleSelectPage}
key={1}
/>
<Menu.Item
id={2}
name='Page 2'
active={this.state.activePageId === 2}
onClick={this.handleSelectPage}
key={2}
/>
</Menu>
</Grid.Column>
<Grid.Column width={14}>
{this.getActivePage()}
</Grid.Column>
</Grid>
);
}
}
Finally, in Page.jsx:
export default class Page extends Component {
state = {
counter: 0
};
increaseCounter = (e) => {
this.setState({ counter: this.state.counter + 1 });
}
render () {
return (
<React.Fragment>
<Header>Page {this.props.id}</Header>
{this.state.counter}
<Button primary content='+' onClick={this.increaseCounter}/>
</React.Fragment>
);
}
}
What I can't figure out:
The page id is passed in the props when creating a Page component in getActivePage(). So, that changes every time I navigate the menu. However, the counter is in the state of the Page. That does not change as I navigate the menu. Am I correct to assume there is only one instance of a Page, which is re-rendered when the props (in this case id) change?
It seems like the only way to have a separate counter for each page is to have some sort global state (in a global object) and read from that when rendering a Page. This would mean, however, that I need to call this.forceUpdate inside Page.increaseCounter to re-render the page without re-clicking the menu item. Is that the React way to go?
I'm also thinking about using routing. But, from my preliminary experiments, it seems that, compared to the current scenario, a new Page would be created for each route (is this right?) with its own state. However, that state gets wiped out when navigating between routes, so I still need to keep a global state object and use forceUpdates or so. Any ideas here?
Thanks!
You have to set the counter state inside your Main.jsx component, and then pass the counter state as props to Page.jsx. I recommend you to use functional components and useState hook. Here you can see an example
https://codesandbox.io/s/recursing-bardeen-8w58z?file=/src/App.js

Material-table: Is it possible to open detailsPanel by default?

I'm trying to create a subtable of the main React Material-Table.
Everything is working properly as it should work, details panel (subtable) is showing on toggle icon press.
Are there any ways to show it opened by default? I mean to remove the toggle icon and show the detailPanel right from the component render?
Here is how my mat-table looks like (I didn't want to insert the whole component code, cause it will be too much code, full code is in the sandbox):
<MaterialTable
icons={tableIcons}
tableRef={tableRef}
columns={tableColumns}
data={tableData}
onRowClick={(evt, selectedRow) =>
setSelectedRow(selectedRow.tableData.id)
}
title="Remote Data Example"
detailPanel={detailSubtable}
options={{
rowStyle: rowData => ({
backgroundColor:
selectedRow === rowData.tableData.id ? "#EEE" : "#FFF"
})
}}
/>
And a link to the Codesandbox
As per knowledge, there is not any proper way or props to achieve this but you can do native DoM manipulation.
Provide custom Icon in DetailPanel icon with some unique ID like this:
DetailPanel: forwardRef((props, ref) => (
<div id="my-id" style={{ display: "none" }}>
<ChevronRight {...props} ref={ref} />
</div>
)),
Now, On componentDidMount find this element and trigger a click event on it and hide parent node like this
useEffect(() => {
const myToggler = document.getElementById("my-id");
if (!!myToggler) {
myToggler.click();
myToggler.parentNode.style.display = "none";
}
}, []);
here is the working sandbox link forked from yours, let me know if I am missing something.
If you see the source code There is a props called defaultExpanded which should work but there is an open issue which is causing the issue of not opening the panel by default.
To make it work (until the issue is fixed), you can imperatively modify the material-table's component the state in the useEffect
Like this
useEffect(() => {
tableRef.current.state.data = tableRef.current.state.data.map(data => {
console.log(data);
data.tableData.showDetailPanel = tableRef.current.props.detailPanel;
return data;
});
}, []);
Working demo of your code
This solution works for any number of rows/detailsPanel.

New react native element is not accessible

I'm working on react-native project (main target is iPhone 6) and got some problems with including new elements in accessibility chain. For some reasons Voice Over does not update when new element appears after re-rendering. Hidden button does not appear in accessibility chain after running showButton() method. It becomes visible, but iOS Voice Over does not see it. The problem occurs only when app does something asynchronously. Here is my code:
export default class SmartView extends Component {
constructor(props) {
super(props)
this.state = {
showButton: false,
}
}
showButton = () => {
setTimeout(() => {
this.setState({ showButton: true })
}, 500)
}
render() {
const { showButton } = this.state
return (
<View style={style.root}>
<Button
onPress={this.showButton}
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Appeared</Text>
</Button>
{showButton && (
<Button
accessibilityRole="button"
accessibilityTraits="button"
accessibilityLabel="appeared"
accessible
simple
>
<Text>Hidden</Text>
</Button>
)}
</View>
)
}
}
So, if I remove setTimeout and do state updating in current js stream, everything work fine. Is there any possibility to make something like VoiceOverReload()?
I use: react-native v0.59.9 and iPhone 6, software version 12.4
Thanks.
Below demo works fine, probably your custom Button component has issues
import React, { useState } from 'react'
import { View, Text, TouchableOpacity } from 'react-native'
export default function Screen () {
const [showButton, setShowButton] = useState(false)
function handleShow () {
setTimeout(() => {
setShowButton(true)
}, 1000)
}
return (
<View style={{ padding: 40 }}>
<TouchableOpacity
onPress={handleShow}
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='This button label is long for demo'
accessible
>
<Text>Appeared</Text>
</TouchableOpacity>
{showButton && (
<TouchableOpacity
accessibilityRole='button'
accessibilityTraits='button'
accessibilityLabel='hidden'
accessible
>
<Text>Hidden</Text>
</TouchableOpacity>
)}
</View>
)
}
If your view is going to update and you need voice over to detect the change faster, the you can add the following trait to the parent view: frequentUpdates. This will be the equivalent of setting "Updates Frequently" on the accessibility properties in XCode, as explained in the following answer: Making dynamically updating content in a UITableView accessible for VoiceOver
This works for ReactNative 0.59, though its deprecated and I don't know how to do it in newer versions of RN.

How do I close a React Native Modal?

I am currently running into an issue where I can open my react-native modal just fine but once it's open I can't seem to close it. I just started using react-native about three weeks ago so I am extremely new to this.
I have tried implementing solutions that I've found online but nothing seemed to work for me. The opening functionality is great and seems to be working perfectly but when it comes to closing the modal none of the things I've tried have seemed to give the modal that ability. I have not been able to find a solid solution for my exact problem anywhere!
This is how I am opening the modal.
constructor(props) {
super(props);
this.state = {
refreshing: false,
display: false
};
}
triggerModal() {
this.setState(prevState => {
return {
display: true
}
});
}
<View>
<Button onPress = { () => this.triggerModal() } title = "Open Modal"></Button>
<DisplayModal display = { this.state.display } />
</View>
This is the modal itself, I am trying to use a button to close it.
import React from 'react'
import { Modal, View, Image, Text, StyleSheet, Button } from 'react-native';
const DisplayModal = (props) => (
<Modal visible={ props.display } animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button title="close" onPress = { () => !props.display }></Button>
</View>
</Modal>
)
export default DisplayModal;
As my familiarity with react-native is limited, it has been difficult wrapping my head around how some aspects of the framework function... I'm probably just making a dumb mistake somewhere in the code.
I appreciate any help with this problem!
You've almost got it, however we can make a few tweaks to get it working as you want.
As your DisplayModal has no state of its own, the state must be controlled by its parent component. So with that in mind we can do the following. Firstly pass an additional prop called closeDisplay to the DisplayModal. We're going to pass a function that sets the display property in state to false.
<DisplayModal
display={this.state.display}
closeDisplay={() => this.setState({display: false})} // <- we are passing this function
/>
Then in our DisplayModal component we are going to call that function to close the modal. So your DisplayModal component should look like this:
const DisplayModal = (props) => (
<Modal
visible={ props.display }
animationType = "slide"
onRequestClose={ this.display }>
<View>
<Button
title="close"
onPress = { () => props.closeDisplay() }> // <- here we call the function that we passed
</Button>
</View>
</Modal>
)
Notice that the onPress function of the Button in the DisplayModal component, we are calling the function closeDisplay(). This function then sets the state in the parent component, which in turn gets passed back down to the DisplayModal component causing it to hide.

Categories