React child component unmounting when parent component state is updated - reactjs

I am seeing strange behavior that I don't understand in the following code
export class PlayerListPage extends React.Component<PlayerListPageProps, {toggle: boolean}> {
constructor(props: PlayerListPageProps) {
super(props);
this.state = {
toggle: false
};
}
handleToggle = () => {
this.setState({
toggle: !this.state.toggle
})
}
render() {
return (
<div>
<TextField
key={1}
label={'label'}
value={'value'}
/>
<Button
onClick={this.handleToggle}
>
Click
</Button>
</div>
)
}
}
Every time this toggle method is triggered, and updates the component's state, the TextField component is unmounted/remounted instead of just re-rendering.
I would expect TextField to re-render without unmounting first. Could somebody kindly point out what I am doing wrong?
Some additional context: here is the parent component (which is the root of the app):
#observer
export class RootView extends React.PureComponent {
private rootStore: RootStore = new RootStore();
private appTheme : any = createMuiTheme(DarkTheme);
#observable private player: Player
async componentDidMount() {
await this.rootStore.playerStore.retrievePlayerBios();
this.player = this.rootStore.playerStore.getPlayer('12');
}
render() {
console.log(this.appTheme);
return (
<main>
<MuiThemeProvider theme={this.appTheme}>
<Provider rootStore={this.rootStore}>
{/* {!!this.player && <PlayerStatsView player={this.player}/>} */}
<PlayerListPage/>
</Provider>
</MuiThemeProvider>
</main>
)
}
}

Related

How to access method of the child component from parent in reactjs

I'm trying to call child component from parent component in reactjs using refs.but it throws error saying showModal() is not a function when I tried to call.
//app.js
class app extends Component {
constructor(props) {
super(props);
this.POPUP = React.createRef();
}
showModal(){
this.POPUP.showModal(true);
}
render() {
return (
<React.Fragment>
<span><a onClick={() => this.showModal()}>Show</a></span>
<POPUP onRef={ref => (this.POPUP = ref)}></POPUP>
</React.Fragment >
)
}
}
popup.js
class POPUP extends Component {
showModal(show) {
console.log('showmodal');
}
render() {
console.log(this.props.showModalPopup);
<React.Fragment>
<Modal
position="center">
<div>
//code
</div>
</Modal>
</React.Fragment>
)
}
}
Is there any alternative in nextjs.please help
https://reactjs.org/docs/refs-and-the-dom.html#accessing-refs
First of all if you want to access that POPUP instance you should do
this.POPUP.current.showModal(true);
BTW Your showModal function needs to be bound to the child component if you intend to alter its state.
However, even this is doable - this is usually not the recommended way of doing React.
If you want the parent to decide if showModalPopup should be true, you probably should keep the state inside of your parent component:
class App extends Component {
constructor(props) {
super(props);
this.state = { showModalPopup: false };
this.showModal = this.showModal.bind(this);
}
showModal(){
this.setState({ showModalPopup: true });
}
render() {
return (
<React.Fragment>
<span><a onClick={this.showModal}>Show</a></span>
<POPUP show={this.state.showModalPopup}></POPUP>
</React.Fragment >
)
}
}
const POPUP = ({ show }) => (
<Modal show={show} position="center">
// your content.
</Modal>
)

passing state of child component

So I have a component "itemSelection" and inside of it I map through an api response like this
<div className="row">
{this.state.items.map(i => <Item name={i.name} quantity={i.quantity} />)}
</div>
Here the state of "Item" component
constructor(props){
super(props);
this.state = {
visible: false,
selected: false,
}
}
How could I pass the state of "Item" component to "itemSelection" component?
Sending data back up to your parent component should be done by using props.
Fairly common question, see this post for the long answer.
As according to me, If I understood your question you want to call the state of the child component to the parent component.
//Child.js
import s from './Child.css';
class Child extends Component {
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
export default withStyles(s)(Child);
//Parent.js
class Parent extends Component {
render() {
onClick() {
this.refs.child.getAlert()
}
return (
<div>
<Child ref="child" />
<button onClick={this.onClick.bind(this)}>Click</button>
</div>
);
}
}
Also, you can get the code reference from the link: https://github.com/kriasoft/react-starter-kit/issues/909
This a little tricky but Maybe, its help you solving your problem.
//Parent.js
class Parent extends Component {
component(props) {
super(props)
this.state = {
test: 'abc'
}
}
ParentFunction = (value) => {
this.state.test = value;
this.setState(this.state);
}
render() {
return (
<div>
<Child
test={this.state.test}
ParentFunction={this.ParentFunction}
/>
</div>
);
}
}
//Child.js
import s from './Child.css';
class Child extends Component {
component(props) {
super(props)
this.state = {
test: props.test
}
}
handleChange = () => {
this.state.test = event.target.value;
this.setState(this.state);
this.handleOnSave()
}
handleOnSave = () => {
this.props.ParentFunction(this.state.test);
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} />
</div>
);
}
}
export default withStyles(s)(Child);

