When button is clicked, previous state is not updated - reactjs

I have this React Class component where I cannot get the Button text to change from on to off
import React from 'react';
import ReactDOM from 'react-dom';
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
on: true
}
}
handleClick = () => {
// todo
this.setState((prevState) => {
on: !prevState.on
})
}
render() {
const {
on
} = this.state;
return ( <
button onClick = {this.handleClick} >
{
on ? "On" : "Off"
} < /button>
);
}
}
ReactDOM.render( <
Toggle / > ,
document.getElementById('root')
);
I thought every time the button is clicked it would it would see the previous state and it would update.

You are just missing some parenthesis. If you want to implicitly return an object, you must put them.
handleClick = () => {
this.setState(prevState => ({
on: !prevState.on
}));
};
Check result here

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;

ReactJS Toggling Props Length

I have a show/hide functionality that I am building out for a component and I am wondering how I can toggle the length of a string from 5 characters to its full length and back based on a button click and the previous state. I have the button click and a boolean indicating a true/false state, but I am not sure how I can switch between the 5 character limit and full length. I can only get the expanded text and not the original state.
Based on a state boolean (showFullText) I thought of this solution:
if (this.state.showFullText == false){
partialText = this.props.discovery.slice(0, this.state.characterLimit);
} else {
partialText = this.props.discovery;
}
but it is not working within the context of this code. No error message.
import React from 'react';
//Annotation Card - Body
export default class Body extends React.Component {
constructor(props){
super(props);
this.state = { showFullText: false, characterLimit: 10 };
this.showHideFullText = this.showHideFullText.bind(this);
}
showHideFullText(){
console.log(this.state.showFullText);
this.setState({
showFullText: !this.state.showFullText,
characterLimit: this.props.discovery.length,
expandButtonText: "Show Less"
});
}
render() {
var partialText;
if (this.state.showFullText == false){
partialText = this.props.discovery.slice(0, this.state.characterLimit);
} else {
partialText = this.props.discovery;
}
var textExpandButton;
if(this.props.discovery.length >= this.state.characterLimit) {
textExpandButton = <TextExpandButton showHide={this.showHideFullText} showFullText={this.state.showFullText} />;
} else {
return this.props.discovery;
}
return (
<div>
<p>{partialText}</p>
{textExpandButton}
</div>
)
}
}
//Annotation Card - Body - Text Expand Link
export const TextExpandButton = props => {
var buttonText;
if(props.showFullText === true){
buttonText = "Show Less";
} else {
buttonText = "Show More...";
}
return <a href="#" onClick={props.showHide}>{buttonText}</a>
}
The approach to use a toggled boolean in state is a great one because of its simplicity.
In Body.showHideFullText, characterLimit is being set to the length of discovery and this.props.discovery.slice(0, this.state.characterLimit) really is this.props.discovery.slice(0, discovery.length).
characterLimit should possibly be a write once value (set to 5 as the initial state of Body component). I'll suggest making this a property of Body and setting its default value to 5. There is no reason to keep it in state with your present requirements for the component.
In Body.showHideFullText, only toggle the boolean value for showFullText
showHideFullText(){
this.setState(
prevState => ({
...prevState,
showFullText: !prevState.showFullText,
})
);
}
I don't find it necessary to store expandButtonText in the component state because its value can be decided from the value for showFullText.
Update: Code for affected components (Run on StackBlitz)
Body.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import TextExpandButton from './TextExpandButton';
class Body extends Component {
constructor(props) {
super(props);
this.state = { showFullText: false };
this.toggleFullTextDisplay = this.toggleFullTextDisplay.bind(this);
}
toggleFullTextDisplay() {
this.setState(prevState => ({
...prevState,
showFullText: !prevState.showFullText,
}));
}
render() {
const { discovery, characterLimit } = this.props;
const { showFullText } = this.state;
return (
<div>
<p>{showFullText ? discovery : discovery.slice(0, characterLimit)}</p>
{discovery.length >= characterLimit && <TextExpandButton showFullText={showFullText} toggleFullTextDisplay={this.toggleFullTextDisplay} />}
</div>
)
}
}
Body.defaultProps = {
characterLimit: 5
};
Body.propTypes = {
characterLimit: PropTypes.number,
discovery: PropTypes.string.isRequired,
};
export default Body;
TextExpandButton.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
const TextExpandButton = props => {
const { showFullText, toggleFullTextDisplay } = props;
const buttonText = showFullText ? "Show Less" : "Show More...";
return (
<a href="#"
onClick={props.toggleFullTextDisplay}
>
{buttonText}
</a>
);
}
TextExpandButton.propTypes = {
showFullText: PropTypes.bool.isRequired,
toggleFullTextDisplay: PropTypes.func.isRequired,
};
export default TextExpandButton;

