React: Manage State of Menu Items and Routes - javascript

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

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 - Slow state setting for dynamic rendered semantic UI dropdown component

I am new to React and playing around with Semantic UI to improve my skills. However, I am having some trouble with setting initial state and laggy state updates on dropdown change. I'm using the Semantic UI table component with an integrated dropdown following an example from the Semantic Docs here. Essentially I want to render table rows dynamically based on the number of products from a JSON array with each row having its own dropdown element pre-filled with a color from the JSON array . On dropdown change I want the specific index to update its state for the dropdown.
class DropdownExampleSelection extends Component {
constructor(props) {
super(props);
this.state = {
currentValues: {}
};
}
handleDropdownChange = (index, e, { value }) => {
this.setState(({ currentValues }) => {
currentValues[index] = value;
return currentValues;
});
};
render() {
const { currentValues } = this.state;
return (
<Table celled padded>
<Table.Header>
<Table.Row>
<Table.HeaderCell singleLine>Product</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{entries.map((entry, index) => {
return (
<Table.Row key={entry._id}>
<Table.Cell>
<Dropdown
placeholder="Select Color"
selection
options={colorOptions}
value={currentValues[index]}
onChange={this.handleDropdownChange.bind(this, index)}
/>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table>
);
}
}
export default DropdownExampleSelection;
See full code on codesandbox.io
There are two issues I am having:
1. I am not sure the correct way to set the initial state for each dropdown which should be the "color" from each product pre-populated as a dropdown value. See "entrylist.json" file in codesandbox above for the array
{
"_id": 21,
"title": "Product 21",
"color": "red"
}
2. The version in the above code sandbox updates the state at at a particular index, but the UI lags substantially when doing so. There are a good amount of products, but I think this is an issue somewhere in my code because when I use the default dropdown component from Semantic (with no custom state setup) codesandbox example here it does not lag and has the same amount of entries. Appreciate any guidance on this!
If you still look for to improve the performance of this. I have some suggestions.
#depish already said, you have too many Dropdown components and each one is rerendered, even if only 1 dropdown is changed. From this point we come to a question how can we render only Dropdown whose value has been changed?
In order to do that, you need to separate code below
return (
<Table.Row key={entry._id}>
<Table.Cell>
<Dropdown
placeholder="Select Color"
selection
options={colorOptions}
value={currentValues[index]}
onChange={this.handleDropdownChange.bind(this, index)}
/>
</Table.Cell>
</Table.Row>
);
to an outside React.PureComponent, and pass all the parameters which it needs (value, handleDropdownChange). Since PureComponent is rerendered only when there is a different props coming from parent, or state change.
It should solve your issue with getting all 150 dropdowns rendered
Good luck
u can replace your constructor function with this one (let me know your progress)
constructor(props) {
super(props);
this.initalstate= entries.map((entry, index) => {
return entry.color
})
this.state = {
currentValues: {...this.initalstate}
};
}

Stuck While lifting state up in React Native

I'm creating a sample react application for learning purpose, in that I've following hierarchy of separate components:
<RadioButton>
//Radio buttons are rendered here and selection of radio buttons are maintained in state of this component
</RadioButton>
<SelectCard>
<RadioButton dataToPopulate = ['choice A', 'choice B'] />
</SelectCard>
<ParentSelectCard>
<SelectCard /> //Rendering SelectCard with passing some data as prop from here
</ParentSelectCard>
<Button /> //Custom button component
<HomeScreen>
<ParentSelectCard />
<Button />
</HomeScreen>
Now when I press the button, I want to navigate to other screen by passing the selected options in radio buttons.
I've read this article about lifting state up. But the problem is, here there is no common parent ancestor to which I can lift the state to.
If I list the state up to <HomeScreen> component, How can I manage the selections made in <RadioButton> component?
Here is the complete code of <RadioButton> component:
class RadioButton extends React.Component {
constructor(props) {
super(props);
this.state = {
radioSelected: 0
}
}
handleRadioClick(id) {
this.setState({
radioSelected: id
});
}
render(){
return this.props.dataToPopulate.map((element) => {
return (
<View key = {element.id} style={styles.radioButton}>
<TouchableOpacity style={styles.radioButtonTint} onPress = {this.handleRadioClick.bind(this, element.id)}>
{ element.id === this.state.radioSelected ? (<View style={styles.radioButtonSelected}/>) : (null) }
</TouchableOpacity>
<Text style={styles.radioButtonText}> {element.value} </Text>
</View>
);
});
}
}
Here you can see that the final choice made will be stored in the state of this component (in radioSelected).
What I'm missing here? Is my design of <RadioButton> wrong?
What I'm missing here? Is my design of is wrong?
Well if you "lift state up" the radio button itself should not have any state at all. Instead pass down a handler to the RadioButton:
<RadioButton onChange={value => this.doSomethingWith(value)} />
Then you can call that inside of the radio button whenevver something was changed, and handle the results in <App/>.
If you have to pass down that handler through multiple levels it might be better to use a context.

React-virtualized. Calling public methods of List element has no effect

I am trying to use from React-virtualized.
In the following component I am trying to call public methods. The problem is, these methods are called (I see them called while debugging, but they have no visible effect.
import React, {Component} from "react";
import {List, AutoSizer, CellMeasurer, CellMeasurerCache} from "react-virtualized";
class InfiniteScroller extends Component {
constructor(props) {
super(props);
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 50
});
this.state = {
currentLineSt: 50,
currentLineEnd: 100,
}
}
renderRow = ({index, parent, key, style}) => {
let className = "code-line";
if (this.state.currentLineSt <= index && this.state.currentLineEnd >= index) {
className += " code-line-focus";
}
return (
<CellMeasurer
key={key}
cache={this.cache}
parent={parent}
columnIndex={0}
rowIndex={index}
>
<div style={style} className={className}>
<span className={"code-index"}><b>{index}: </b></span>
<span className={"code"} style={{whiteSpace: "pre-wrap"}}>{this.props.data[index]}</span>
</div>
</CellMeasurer>
)
};
componentDidUpdate() {
// these methods are called but do nothing visible
this.myInfiniteList.forceUpdateGrid();
this.myInfiniteList.scrollToRow(100);
}
componentDidMount() {
// these methods are called but do nothing visible
this.myInfiniteList.forceUpdateGrid();
this.myInfiniteList.scrollToRow(100);
}
render() {
return (
<AutoSizer>
{
({width, height}) => {
return <List
ref={(ref) => this.myInfiniteList = ref}
forceUpdateGrid
rowCount={this.props.data.length}
width={width}
height={height}
deferredMeasurementCache={this.cache}
rowHeight={this.cache.rowHeight}
rowRenderer={this.renderRow}
/>
}
}
</AutoSizer>
);
}
}
export default InfiniteScroller;
I need to call them since:
1) After data change, line size does not change
2) Need a way to scroll to line on click.
Any ideas why it doesn't work or how I could do this differently would be greatly appreciated.
You'll have to talk more Brian to get a real understanding, but it appears your list isn't fully initialized on componentDidMount.
Note in this sandbox (took yours and tweaked): https://codesandbox.io/s/lro6358jr9
The log of the element in componentDidMount has an array of 0 for children, whereas the log when I click to do the same thing later has 26 children (and works fine).
I've noticed a lot of weird first load issues in react-virtualized usages (like your list not loading initially). Keep in mind that if you're giving react-virtualized data you expect it to update on, make sure that data is changing a prop somewhere. Otherwise nothing inside will re-render.

Include TabBarIOS on each page of React native app?

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.

Categories