Setting state of a created but not mounted React component - reactjs

I have a react component I'm creating as a local variable. I'd like to tweak its state before attaching it to the DOM. A super-simplified version of the code looks like this:
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {foo: 2};
}
render() {
return <p>{this.state.foo}</p>;
}
}
class App extends React.Component {
render() {
let elem = <Demo/>;
elem.setState({foo:4});
}
}
(The real code has a point, but I'm posting the simplified test case so you don't have to read long irrelevancies)
I'm getting the error
TypeError: elem.setState is not a function
What does this error mean? I'm checked that element is an instance of Demo.
Is there a way to set the state at this time?
ETA: I know what props is. I really want to modify the element after creating it.

You can't setstate like this. If you want to manipulate state in the child component you have to add props to do that.
import React, { Component } from "react";
class Demo extends Component {
state = {
foo: this.props.foo || 0
};
componentDidUpdate(prevState) {
console.log(prevState.foo, this.props.foo);
if (prevState.foo !== this.props.foo) {
this.setState({ foo: this.props.foo });
}
}
render() {
return <button {...this.props}>{this.state.foo}</button>;
}
}
export default class App extends Component {
state = {
count: 0
};
clickMe = () => {
this.setState({
count: this.state.count + 1
});
};
render() {
return <Demo onClick={() => this.clickMe()} foo={this.state.count} />;
}
}
Here is working example https://codesandbox.io/s/festive-rhodes-redk7

Related

React. How to call method of specific subchild in component tree

I'm starting to learn React and wonder how the following theoretical problem can be solved.
Suppose I have such components.
class Game extends React.Component {
constructor(props) {
super(props);
this.state = {
galaxyData:{}
};
}
handleGalaxyCommand(cmd) {
...
}
render() {
return (
<Galaxy galaxyData={this.state.galaxyData} />
);
}
}
class Galaxy extends React.Component {
render() {
return (this.props.galaxyData.sectors.map((sector) =>
<Sector sectorData={sector.sectorData} />
)
);
}
}
class Sector extends React.Component {
render() {
return (this.props.sectorData.ships.map((ship) =>
<Ship shipData={ship.shipData} />
)
);
}
}
class Ship extends React.Component {
constructor(props) {
super(props);
this.state = {
x: this.props.shipData.inialX,
y: this.props.shipData.inialY,
};
}
moveTo(x,y){
...
}
render() {
return <div x={this.state.x} y={this.state.y} id={this.props.shipData.id}/>
}
}
I wrote the code quickly for an example only, so I apologize for any syntax errors.
So the component tree looks something like this.
<Galaxy>
<Sector>
<Ship/>
...
<Ship/>
</Sector>
<Sector>
<Ship/>
...
<Ship/>
</Sector>
</Galaxy>
There may even be thousands of ships.
The ship has a "moveTo" method, which starts the Timer to change the x and y variables in the state, which causes the re-render, the move effect.
Let's assume that the Game component receives the command via the "handleGalaxyCommand" method to make the ship start moving.
How to call the "moveTo" method on a ship that interests me?
This is actually possible in react :) in a very simple way.
But this works only in class-based components (not functional or hooks).
Basically, you can call any child's methods from the parent if you access it's refs
Something like:
class Parent extends Component {
childRef = null;
componentDidMount() {
//via ref you can call it
this.childRef.myCustomMethod();
}
render() {
return <Child ref={ref => this.childRef = ref} />
}
}
class Child extends Component {
myCustomMethod() {
console.log("call me ");
}
render() {
return <div />;
}
}
Check this part of the docs for more details: https://reactjs.org/docs/refs-and-the-dom.html#adding-a-ref-to-a-class-component

Reactjs: Parent function call triggered by a child