Reactjs. Counter of renders

How to make counter of renders the child component in parent?
I have 2 components Widget (parent) and Message(child). I passed counter from child to parent and trying to set getting value from child set to state. And I getting err: Maximum update depth exceeded.
There is child component Message:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.changeCount = this.changeCount.bind(this);
this.state = { h: 0, counter: 0 };
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
changeCount = () => {
this.setState(state => ({
counter: ++state.counter
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.changeCount();
this.props.getCount(this.state.counter);
}
render() {
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
There is parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: state.counter
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
What I do wrong?
The answer by #Yossi counts total renders of all component instances. This solution counts how many renderes and re-renders an individual component has done.
For counting component instance renders
import { useRef } from "react";
export const Counter = props => {
const renderCounter = useRef(0);
renderCounter.current = renderCounter.current + 1;
return <h1>Renders: {renderCounter.current}, {props.message}</h1>;
};
export default class Message extends React.Component {
constructor() {
this.counter = 0;
}
render() {
this.counter++;
........
}
}
In order to count the number of renders, I am adding a static variable to all my components, and incrementing it within render().
For Class components:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export class SampleClass extends Component {
render() {
if (__DEV__) {
renderCount += 1;
console.log(`${this.constructor.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
}
For functional Components:
import React from 'react';
import { View, Text } from 'react-native';
let renderCount = 0;
export function SampleFunctional() {
if (__DEV__) {
renderCount += 1;
console.log(`${SampleFunctional.name}. renderCount: `, renderCount);
}
return (
<View>
<Text>bla</Text>
</View>
)
}
The componentDidUpdate is calling this.changeCount() which calls this.setState() everytime after the component updated, which ofcourse runs infinitely and throws the error.
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
// Add a if-clause here if you really want to call `this.changeCount()` here
// For example: (I used Lodash here to compare, you might need to import it)
if (!_.isEqual(prevProps.color, this.props.color) {
this.changeCount();
}
this.props.getCount(this.state.counter);
}

ReactJS. Infinity loop

I'am getting props from child in getCount function. And set it prop into state. Than i try set it in component and get infinity loop. How can i fix that?
There is code of parent component:
import React, { Component } from "react";
import Message from "./Message/Message";
export default class Widget extends React.Component {
constructor(props) {
super(props);
this.state = {
color: {
s: 30,
l: 60,
a: 1
},
counter: 0
};
}
getCount = count => this.setState(state => ({
counter: count
}));
getColor = color => {
console.log(`the color is ${color}`);
};
render() {
const counter = this.state.counter;
return (
<div>
<Message
getColor={this.getColor}
getCount={this.getCount}
color={this.state.color}
>
{undefined || `Hello World!`}
</Message>
{counter}
</div>
);
}
}
child:
import React, { Component } from "react";
export default class Message extends React.Component {
constructor(props) {
super(props);
this.changeColor = this.changeColor.bind(this);
this.state = { h: 0 };
this.counter = 0;
}
changeColor = () => {
this.setState(state => ({
h: Math.random()
}));
};
componentDidUpdate(prevProps) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
render() {
this.counter++;
const { children } = this.props;
const { s, l, a } = this.props.color;
this.color = `hsla(${this.state.h}, ${s}%, ${l}%, ${a})`;
return (
<p
className="Message"
onClick={this.changeColor}
style={{ color: this.color }}
>
{children}
</p>
);
}
}
The problem lies in your Message component.
You are using getCount() inside your componentDidUpdate() method. This causes your parent to re-render, and in turn your Message component to re-render. Each re-render triggers another re-render and the loop never stops.
You probably want to add a check to only run the function if the props have changed. Something like:
componentDidUpdate(prevProps) {
if(prevProps.color !== this.props.color) {
this.props.getColor(this.color);
this.props.getCount(this.counter);
}
}
This will keep the functionality you need, but prevent, not only the infinity-loop, but also unnecessary updates.

Refresh previous screen from notification screen when press back button in react-native

I am new in react-native world. I have two screen 1). Home 2). Notification
I am navigating from Home to Notification screen. When I am in Notification Screen that time when I press back button on that condition I want to refresh Home Screen. Please suggest.
Thanks in Advance!
Could you give us a little bit more details ? Are you using a navigator like react-navigation ?
If you want to trigger a method of the parent component from a child component, you should use props.
So, you can do something like this, if you are managing your view by yourself:
export default class Wrapper extends Component
{
state = {screen: 'Home'}
useNewScreen = screenToUse => this.setState({screen: screenToUse})
reloadHome = () => yourFunctionToRefreshThePage
render = () =>
{
if (this.state.screen === 'Home')
return (<Home goToNotif={() => this.useNewScreen('Notif')} />);
else if (this.state.screen === 'Notif')
return (<Notif onGoBack={() => this.reloadHome()} />);
}
}
class Home extends Component
{
render = () =>
{
return (
<TouchableOpacity onPress={() => this.props.goToNotif()}/>
);
}
}
class Notif extends Component
{
render = () =>
{
return (
<TouchableOpacity onPress={() => this.props.onGoBack()}/>
);
}
}
If you are using react-navigation, you can use the same idea:
You navigate to the new page with a special props
this.props.navigation.navigate('Notif', {
onGoBack: () => this.refresh()
});
And when you want to go back, you can call this method
this.props.navigation.state.params.onGoBack();
this.props.navigation.goBack(null);
I hope this is clear enough :)
Firstly always save navigated key screen to any TempStore.
Secondly set time interval to watch and compare both watching screen key and current screen key to able to call the inserted function from the screen you want to wacth and insert the screen mode to the function event..
OnScreen.js
import React, { Component } from 'react';
import { TempStore } from './helpers';
import {
Text,
View
} from "native-base";
class OnScreen extends Component {
constructor(props) {
super(props);
this.state = {
lastValue:'active'
}}
startWatching=()=>{
if (this.interval) { return; }
this.interval = setInterval(this.checkView, 100);
}
stopWatching=()=>{
this.interval = clearInterval(this.interval);
}
componentDidMount(){
this.startWatching();
}
componentWillUnmount(){
this.stopWatching();
}
checkView =()=> {
const proState = {};
proState.currentRoute=TempStore({type:'get',name:'_currentRoute'})
if(!proState.currentRoute){
proState.currentRoute={routeName:'Home',key:'Home'}
}
if(!this.props.statekey){return false}
var isVisible;
if(this.props.statekey === proState.currentRoute.key){
isVisible='active'
}else{
isVisible='inactive'
}
// notify the parent when the value changes
if (this.state.lastValue !== isVisible) {
this.setState({
lastValue: isVisible
})
this.props.onChange(isVisible);
}
}
render = () => {
return (
<View></View>
);
}
}
export default OnScreen;
react-navigation App.js
.......
const AppNav = AppNavigator(AppNavigators)
function getActiveRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getActiveRouteName(route);
}
return route;
}
export default () =>
<Root>
<AppNav onNavigationStateChange={(prevState, currentState) => {
TempStore({type:'set',name:'_currentRoute',value:getActiveRouteName(currentState)})}}/>
</Root>;
AnyScreenYouWishToWatch.js
import OnScreen from '../../../helper/OnScreen';
......
OnActiveToggle=(str)=> {
if(str==='active'){
alert('active');}
else{
alert('inactive');}
}
.....
<OnScreen statekey={this.props.navigation.state.key} onChange={this.OnActiveToggle}/>

Resources