Prevent render on the Second Component when state changes for the first

I have written a simple React POC where I have a Main component and two child components. The entire state of the application is maintained in the Main component. when the state changes, the child components are re-rendered when the new state is passed as props.
import * as React from 'react'
import * as ReactDOM from 'react-dom'
class Comp1 extends React.Component<IProps1, any> {
render() {
console.log('going to render Component1')
return (
<React.Fragment>
<input type='text' value={this.props.curtext} onChange={(e) => this.props.handleChange(e.target.value)} />
<button onClick={(e) => this.props.onSave(this.props.curtext)}>save</button>
</React.Fragment>
)
}
}
class Comp2 extends React.Component<IProps2, any> {
render() {
console.log('going to render Component2')
return (
<React.Fragment>
<ul>
{this.props.items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
</React.Fragment>
)
}
}
class Main extends React.Component<any, IState> {
constructor(props: any) {
super(props)
this.state = {
currtext: "",
items: []
}
this.handleChange = this.handleChange.bind(this)
this.onSave = this.onSave.bind(this)
}
handleChange(text: string) {
this.setState({currtext: text})
}
onSave(text: string) {
var copy = this.state.items;
copy.push(text)
this.setState({currtext: "", items: copy})
}
render() {
return (
<React.Fragment>
<Comp1 handleChange={this.handleChange} curtext={this.state.currtext} onSave={this.onSave} />
<Comp2 items= {this.state.items} />
</React.Fragment>
)
}
}
ReactDOM.render(<Main />, document.getElementById("root"))
interface IState {
currtext: string
items: Array<string>
}
interface IProps1 {
handleChange: (text: string) => void,
curtext: string,
onSave: (text: string) => void
}
interface IProps2 {
items: Array<string>
}
The only problem with the app is that when the property "currtext" of the state changes, Component2 is still re-rendered.
what I expected was that curtext changes, then Component1 is rendered and when the items is changed then Component2 re-renders.
but right now I see Component2 being re-rendered on every keystroke in the input text box which changes only the part of the state with which Component2 is not concerned.
Edit:: Based on suggestion below I changed the code of component 2 to
class Comp2 extends React.Component<IProps2, any> {
shouldComponentUpdate(nextProps: IProps2, nextState: any) {
console.log(nextProps.items);
console.log(this.props.items);
if (nextProps.items.toString() === this.props.items.toString()) {
return false
} else {
return true
}
}
render() {
console.log('going to render Component2')
return (
<React.Fragment>
<ul>
{this.props.items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
</React.Fragment>
)
}
}
Now if I enter something in text box and click save. I see that the nextProps.items and this.props.items are always same and therefore my component2 doesn't render at all.
The reason why Comp2 re-renders is because when the parent state changes everything inside of the parents render method will be re-rendered.
class Comp2 extends React.Component<IProps2, any> {
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.currtext) return false;
return true;
}
render() {
console.log('going to render Component2')
return (
<React.Fragment>
<ul>
{this.props.items.map((item, index) => <li key={index}>{item}</li>)}
</ul>
</React.Fragment>
)
}
}
Try adding the shouldComponentUpdate lifecycle hook.
Pass currtext into Comp2 as a prop. If currtext contains a letter the Comp2 will not update. Once you submit and currtext becomes "" empty it will update.
<Comp2 items= {this.state.items} currtext={this.state.currtext} />
Also do not mutate state directly.
onSave(text: string) {
var copy = this.state.items.slice();
copy.push(text)
this.setState({currtext: "", items: copy})
}

Trigger event when the state of child of another component changed in react