So I am building my first react project and stumbled upon following problem:
In my App.js (main application) I got a function and render my components:
class App extends Component {
constructor(props) {
super(props);
this.candidateCounter = 0;
this.setCandidateVote = this.setCandidateVote.bind(this);
}
...
setCounter (name) {
this.candidateCounter++;
console.log(this.candidateCounter);
}
render() {
...
<Candidates setCounter={this.setCounter} />
}
}
The child component Candidates.jsx has another function and thus calls another component:
export class Candidates extends React.Component {
constructor(props) {
super(props);
this.AppProps = props;
}
...
registerVote(name) {
...
this.AppProps.setCounter(name);
}
render() {
...
<MyButton id={this.state.candidates[i].name} register={this.registerVote} />
}
And the last component MyButton.jsx looks like this:
export class MyButton extends React.Component {
constructor(props) {
super();
this.ParentProps = props;
this.state = { active: false }
}
buttonActiveHandler = () => {
this.setState({
active: !this.state.active
});
if (this.state.active === false) {
this.ParentProps.register(this.ParentProps.id);
}
else {
...
}
}
render() {
return (
<Button content='Click here' toggle active={this.state.active} onClick={this.buttonActiveHandler} />
);
}
}
I have successfully debugged that all functions calls are working except when the grandchild MyButton has triggered the registerVote() function in my Candidates module. Logging in this method gets printed but it cannot call this.AppProps.setCounter() from the parent App. I receive the following error:
TypeError: Cannot read property 'setCounter' of undefined
I hope this wasn't too complicated explained, any help is appreciated :)
Simply bind the function in the constructor of the class as #qasimalbaqali stated in his comment.
constructor(props) {
super();
this.registerVote = this.registerVote.bind(this);
}

How to get updated data into constructor in React

// parent class
export default class HomeScreen extends React.Component {
constructor(props){
super(props);
this.state = {
password: '',
username:'',
session: 'mani',
};
}
authentication(){
const self = this;
axios.post('/api/users/login/', {
password: this.state.password,
username: this.state.username
})
.then(function (response) {
console.log(response);
var sessionid=response.headers["set-cookie"][0].split('Secure,').pop().split(';').shift();
self.setState({session: sessionid });
})
.catch(function (error) {
console.log("Invalid username/password");
});
}
render(){
return(<Session sessid={this.state.session}/>);
}
}
export default class Session extends Component {
constructor(props){
super(props);
this.sessionVariable = this.props.sessid;
}
render(){
console.log(this.props.sessid); // here i am getting updated value
console.log("constructor "+this.sessionVariable); // here i can't able to get updated value
return (<View></View>);
}
}
//child class
import React, { Component } from 'react'
import { View, Text } from 'react-native'
export default class Session extends Component {
constructor(props){
super(props);
this.state = {
sessid: this.props.sessid
}
}
componentWillReceiveProps(nextProps){
const { sessid } = nextProps;
if (sessid !== this.state.sessid) {
this.setState({sessid});
}
}
render(){
console.log(this.props.sessid);
console.log("dummy"+this.state.sessid);
return (<View></View>);
}
}
How to get updated data into constructor in React
Can you please give me the quick solution
I need to update the data in constructor . Then only I am able to call the global variable throughout the all components
How to get updated data into constructor in React
Can you please give me the quick solution
I need to update the data in constructor . Then only I am able to call the global variable throughout the all components
Add your props value inside the state and update it using componentWillReceiveProps ' lifecycle.
constructor(props) {
super(props);
this.state = {
sessid: this.props.sessid;
};
}
componentWillReceiveProps(nextProps){
const { sessid } = nextProps;
if(sessid !== this.state.sessid) {
this.setState({sessid});
}
}
You can use states to check that current condition, let me explain it with an example,
This is the constructor with initial state for the data toggle
constructor(props){
super(props);
this.state = {
toggle: true;
}
}
Update the existing state, do this
this.setState({ toggle: false });
Make sure you are using the above code inside an arrow function or else bind the .this
If you want more info comment that below...
The only reason that it doesn't show the updated value is because constructor is called only on the initial mount of the component and hence whatever class variable is set in constructor based on props, it won't be updated if the props change.
Second: setting a state or a class variable which is directly derivable from props is an anti pattern and you should just use props directly in render
export default class Session extends Component {
render(){
console.log(this.props.sessid);
return (
<View>
</View>
)
}
}
Still if you want to update the class variable based on, you can make use of componentWillReceiveProps function like
componentWillReceiveProps(nextProps) {
if(nextProps.sessid !== this.props.sessid) {
this.sessionVariable = nextProps.sessid;
this.forceUpdate(); // called to cause a re-render
}
}
or
export default class Session extends Component {
constructor(props) {
super(props);
this.state = {
sessionVariable: props.sessid
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.sessid !== this.props.sessid) {
this.setState({ sessionVariable: nextProps.sessid});
}
}
render(){
console.log(this.state.sessionVariable);
return (
<View>
</View>
)
}
}
In your constructor define a state:
constructor(props) {
super(props);
this.state = {
sessionVariable: props.sessid;
};
}
Now, in your render():
console.log(props.sessid);
console.log("constructor "+ this.state.sessionVariable);
The constructor for a React component is called only before it is mounted. If you define some variable in constructor, it will store its value not reference and will not be re render again.
To change any value defined in constructor, you need to change it in updating phase of react (ComponentWillReceiveProps) and then call the
this.forceUpdate(); // to render the component
eg.
componentWillReceiveProps(nextProps) {
if(this.props.sessid !== nextProps.sessid) {
this.sessionVariable= nextProps.sessid;
this.forceUpdate();
}
}
Or you can directly use props in render function.

Unable to access child function via ref?

Initially everything was working fine,I have a component something like.
this
class A extends React.Component {
constructor(props) {
super(props);
this.childRef = null
}
componentDidMount() {
this.childRef = this.refs.b
// now I can call child function like this
this.childRef.calledByParent()
}
render(){
<B ref = "b"/>
}
}
In other file
class B extends React.Component {
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default B
till here it was working fine but when I do something like this in class B export default connect(mapStateToProps, mapDispatchToProps)(B)
It is not working. I have imported connect from react-redux
connect() accepts option as the forth parameter. In this option parameter you can set flag withRef to true. After this you can access functions to refs by using getWrappedInstance() like
class A extends React.Component {
constructor(props) {
super(props);
this.childRef = null
}
componentDidMount() {
this.childRef.getWrappedInstance().calledByParent()
}
render(){
<B ref = {ref => this.childRef = ref}/>
}
}
class B extends React.Component {
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default connect(mapStateToProps, mapDispatchToProps, null, {withRef: true})(B)
Might be a little late but another (better) solution than using refs is to only give control to specific functions of the component.
class A extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.ctrl_B.calledByParent()
}
render(){
<B provideCtrl={ctrl => this.ctrl_B = ctrl} />
}
}
class B extends React.Component {
componentDidMount() {
this.props.provideCtrl({
calledByParent: () => this.calledByParent()
});
}
componentWillUnmount() {
this.props.provideCtrl(null);
}
calledByParent(){
console.log("i'm called")
}
render(){
<div> hello </div>
}
}
export default connect(mapStateToProps, mapDispatchToProps)(B)
I had similar problem but I didn't want to make my APIs dependent on getWrappedInstance() calls. In fact some components in your class hierarchy may use connect() and access the store and some others are just stateless components that don't need that additional Redux layer.
I have just written a small (maybe a bit hackish) method. Please note, it hasn't been fully tested yet so expect you may need to make some adjustments to get it working in your own scenario.
TypeScript (should be easy to convert to pure JavaScript syntax):
function exposeWrappedMethods(comp: React.ComponentClass<any>, proto?: any): any {
if (!proto) {
if (comp.prototype.constructor.name === 'Connect') {
// Only Redux component created with connect() is supported
proto = comp.prototype.constructor.WrappedComponent.prototype;
} else {
console.warn('Trying to extend an invalid component.');
return comp;
}
}
let prototypeName: string = proto.constructor.name;
if (prototypeName.search(/^React.*Component.*/) < 0 && proto.__proto__) {
for (let propertyName of Object.getOwnPropertyNames(proto)) {
if (!comp.prototype[propertyName]) {
let type: string = typeof proto[propertyName];
if (type === 'function') {
// It's a regular function
comp.prototype[propertyName] = function (...args: any[]) {
return this.wrappedInstance[propertyName](args);
};
} else if (type === 'undefined') {
// It's a property
Object.defineProperty(comp.prototype, propertyName, {
get: function () {
return (this as any).wrappedInstance[propertyName];
},
set: function (value: any) {
(this as any).wrappedInstance[propertyName] = value;
}
});
}
}
}
return exposeWrappedMethods(comp, proto.__proto__);
}
return comp;
}
Use it by simply wrapping your connect() call with exposeWrappedMethods. It will add all methods and properties from your own class (and subclasses) but will not overwrite already existing methods (i.e. methods from React.Component base class).
export default exposeWrappedMethods(
connect<any, any, Properties>(
(state: ApplicationState) => state.counter,
CounterState.actionCreators,
null,
{ pure: false, withRef: true } // It requires use of "withRef: true"
)(Counter)) as typeof Counter;
Hope you (or someone else) find it useful.
/Lukasz

Child componenet not updating when parent component is updating its array in react

I am having a list of item in parent react component and in which i am added new item and updating items. Child component will receive the items in props and render it.
When parent state is getting updated , child component is not updating its value.
Do i need to update the state in child component state in "componentWillReceiveProps" ? What is the correct way of doing it.
Code Example
// parent component
import React, { Component } from "react";
import TestList from '../controls/testlistview'
export default class TestView extends Component {
constructor(props) {
super();
this.state = {
items: []
};
}
render() {
return (<div>
<button onClick={this.addItem.bind(this)}> Add item</button>
<button onClick={this.changeFirstItemText.bind(this)}> Change item</button>
<TestList items={this.state.items} index={index}/>
</div>);
}
addItem() {
var items = this.state.items.map(s=> s);
items.push('new one');
this.setState({
items: items
});
}
changeFirstItemText() {
var items = this.state.items.map(s=> s);
items[0] = "changed text";
this.setState({
items: items
});
}
}
//Child component
import React, { Component } from "react";
export default class TestList extends Component {
constructor(props) {
super();
debugger;
this.state = {
rootNodes: props.items
};
}
componentWillReceiveProps(nextProps){
debugger;
}
render() {
var items = this.state.rootNodes.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
}
Instead of
render() {
var items = this.state.rootNodes.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
you get the items from props
render() {
var items = this.props.items.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
You don't have to assign props to TestList state again, otherwise you will need to do setState() again from TestList in order to trigger the render again. (which is not necesary step)
http://codepen.io/kossel/pen/ObQLoR
In the TestList class you shouldn't assign the props to the Component's state - this is a surefire way to cause major issues in React, and is the cause of your issue here. See my answer here for why this is a bad idea.
If you change your TestItem to the following, then it should work fine.
export default class TestList extends Component {
constructor(props) {
super();
debugger;
}
componentWillReceiveProps(nextProps){
debugger;
}
render() {
var items = this.props.items.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
}
Reason is you are creating the ui element by the state of child component.
There are two ways of solving this issue:
1. update the state value in componentWillReceiveProps() funtion like this
componentWillReceiveProps(newProps){
this.setState({
rootNodes: newProps.items
});
}
2.Create the ui element directly from the props values like this-
render() {
var uiItems = this.props.items.map((item) => {
return <div>{item}</div>;
});
return (<div>{uiItems}</div>);
}

Resources