I am trying to update the state in a method start() and then passing the state's value to MyComponent.
My component works ok, and the state updates ok when I'm setting it within the class, but when I'm trying to pass it from start method - it doesn't work. getting "TypeError: this.setState is not a function"
Edited - fixed binding but something still doesn't work.
What can be the problem?
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
this.start= this.start.bind(this)
}
start() {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
this is MyComponent:
class MyComponent extends Component {
constructor(props){
super(props)
this.state={}
this.state.custum={
backgroundColor: 'red'
}
let intervalid;
if (this.props.value){
setTimeout(() => {
this.setState( {
custum:{
backgroundColor: 'green'
}
})
}, 1000);
setTimeout(() => {
this.setState( {
custum:{
backgroundColor: 'red'
}
})
}, 2000);
}
}
render() {
return (
<View style={[styles.old, this.state.custum]}>
</View>
);
}
}
var styles = StyleSheet.create({
old:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
},
})
export default MyComponent;
You have to bind the context to your function.
constructor (props){
super(props);
this.state = {
val: false
}
this.start = this.start.bind(this)
}
or just you can an arrow function, without binding
start = () => {
this.setState({ val: true })
}
bind start method to the component other 'this' will be undefined for the start function
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
this.start = this.start.bind(this)
}
start() {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
You need to make start function to be binded through constructor or ES6 arrow function.
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
}
start = () => {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
You have to bind the method with this. Just add
this.start = this.start.bind(this)
after this.state in the constructor.
EDIT
And also try to move custom inside state in MyComponent like this:
this.state={
custum: {
backgroundColor: 'red'
}
}
and remove
this.state.custum={
backgroundColor: 'red'
}
As you can't just set state like this.
Related
I have got two React Components:
First:
class Clock extends React.Component {
constructor(props){
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval( () => this.tick(),1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div style={{border:"1px solid black"}}>
<h1 style={{color:"blue"}}> Component Clock has been rendered </h1>
<h2> Time: {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
};
And second:
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'On' : 'Off'}
</button>
);
}
}
In second component it doesn't work until I bind handleClick() to this. But it works perfectly in first case, although, I haven't use bind(). I can't understand what's the matter =(.
It seems that in first component it automatically captures this, how does it happen?
In the first example it only works because you used an arrow function as the setInterval callback which bound the this of the calling function, componentDidMount to be bound to the callback () => this.tick() which is the correct expected this in the tick function when it's called.
In other words, if you had instead done:
componentDidMount() {
this.timerID = setInterval(this.tick, 1000);
}
You'd see the TypeError: this.setState is not a function as the component's this isn't bound to the inner function.
You could also just bind this when passing the callback to `setInterval:
componentDidMount() {
this.timerID = setInterval(this.tick.bind(this), 1000);
}
In the second example the onClick={this.handleClick} handler doesn't work until the component's this has been bound to the handler. This is done in a few ways:
Bound in constructor (just like the good old days)
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
this.handleClick = this.handleClick.bind(this); // <-- here
}
handleClick() {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "On" : "Off"}
</button>
);
}
}
Bound when passing callback
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
}
handleClick() {
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick.bind(this)}> // <-- here
{this.state.isToggleOn ? "On" : "Off"}
</button>
);
}
}
Using arrow function.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
}
handleClick = () => { // <-- here as arrow function
this.setState((prevState) => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "On" : "Off"}
</button>
);
}
}
It seems just recently React won't treat this.props.children as a function as it did in the past.
I just coded a Modal component where its closeModal function should be passed to the children,
render() {
return (
<Modal>
{
(closeModal) => {
return (<span onClick={closeModal}>Click to close modal</span>)
}
}
</Modal>
)
}
Modal looks like this
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children }
</div>
)
}
}
I tried to pass function as a prop via this.props.children({ closeModal: this.closeModal }), guess what, this.props.children is not a function according to latest React 16.9.
As a reference for the folks working with GraphQL, I see Apollo client's <Mutation> and <Query> working quite much the same way.
How can it be achieved?
Edit: Why not a duplicate?
Because other answers rely on this.props.children as function whereas recently React is now rendering error demanding a new approach to this issue:
TypeError: this.props.children is not a function
I've answered the updates needed to show what is wrong and how it can be changed in-line below.
class Modal extends React.Component {
constructor(props) {
// 1️⃣ Make sure to make `props` available in this component.
// This call is what makes `this.props` call to be available within `Modal`.
super(props);
this.state = { show: true };
// 2️⃣ Assign a newly bound method to the matching class method
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false });
}
render() {
if (!this.state.show) return null;
// 3️⃣ Pass the modal handler to children
// If you had kept `this.close`, then when you pass the handler
// "{ closeModal: this.close }" instead of "{ closeModal: this.closeModal }"
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{/* 4️⃣ destructure `closeModal` */}
{({ closeModal }) => {
return <button onClick={closeModal}>Click to close modal</button>;
}}
</Modal>
</div>
);
}
As Emile Bergeron has kindly pointed out, you can pass this.props.children(this.close) instead of an object but I found it easier to read/use.
You can fork and try or run the code snippet below~
Thanks Emile Bergeron for the suggestion in the comment~
class Modal extends React.Component {
constructor(props) {
super(props);
this.state = { show: true };
// this.close = this.closeModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
closeModal() {
this.setState({ show: false }, () => {
console.log(`Modal closed`);
});
}
render() {
if (!this.state.show) return null;
return <div>{this.props.children({ closeModal: this.closeModal })}</div>;
}
}
function App() {
return (
<div className="App">
<Modal>
{({ closeModal }) => {
return (
<button type="button" onClick={closeModal}>
Click to close modal
</button>
);
}}
</Modal>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id="root"></div>
try this one
class Modal extends React.Component {
constructor(props) {
this.state = { show: true }
this.close = this.closeModal.bind(this)
}
closeModal() {
this.setState({ show: false })
}
render() {
if (!this.state.show)
return null
return (
<div>
{ this.props.children(this.close) }
</div>
)
}
}
I have navigation screens in my App.js with one screen rendering custom header as:
const DailyStack = createStackNavigator({
Dashboard,
SalesDashboard: {
screen : SalesDashboard,
navigationOptions:{
header: null,
}
},
StackNavSecond : {
screen: StackNavSecond,
navigationOptions : {
header : <CustomHeader />,
}
},....
Then in my CustomHeader.js file, I have some state data:
class CustomHeader extends Component {
constructor(props) {
super(props)
this.state = {
title:'Regions',
subtitle:'',
oem: ''
}
}
async componentDidMount(){
let car_brand = await AsyncStorage.getItem('brand_name');
let main_oem = await AsyncStorage.getItem('oem_name');
await this.setState({
oem: main_oem,
subtitle: car_brand,
});
console.log(this.state.oem)
}
render() {
console.log(this.state.title)
const {title, subtitle, oem} = this.state;
return (
<View>
<CustomDropdown title={title} subtitle={subtitle} oem={oem} />
</View>
)
}
}
export default withNavigation(CustomHeader);
The prop title is not getting passed to its child component which is getting rendered further in two more screens.
The code for CustomDropdown.js is:
class CustomDropdown extends Component {
constructor(props) {
super(props)
this.state = {
title: '',
oem: '',
subtitle:''
};
}
componentDidMount(){
this.setState({
title:this.props.title,
subtitle: this.props.subtitle,
oem: this.props.oem,
});
console.log(this.state.title, this.state.oem, this.state.subtitle)
}
render() {
return (
<View style={{flexDirection:'row', justifyContent: 'space-between'}}>
.........
</View>
)
}
}
export default withNavigation(CustomDropdown);
When I console this.state.title, it prints but no value for subtitle and oem. I even tried putting console statement inside the callback() of this.setState() but still, no props gets print for oem and subtitle.
Please help to resolve this.
You can use withNavigation when you do not want to deliver components. But your code is deeply nested.
If you want to use it like this, you can change the code like this.
CustomHeader.js
class CustomHeader extends React.Component {
constructor(props) {
super(props)
this.state = {
title:'Regions',
subtitle:'',
oem: ''
}
}
async componentDidMount(){
let car_brand = await AsyncStorage.getItem('brand_name');
let main_oem = await AsyncStorage.getItem('oem_name');
this.setState({
oem: main_oem,
subtitle: car_brand,
});
console.log(this.state.oem)
}
render() {
console.log(this.state.title)
const {title, subtitle, oem} = this.state;
return (
<View>
<CustomDropdown title={title} subtitle={subtitle} oem={oem} />
</View>
)
}
}
export default withNavigation(CustomHeader);
CustomDropdown.js
class CustomDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
oem: props.oem,
subtitle: props.subtitle
};
}
componentDidMount(){
console.log(this.state.title, this.state.oem, this.state.subtitle)
}
render() {
return (
<View style={{flexDirection:'row', justifyContent: 'space-between'}}>
.........
</View>
)
}
}
export default CustomDropdown;
I am trying to make a simple clock that starts and stops on the press of a button. Here I have set a variable equal to a setInterval so that I can clear it later on. But for some reason, it is being called without the button being pressed.
Here the autoInc should have been called ideally when I pressed the "Start" button but it gets called anyway.
import React, { Component } from "react";
import { Text, View, Button } from "react-native";
export default class Counter extends Component {
increaser = () => {
this.setState(prePre => {
return { counter: prePre.counter + 1 };
});
};
constructor(props) {
super(props);
this.state = {
counter: 0,
wantToShow: true
};
autoInc = setInterval(this.increaser, 1000);
}
render() {
if (this.state.wantToShow)
return (
<View>
<Text style={{ color: "red", fontSize: 50, textAlign: "center" }}>
{this.state.counter}
</Text>
<Button title="Start" onPress={this.autoInc} />
</View>
);
}
}
A full react example here, you just have to translate functions in react native.
Create a variable this.interval=null in your constructor, assign to this the interval in the startFn , then just remove it with window.clearInterval(this.interval);
export default class App extends Component {
constructor(props) {
super(props);
this.interval = null;
}
componentDidMount() {}
startFn = () => {
console.log("assign and start the interval");
this.interval = setInterval(this.callbackFn, 1000);
};
stopFn = () => {
console.log("clear interval");
window.clearInterval(this.interval);
};
callbackFn = () => {
console.log("interval callback function");
};
render(props, { results = [] }) {
return (
<div>
<h1>Example</h1>
<button onClick={this.startFn}>start Interval</button>
<button onClick={this.stopFn}>stop Interval</button>
</div>
);
}
}
Codesandbox example here: https://codesandbox.io/s/j737qj23r5
In your constructor, you call setInterval(this.increaser,1000) and then assign its return value to the global property autoInc.
this.autoInc is undefined in your render function.
It looks like
autoInc = setInterval(this.increaser,1000)
is simply incorrectly written and what you want instead is:
this.autoInc = () => setInterval(this.increaser,1000)
I know there's many similar threads but since I have a hard time understanding the answers I figured I might try with my own code and see if I can make any sense of the answers.
I just set this up really simple to test it out. I have an Index file that is opened when I start the app. In the index I have testValue in this.state:
Update:
In SignIn:
constructor(props){
super(props);
this.state={
credentials: {
username: "",
password:"",
}
}
this.navigate = this.props.navigation.navigate;
{...}
this.navigate("main", {
credentials: this.state.username,
});
In main:
constructor(props) {
super(props);
this.params = this.props.navigation.state.params;
this.navigate = this.props.navigation.navigate;
{...}
render() {
console.log(this.params.username);
This would actually log the testValue. All you have to do is to pass the testValue state as a prop in TestIndex component. Like this:
Index.jsx
export default class Index {
constructor(props) {
super(props);
this.state = {
testValue: 'hello'
}
}
render() {
return <TestIndex testValue={this.state.testValue} />
}
}
TestIndex.jsx
export default class TestIndex extends React.Component {
index() {
this.props.navigation.navigate("index")
}
handleClickMain = () => {
this.props.navigation.navigate("index");
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.btn} onPress={() => {
console.log(this.props.testValue) //here's where I want it to log testValue from the index file
this.handleClickIndex()
}
}>
</TouchableOpacity>
</View>
);
}
}