Let say I have two components called A and B.
B has a status which keeps changing and I would like to pass the status to A so that I can trigger event when certain condition is met.
How do I keep monitoring/getting state of child from another component in React?
Thanks!
When component on the same level:
class Parent extends React.Component {
constructor() {
super();
this.state = {
status: "B not clicked"
}
this.componentBchangeHandler = this.componentBchangeHandler.bind(this);
}
componentBchangeHandler(value) {
this.setState({ status: value })
}
render() {
return (
<div>
<ComponentA status={this.state.status} />
<ComponentB componentBchangeHandler={this.componentBchangeHandler} />
</div>
)
}
}
const ComponentA = (props) => <div>{props.status}</div>;
const ComponentB = (props) => <div onClick={() => props.componentBchangeHandler("some val")}>click me</div>;
https://codesandbox.io/s/k29mn21pov
And check out the documents I mentioned earlier.
If you're talking about a parent-child relationship, all you'd have to do is define a function that changes state on A and pass it as prop to B, like so:
class A extends Component {
constructor(props) {
super(props);
this.state = {
changed: false,
}
}
_statusChange = () => this.setState({ changed: !this.state.changed });
render() {
return(
<div>
<span>State on A: { this.state.changed.toString() }</span>
<B changeHandler={this._statusChange} />
</div>
)
}
}
class B extends Component {
render() {
return(
<div>
<button onClick={this.props.changeHandler}>Click me</button>
</div>
)
}
}
const App = () => (
<A />
);
If they should be on the same level, by convention, you should define a third component, wrap A and B in it, again passing state as props between them.

How to access component function in a component in reactjs

I have different jsx files.I want to access Menu.jsx component function in Header.jsx function to open menu. I am using Material-UI also. So Here I have a function "handleToggle" in Menu.jsx and I want to trigger this function from a button "onLeftIconButtonTouchTap" which is available in Header.jsx. How can I access component internal function from any other component, should I need to maintain any hierarchy?
App.jsx
export default class App extends React.Component{
render(){
return(
<main>
<Menu/>
<Header/>
<Body/>
<Footer/>
</main>
)
}
}
Header.jsx
export default class Header extends BaseMUI{
render(){
return (
<header>
<AppBar
title="title"
onLeftIconButtonTouchTap={this.handleToggle}
iconClassNameRight="muidocs-icon-navigation-expand-more"
/>
</header>
)
}
}
Menu.jsx
export default class Menu extends BaseMUI{
constructor(props) {
super(props);
this.state = {
open: false
};
}
handleToggle = () => this.setState({open: !this.state.open});
handleClose = () => this.setState({open: false});
componentDidMount(){
console.log(this.refs);
}
render(){
return (
<nav>
<RaisedButton
label="Open Drawer"
onTouchTap={this.handleToggle}/>
<Drawer
docked={false}
width={200}
open={this.state.open}
ref="drawer"
onRequestChange={(open) => this.setState({open})}>
<MenuItem onTouchTap={this.handleClose}>Menu Item</MenuItem>
<MenuItem onTouchTap={this.handleClose}>Menu Item 2</MenuItem>
</Drawer>
</nav>
)
}
}
You need to create parent component, store there state of opening menu, and change props of the menu - here an example, how it can be implemented in only react, without Flux and Redux, will be more right to use them, if you have many idential situations.
class MenuContainer extends React.Component {
constructor(props) {
super(props)
this.state = {
isMenuOpen = false
}
}
shouldComponentUpdate(newProps, newState) {
return newState.isMenuOpen != this.state.isMenuOpen
}
openMenu = (isMenuOpen ) => {
this.setState({isMenuOpen : isMenuOpen });
}
render () {
return (
<Header : isMenuOpen={this.state.isMenuOpen}
openMenu= {this.openMenu}/>
<Menu isMenuOpen={this.state.isMenuOpen})
}
}
And then in your menu component
shouldComponentUpdate(newProps) {
return this.props.isMenuOpen != newProps.isMenuOpen
}
Since you Header.jxs and Menu.jsx are in same hierarchy, you can solve in 2 ways...
1) you can fire a event from Header.jxs and listen for the action in Menu.jsx
2) Using react-redux way, fire the action on click and observer the props changes in Menu.jsx

Resources