I am trying to update state of my parent component through child component via setState. below is my parent component:
Fulllayout
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import Header from '../components/header/header.jsx';
import Customizer from '../components/customizer/customizer';
import { Navbar, NavbarBrand, Collapse } from 'reactstrap';
export const settings = {
navbarbg: 'skin1',
sidebarbg: 'skin6',
logobg: 'skin6'
}
class Fulllayout extends React.Component {
constructor(props) {
super(props);
this.state = {
settings: settings
};
}
render() {
return (
<div id="main-wrapper">
<header data-navbarbg={this.state.settings.navbarbg}>
<Navbar expand="md" className={}></Navbar>
</header>
<div className="page-wrapper d-block"></div>
<Customizer />
</div>
);
}
}
export default Fulllayout;
in parent i have defined one constant settings which is exported. It is also given in the this.state. and in header, there is an attribute data-navbarbg={this.state.settings.navbarbg}.
I wanted to change its value dynamically. So, i have one customizer which is imported in parent as a child. Below is the child component:
customizer
import React from 'react';
import { settings } from '../../layouts/fulllayout';
import update from 'immutability-helper';
class Customizer extends React.Component {
constructor(props) {
super(props);
this.navbarbgChange = this.navbarbgChange.bind(this);
this.state = {
settings: settings
};
}
navbarbgChange(e) {
var skin = e.currentTarget.dataset.navbarbg;
var abc = update(this.state.settings, {
navbarbg: { $set: skin }
});
this.setState({ settings: abc });
}
render() {
return (
<aside className="customizer" id="customizer">
<a className="service-panel-toggle text-white"></a>
<div className="customizer-body pt-3">
<div className="mt-3 border-bottom px-3">
<ul className="theme-color mb-2">
<li><a data-navbarbg="skin1" onClick={this.navbarbgChange}></a></li>
</ul>
</div>
</div>
</aside>
);
}
}
export default Customizer;
From customizer, by clicking on color, i wanted to setState of parent to the value given in data-navbarbg attribute.
If i put this code in parent jsx file, it is working fine but for some reasons, this files should be kept separated.
So, what is missing in my code? or the whole approach is wrong? Thanks.
Is there a reason for navbarbgChange to be defined in Customizer?
You could consider moving navbarbgChange to Fulllayout instead.
That way you can do
<li><a data-navbarbg="skin1" onClick={this.props.navbarbgChange}></a></li>
This will ensure that the Fulllayout has the updated background in its state. This also ensures that there is good separation of concerns since settings is defined in the parent and not the child component
In react you can always pass methods from parent to child. Let's write method in the parent to change the state of the parent from the child like this.
class Fulllayout extends React.Component {
constructor(props) {
super(props);
this.state = {
settings: settings
};
}
// This will be passed to the child component
changeForCustomizerState() {
this.setState({
settings: abc
});
}
changeForHeaderState() {
this.setState({
settings: abc
});
}
render() {
return (
<div id="main-wrapper">
<header chageNavbarbg={this.changeForHeaderState}>
<Navbar expand="md" className={}></Navbar>
</header>
<div className="page-wrapper d-block"></div>
<Customizer chageNavbarbg={this.changeForCustomizerState} />
</div>
);
}
}
Then onClick on the child just call the parent method from the child which is passed from the parent.
render() {
return (
<aside className="customizer" id="customizer">
<a className="service-panel-toggle text-white"></a>
<div className="customizer-body pt-3">
<div className="mt-3 border-bottom px-3">
<ul className="theme-color mb-2">
<li><a data-navbarbg="skin1" onClick={this.props.chageNavbarbg}></a></li>
</ul>
</div>
</div>
</aside>
);
}
The state update can be done in Parent from Child using callbacks. Check below code for better understanding
Fulllayout:
updateState = (skin) => {
this.setState({
settings.navbarbg: skin
});
}
render(){
return(
<div>
<Customizer updateState={this.updateState}
</div>
);
}
And in your customizer
navbarbgChange(e){
const skin = e.currentTarget.dataset.navbarbg;
this.props.updateState(skin);
}
OR
Fulllayout:
updateState = (e) => {
const skin = e.currentTarget.dataset.navbarbg;
this.setState({
settings.navbarbg: skin
});
}
render(){
return(
<div>
<Customizer updateState={this.updateState}
</div>
);
}
Customizer: directly pass parent function to onClick
<li><a data-navbarbg="skin1" onClick={this.props.updateState}></a></li>
Also stop using var, and start using let and const mostly. ECMASCRIPT itself argues to avoid using var because of its window scope.
Related
Hello I'm trying to change the state when I click the button and only when the state changes run createBattle() but the state does not change after I click the button.
At the beginning I set the state to false. The button is in Form.js with an event onClick={this.handleClick}. Then the event handleClick should set the state to true and when the state changes createBattle() in Battle.js should render the table.
Please tell me what am I doing wrong ?
Thanks
App.js
import React from "react";
import Titles from "./Components/Title";
import Form from "./Components/Form";
import Battle from "./Components/Battle";
import "./App.css";
class App extends React.Component{
state = {
startPosition : false
}
render(){
return(
<div>
<header>
<div className="meniu"></div>
</header>
<div className="wrapper">
<div className="main">
<div className="container">
<div className="title-container">
<Titles />
<div className="info">
<Form startPosition={this.state.startPosition} />
</div>
</div>
<div className="form-container">
<Battle startPosition={this.state.startPosition}/>
</div>
</div>
</div>
</div>
</div>
);
}
};
export default App;
Battle.js
import React, {Component} from "react";
import Square from "./Square";
class Battle extends Component{
constructor(){
super();
}
createBattle = () => {
let table=[];
for (let i=1; i<=10; i++){
let children = [];
for (let j=1; j<=10; j++){
children.push(<Square />)
}
table.push(<div className="board-row">{children}</div>)
}
return table;
}
render(){
console.log(this.props);
return(
<div className="center">
{this.startPosition && this.props.createBattle()}
</div>
);
}
}
export default Battle;
Form.js
import React from "react";
class Form extends React.Component{
constructor(){
super();
}
handleClick = () =>{
this.setState({
startPosition: true
});
};
render(){
console.log(this.props);
return(
<div>
<button className="button" onClick={this.handleClick}>START</button>
</div>
);
}
};
export default Form;
The state and props of a given component is not shared across other components.
If you need to communicate between components you mostly have 2 different options :
Move state logic to a common parent and pass this state as props in child components (Note that you may also need to pass some functions to allow to interact with this parent state from the child components)
Use a common state, with a framework like Redux (widely used in complex projects)
React does not support sharing of state or props values.
So you should use any of the following
React Redux
AsyncStorage
I have a requirement where in I am making a reusable modal, for which the structure is as shown below:
--Parent.js
--ModalComponent.js
In parent, I am creating the content for modal on a click,
getModalContent = () => {
let ModalBody = (
<div onClick={this.clickableHandler}> //I want this clickableHandler to trigger in child(modal component)
This is clickable
</div>
);
this.setState({modalContent: ModalBody});
}
render(){
return(
<button onClick={this.getModalContent}>Open modal</button>
<ModalComponent modalContent={this.state.ModalContent}/>
);
}
In child(Modal) component, on click of that div, I want the function clickableHandler to get triggered.
ModalComponent.js----
import React, {Component} from 'react';
class ModalComponent extends Component{
clickableHandler = () => {
console.log('I am clicked');
}
render(){
return (
<div>
{this.props.modalContent}
</div>
);
}
}
But by doing so, I not getting the function clickableHandler() function to get triggered in modal, ie, the child component. Let me know where I am going wrong.
You need to define the clickableHandler in the parent not in the modal. You are registering the event callback in your parent and refer to it as this.clickableHandler. In this case 'this' refers to parent but there is no class method called clickable handler on the parent. I hope it helps.
Since the clickHandler is actually defined inside ModalComponent and you are providing the content from the Parent component, its probably a good idea to make use of renderProps in ModalComponent like
import React, {Component} from 'react';
class ModalComponent extends Component{
clickableHandler = () => {
console.log('I am clicked');
}
render(){
return this.props.children(this.clickableHandler);
}
}
and use it like
getModalContent = (clickableHandler) => {
return (
<div onClick={clickableHandler}>
This is clickable
</div>
);
}
render(){
return(
<button onClick={this.getModalContent}>Open modal</button>
<ModalComponent>
{getModalContent}
</ModalComponent>
);
}
You need to bind the clickHandler function in the parent component. To dynamically change the child components, you can update the Parent state, and pass this needed values down as props. Every time state/props update the component affected will re-render. Give this a go.
Parent Component
class Parent extends React.Component {
constructor () {
super()
this.state = {
modalContent: '',
childComponent: ''
}
this.getModalContent = this.getModalContent.bind(this)
this.clickableHandler = this.clickableHandler.bind(this)
}
getModalContent () {
let ModalBody = (
<div onClick={this.clickableHandler}>
</div>
)
this.setState({modalContent: ModalBody})
}
clickableHandler () {
this.setState({
childComponent: yourData
})
}
render () {
return (
<div>
<button onClick={this.getModalContent}>Open Modal</button>
<ModalComponent modalContent={this.state.modalContent}
childComponent={this.state.childComponent} />
</div>
)
}
}
export default Parent
class ModalComponent extends Component {
render () {
return (
<div>
{this.props.modalContent}
<AnotherChild childBody={this.props.childComponent} />
</div>
)
}
}
export default ModalComponent
Recently I learned how to pass props from one component to another. In my case, from <FileTree> to <TextBox>, as you can see here: https://codesandbox.io/s/y018010qk9
But after, I reorganized the code a bit and now it is possible to see the structure of my React App inside <App> (App.js). I decided to put the <FileTree> and <TextBox> side by side, with Bootstrap.
So, logically I thought that passing props from <App> to <TextBox> would be the same as I did before: From <FileTree> to <TextBox>. Unfortunatelly, it is not the case.
At the moment, this is the code inside <App>:
// Structure of the project
export class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div className="col-md-12">
<SearchEngine />
</div>
<div className="col-md-6">
<FileTree />
</div>
<div className="col-md-6">
<TextBox content={this.props.activeNode} />
</div>
</div>
);
}
}
And here, the code inside <TextBox>:
// TextBox component
export class TextBox extends React.Component {
constructor(props) {
super(props);
this.state = {
content: 'Select A Node To See Its Data Structure Here...'
}
this.showContent = this.showContent.bind(this);
}
showContent (newContent) {
this.setState ({
content: newContent
})
}
componentWillReceiveProps (nextProps) {
this.setState ({
content: nextProps.content
})
}
render() {
return (
<div className="padd_top">
<div className="content_box">{this.state.content}</div>
</div>
);
}
}
export default TextBox;
Just in case, here one can find the <FileTree> component:
// Construction of FileTree
export class FileTree extends React.Component {
constructor(props) {
super(props)
this.state = {
activeNode: null
}
this.setActiveNode = this.setActiveNode.bind(this);
}
setActiveNode(name) {
this.setState({activeNode: name})
}
render() {
return(
<div className="padd_top">{
renderTree(
this.props.root || root,
this.setActiveNode,
this.state.activeNode
)
}
</div>
)
}
}
I'm recently getting to know React.js and I'm very thankful for any advice/clarity you can provide.
Thank you.
You need to use lift state method passing state from child to parent then from parent pass it to the child you want
In your parent component create a constructor with states then create liftStateUp function pass it to the child component that you want to receive the data from
constructor(props) {
super(props)
this.state = {
activeNode: '',
}
}
liftStateUp = (data) =>{
this.setState({ activeNode: data})
}
<div>
<div className="col-md-6">
<FileTree liftStateUp={this.liftStateUp} />
</div>
<div className="col-md-6">
<TextBox content={this.state.activeNode} />
</div>
</div>
Then in file_tree.js FileTree function you need to call liftStateUp function that we created it in the parent component
setActiveNode(name) {
this.setState({ activeNode: name });
this.props.liftStateUp(name);
}
https://reactjs.org/docs/lifting-state-up.html
Props are passed down from the parent component to child component. You need to work with global store so that you can interact with state in different siblings of components. For this, you may use redux.
If your application size is smaller, then you may also try using context api.
Hope, this helps to you.
I would really appreciate if you could help me with that one. Namely, I have a parent component and it gets new data from a child component. In child component array-data is mapped so incoming data into parent component is not one but multiple. I would like to save it inside the list tags to get as many list items as incoming values from child component.
Here's the code:
import React, { Component } from 'react';
import TextEnter from './TextEnter.jsx';
class Terminal extends Component {
constructor(props) {
super(props);
this.state = {
incomingData: '',
};
}
updateParent(val) {
this.setState({
incomingData: <li> {val} </li> // here I would like to save every incoming mapped data
});
}
render() {
return (
<div className="terminal">
<TextEnter
afterCommand={this.props.afterCommand}
triggerParent={(val) => this.updateParent(val)}
/>
<ul className="listContainer">
{this.state.incomingData}
</ul>
</div>
);
}
}
export default Terminal;
I don't really like to store React Elements (created with <li> in a jsx file) as state. I'd rather store the list, and leave how it is rendered to the render() function. I would change it to something like:
import React, { Component } from 'react';
import TextEnter from './TextEnter.jsx';
class Terminal extends Component {
constructor(props) {
super(props);
this.state = {
incomingData: [],
};
}
updateParent(val) {
this.setState({
incomingData: this.state.incomingData.concat(val)
});
}
render() {
const listItems = this.state.incomingData.map(item => {
return <li>{ item }</li>
});
return (
<div className="terminal">
<TextEnter
afterCommand={this.props.afterCommand}
triggerParent={this.updateParent}
/>
<ul className="listContainer">
{listItems}
</ul>
</div>
);
}
}
export default Terminal;
Notice that I also pass this.updateParent directly instead of wrapping it in an anonymous function which should give the same result. You'll get a key warning from react, but I don't know what unique identifier you have access to.
Hello I am trying to implement a very simple functionality that would update my state based on the value passed into a function. The function is declared in my parent component, is passed to my child component via props and it is being called on a child component.
I keep getting this error on the console:
Warning: setState(...): 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.
Here is my code:
Parent component
import React, {Component } from "react";
import Sidebar from './Sidebar';
import Content from './Content';
class Tabs extends Component {
constructor(props){
super(props);
this.state={
message:'Select a name from the tabs menu'
};
this.handleName = this.handleName.bind(this);
}
componentWillMount () {
if ('pluginLoaded' in window) {
(window).pluginLoaded('tabs', function (port: any, context: any) {
// Future work should interact with the message channel here
});
}
}
handleName(value){
if (value === 'Vanessa'){
console.log(`${value} in da house `)
this.setState({
message: 'Vanessa means "butterfly"'
})
}
}
render() {
return (
<div className="Tabs">
<Sidebar
handleName = {this.handleName}
/>
<Content
message = {this.state.message}
/>
</div>
)
}
}
export default Tabs;
Child component
import React from 'react';
const Sidebar = (props) =>{
let Vanessa= 'Vanessa';
let Paola = 'Paola';
return(
<div className="Sidebar">
<h1>Tabs</h1>
<ul>
<li><a onClick={props.handleName(Vanessa)}>Vanessa</a></li>
<li><a onClick={props.handleName(Paola)}>Paola</a></li>
</ul>
</div>
)
}
export default Sidebar;
Instead of:
<li><a onClick={props.handleName(Vanessa)}>Vanessa</a></li>
try:
<li><a onClick={() => props.handleName(Vanessa)}>Vanessa</a></li>