I have a NavBarRouteMapper object that I pass to my navbar. However, in the onpress function of one of the buttons I need to access the state, but I'm not sure how to bind 'this' to the object, since it is a non-function. Relevant code as follows
class app extends Component {
state: {
sideMenuIsOpen: boolean,
};
constructor(props: Object) {
super(props);
this.state = {
sideMenuIsOpen: false,
};
};
static NavigationBarRouteMapper = {
LeftButton(route, navigator, index, navState) {
if (index > 0) {
return (
<SimpleButton
// TODO: Make this work, Menu button needs to go to this
// The problem is here. this.state is undefined
onPress={console.log(this.state)}
customText="Back"
style={styles.navBarLeftButton}
textStyle={styles.navBarButtonText}
/>
);
}
},
RightButton(route, navigator, index, navState) {
// TODO change add button for admins
switch (route.title) {
case "Login":
return null;
default:
return (
<SimpleButton
onPress={() => {
navigator.push({
title: "Profile",
component: Profile,
});
}}
customText="Add"
style={styles.navBarRightButton}
textStyle={styles.navBarButtonText}
/>
);
}
},
Title(route, navigator, index, navState) {
return (
<Text style={styles.navBarTitleText}>{route.title}</Text>
);
},
};
render() {
return (
<SideMenu
menu={<Menu navigate={this.navigate} />}
isOpen={this.state.sideMenuIsOpen}
>
<Navigator
ref="rootNavigator"
initialRoute={{
title: "Login",
component: LoginScene,
navigator: this.refs.rootNavigator,
}}
renderScene = {this.renderScene}
navigationBar={
<Navigator.NavigationBar
// Since this is an object, I can't bind 'this' to it,
// and the documentation calls for it to be passed an object
routeMapper={app.NavigationBarRouteMapper}
style={styles.navBar}
/>
}
/>
</SideMenu>
);
};
}
So you're trying to return the parent's state from the child onClick? If so you can add an onClick which calls the parent onClick that you can pass to the child via props.
var Hello = React.createClass({
getInitialState: function() {
return {test: "testing 1 2 3"};
},
clickMethod: function () {
alert(this.state.test);
},
render: function() {
return <div onClick={this.clickMethod}>Hello {this.props.name}</div>;
}
});
var Child = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
https://jsfiddle.net/reactjs/69z2wepo/
Also if you had a list of components where each component needs to pass a unique piece of data to the parent you can do so using bind.
var Hello = React.createClass({
getInitialState: function() {
return {test: "testing 1 2 3"};
},
clickMethod: function (argument) {
alert(argument);
},
render: function() {
return <div onClick={this.clickMethod.bind(this, "custom argument")}>Hello {this.props.name}</div>;
}
});
var Child = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
https://jsfiddle.net/chrshawkes/64eef3xm/1/
Related
I'm making my first step in ReactJS and trying to understand communication between parent and children.
I'm making form, so I have the component for styling fields. And also I have parent component that includes field and checking it. Example:
var LoginField = React.createClass({
render: function() {
return (
<MyField icon="user_icon" placeholder="Nickname" />
);
},
check: function () {
console.log ("aakmslkanslkc");
}
})
var MyField = React.createClass({
render: function() {
...
},
handleChange: function(event) {
// call parent!
}
})
Is there any way to do it. And is my logic is good in reactjs "world"? Thanks for your time.
To do this you pass a callback as a property down to the child from the parent.
For example:
var Parent = React.createClass({
getInitialState: function() {
return {
value: 'foo'
}
},
changeHandler: function(value) {
this.setState({
value: value
});
},
render: function() {
return (
<div>
<Child value={this.state.value} onChange={this.changeHandler} />
<span>{this.state.value}</span>
</div>
);
}
});
var Child = React.createClass({
propTypes: {
value: React.PropTypes.string,
onChange: React.PropTypes.func
},
getDefaultProps: function() {
return {
value: ''
};
},
changeHandler: function(e) {
if (typeof this.props.onChange === 'function') {
this.props.onChange(e.target.value);
}
},
render: function() {
return (
<input type="text" value={this.props.value} onChange={this.changeHandler} />
);
}
});
In the above example, Parent calls Child with a property of value and onChange. The Child in return binds an onChange handler to a standard <input /> element and passes the value up to the Parent's callback if it's defined.
As a result the Parent's changeHandler method is called with the first argument being the string value from the <input /> field in the Child. The result is that the Parent's state can be updated with that value, causing the parent's <span /> element to update with the new value as you type it in the Child's input field.
2019 Update with react 16+ and ES6
Posting this since React.createClass is deprecated from react version 16 and the new Javascript ES6 will give you more benefits.
Parent
import React, {Component} from 'react';
import Child from './Child';
export default class Parent extends Component {
es6Function = (value) => {
console.log(value)
}
simplifiedFunction (value) {
console.log(value)
}
render () {
return (
<div>
<Child
es6Function = {this.es6Function}
simplifiedFunction = {this.simplifiedFunction}
/>
</div>
)
}
}
Child
import React, {Component} from 'react';
export default class Child extends Component {
render () {
return (
<div>
<h1 onClick= { () =>
this.props.simplifiedFunction(<SomethingThatYouWantToPassIn>)
}
> Something</h1>
</div>
)
}
}
Simplified stateless child as ES6 constant
import React from 'react';
const Child = (props) => {
return (
<div>
<h1 onClick= { () =>
props.es6Function(<SomethingThatYouWantToPassIn>)
}
> Something</h1>
</div>
)
}
export default Child;
You can use any parent methods. For this you should to send this methods from you parent to you child like any simple value. And you can use many methods from the parent at one time. For example:
var Parent = React.createClass({
someMethod: function(value) {
console.log("value from child", value)
},
someMethod2: function(value) {
console.log("second method used", value)
},
render: function() {
return (<Child someMethod={this.someMethod} someMethod2={this.someMethod2} />);
}
});
And use it into the Child like this (for any actions or into any child methods):
var Child = React.createClass({
getInitialState: function() {
return {
value: 'bar'
}
},
render: function() {
return (<input type="text" value={this.state.value} onClick={this.props.someMethod} onChange={this.props.someMethod2} />);
}
});
Using Function || stateless component
Parent Component
import React from "react";
import ChildComponent from "./childComponent";
export default function Parent(){
const handleParentFun = (value) =>{
console.log("Call to Parent Component!",value);
}
return (
<>
This is Parent Component
<ChildComponent
handleParentFun = {(value) => {
console.log("your value -->",value);
handleParentFun(value);
}}
/>
</>
);
}
Child Component
import React from "react";
export default function ChildComponent(props){
return(
<>
This is Child Component
<button onClick={props.handleParentFun("Your Value")}>
Call to Parent Component Function
</button>
</>
);
}
Pass the method from Parent component down as a prop to your Child component.
ie:
export default class Parent extends Component {
state = {
word: ''
}
handleCall = () => {
this.setState({ word: 'bar' })
}
render() {
const { word } = this.state
return <Child handler={this.handleCall} word={word} />
}
}
const Child = ({ handler, word }) => (
<span onClick={handler}>Foo{word}</span>
)
React 16+
Child Component
import React from 'react'
class ChildComponent extends React.Component
{
constructor(props){
super(props);
}
render()
{
return <div>
<button onClick={()=>this.props.greetChild('child')}>Call parent Component</button>
</div>
}
}
export default ChildComponent;
Parent Component
import React from "react";
import ChildComponent from "./childComponent";
class MasterComponent extends React.Component
{
constructor(props)
{
super(props);
this.state={
master:'master',
message:''
}
this.greetHandler=this.greetHandler.bind(this);
}
greetHandler(childName){
if(typeof(childName)=='object')
{
this.setState({
message:`this is ${this.state.master}`
});
}
else
{
this.setState({
message:`this is ${childName}`
});
}
}
render()
{
return <div>
<p> {this.state.message}</p>
<button onClick={this.greetHandler}>Click Me</button>
<ChildComponent greetChild={this.greetHandler}></ChildComponent>
</div>
}
}
export default MasterComponent;
I'm passing storyLike function from parent to child.Sometimes when onPress is clicked I get following error: this.props.storyLike(props.story) is not a function.
I thought it's a context issue but couldn't resolve it.
Here is my Parent:
renderRow(item) {
return (
<Story
story={item}
openStory={this.props.openStory}
getProfile={this.props.getProfile}
storyLike={this.props.storyLike}
/>
);
}
Here is child component Story, just passing props to StoryFooter component:
class Story extends Component {
render() {
return (
<View style={styles.container}>
<StoryHeader {...this.props} />
<StoryContent {...this.props} />
<StoryFooter {...this.props} storyViewType={"feed"} />
</View>
);
}
}
Bellow is child component, I'm triggering storyLike from onPress changeLikedStatus function:
class StoryFooter extends Component {
state = { isLiked: false, likes: 0 };
changeLikedState(props) {
const numberOflikes = this.state.isLiked
? this.state.likes - 1
: this.state.likes + 1;
if (this.state.isLiked) {
this.setState({ isLiked: false, likes: numberOflikes });
} else {
this.setState({ isLiked: true, likes: numberOflikes });
}
this.props.storyLike(props.story);
}
<TouchableOpacity onPress={() => this.changeLikedState(this.props)}>
This is how your parent should look like
renderRow(item) {
return (
<Story
// ... other props
storyLike={(prop) => this.props.storyLike(prop)}
/>
);
}
This way you explicitly tell that the child will be passing a prop to the function.
Hopefully your storyLike function is an arrow function that looks like this:
storyLike = (prop) => {
// code
}
I have a react.js component where I want to pass down a bunch of different methods to child components from the parent component, the methods modify the state of the parent component.
class Page extends Component {
constructor(props) {
super(props);
this.state = {
items: this.props.items,
currentItemID: 1
};
this.actions = this.actions.bind(this);
}
render() {
return (
<div className="page">
{
this.state.items.map(item =>
<Item key={item._id} item={item} actions={this.actions} />
)
}
</div>
);
}
actions() {
return {
insertItem: function (itemID) {
const currentItems = this.state.items;
const itemPosition = this.state.items.map((item) => item._id).indexOf(itemID);
const blankItem = {
_id: (new Date().getTime()),
content: ''
};
currentItems.splice(itemPosition + 1, 0, blankItem)
this.setState({
items: currentItems,
lastAddedItemID: blankItem._id
});
},
setCurrentItem: function (itemID) {
this.setState({ currentItemID: itemID });
},
focus: function(itemID) {
return (itemID === this.state.currentItemID);
}
}
}
In my child component, I am trying to use the focus method in the componentDidMount lifecyle method as shown below:
componentDidMount() {
if (this.props.actions().focus(this.props.item._id)) {
this.nameInput.focus();
}
}
However, I am getting the error
Uncaught TypeError: Cannot read property 'currentItemID' of undefined
in the definition of the focus method, within the actions methods. Can anyone point me in the right direction as to why I'm getting the error or an alternative way to pass down multiple actions to child components?
the context is not passed to the function, then the 'this' in the function is that of the function itself and not the component.. you can solve it that way (put the functions in the components):
actions() {
return {
insertItem: this.insertItem.bind(this),
setCurrentItem: this.setCurrentItem.bind(this),
focus: this.focus.bind(this),
}
}
insertItem(itemID) {
const currentItems = this.state.items;
const itemPosition = this.state.items.map((item) => item._id).indexOf(itemID);
const blankItem = {
_id: (new Date().getTime()),
content: ''
};
currentItems.splice(itemPosition + 1, 0, blankItem)
this.setState({
items: currentItems,
lastAddedItemID: blankItem._id
});
},
setCurrentItem(itemID) {
this.setState({ currentItemID: itemID });
},
focus(itemID) {
return (itemID === this.state.currentItemID);
}
but yet, the recomended way is to put the functions in the components like above and remove the actions method and do this:
<Item key={item._id} item={item} actions={{
insertItem: this.insertItem.bind(this),
setCurrentItem: this.setCurrentItem.bind(this),
focus: this.focus.bind(this)
}} />
or
<Item key={item._id} item={item} actions={{
insertItem: () => this.insertItem(),
setCurrentItem: () => this.setCurrentItem(),
focus: () => this.focus()
}} />
I have child components that receive props from a parent, but on an event (button click) in a child I want to setState again with the new props. So the parent passes down all items in a list to the children. In the child prop a button deletes an item in the list. But how can you update the state so the list item is also removed from the view. This is my code:
const Parent = React.createClass({
getInitialState:function(){
return {
items: []
};
},
componentWillMount:function(){
axios.get('/comments')
.then(function(response) {
this.setState({
items: response.data
})
}.bind(this));
},
render() {
return (
<div>
<Child1 data={this.state.items}/>
</div>
);
}
});
export default Parent;
export default function Child1(props){
return(
<div>
{ props.data.map((comment,id) =>(
<p key={id}>
{comment.name}<Delete data={comment.id}/>
</p>
)
)}
</div>
)
}
class Delete extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
Purchase.Action(this.props.data,'remove');
axios.post('/comments', {
item: this.props.data
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
}
render() {
return <Button onClick={this.handleClick}>Delete</Button>;
}
}
module.exports = Delete;
So although the comment is deleted at the server, I want to delete the comment from the component view by updating the state.
If you want to delete the comment from the component, you have to update your Parent state.
In order to do that you can create a new method, delete(id), in your Parent component where you remove the deleted item from the state.
const Parent = React.createClass({
getInitialState:function(){
return {
items: []
};
},
componentWillMount:function(){
this.setState({
items: [
{id: 1,name: "Name 1"},
{id: 2,name: "Name 2"},
{id: 3,name: "Name 3"}
]
})
},
delete(id){
// axios.post(...)
let items = this.state.items.filter(item => item.id !== id);
this.setState({items});
},
render() {
return (
<div>
<Child1
data={this.state.items}
handleClick={this.delete} // Pass the method here
/>
</div>
);
}
});
function Child1(props){
return(
<div>
{ props.data.map((comment,id) =>(
<p key={id}>
{comment.name}
<Delete
data={comment.id}
handleClick={() => props.handleClick(comment.id)} // Pass the id here
/>
</p>
)
)}
</div>
)
}
class Delete extends React.Component {
constructor(props) {
super(props);
}
render() {
return <button onClick={this.props.handleClick}>Delete</button>;
}
}
jsfiddle
I'm using react-router which forces me to use React.cloneElement to pass down properties to my Children. I can pass down objects and functions, but my issue is where one of my functions has a return object back up to the parent, which is always undefined. The function triggers in the parent, but it doesn't receive the object I'm passing it from the child.
Here is a jsFiddle of the below example code if anyone wants to edit it https://jsfiddle.net/conor909/gqdfwg6p/
import React from "react";
import ReactDom from "react-dom";
const App = React.createClass({
render() {
return (
<div>
{this.getChildrenWithProps()}
</div>
)
},
getChildrenWithProps() {
return React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {
myFunction: this.myFunction
});
});
},
// NOTE:
// the idea is that the variable 'newForm' should be sent back up to App, I can log out 'newForm' in the Child, but here in App, it is undefined.
myFunction(newForm) {
console.log(newForm); // => undefined object
}
});
const Child = React.createClass({
propTypes: {
myFunction: React.PropTypes.func,
myForm: React.PropTypes.object
},
render() {
return (
<form className="col-sm-12">
<MyForm
changeForm={this.onChangeForm}
form={this.props.myForm} />
</form>
)
},
onChangeForm(formChanges) {
let newForm = {
...this.props.myForm,
...formChanges
}
// console.log(newForm); => here my newForm object looks fine
this.props.myFunction(newForm);
}
});
const MyForm = React.createClass({
propTypes: {
changeForm: React.PropTypes.func.isRequired
},
render() {
return (
<div>
<Input onChange={this.onChangeForm}>
</div>
)
},
onChangeForm(value) {
this.props.changeForm({ something: value });
}
});