How to call parent function from child prop React 15.5.0 - javascript

Hello I was trying to pass function to child class and call it from the child class but the problem it show the the function is undefined
Cannot read property 'removeComment' of undefined
here is my codes
parent class:
import React from 'react';
import Navbar from './notes';
export default class Board extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
comments: [
'Kingyyy',
'React',
'Learning'
]
};
}
removeComment() {
console.log(i);
var arr = this.state.comments;
arr.splice(i,1);
this.setState({comments: arr});
}
editComment(newtext, i) {
console.log(i);
var arr = this.state.comments;
arr[i] = newtext;
this.setState({comments: arr});
}
addComment() {
var text = prompt('enter the new ');
var arr = this.state.comments;
arr[arr.length] = text;
this.setState({comments: arr});
}
eachComment(elem, i) {
return (
<Navbar key={i} index={i} editComment={(newtext, i) => this.editComment.bind(this)} removeComment={(i) => this.removeComment.bind(this)}>
{elem}
</Navbar>
);
}
render() {
return (
<div>
<button onClick={this.addComment} className="btn btn-success">add new comment</button>
<br />
{
this.state.comments.map(this.eachComment)
}
</div>
);
}
}
the child class:
import React from 'react';
export default class Navbar extends React.Component {
edit() {
this.setState({
edit: !this.state.edit
})
}
save() {
var value = this.refs.newtext.value;
this.props.editComment(value,this.props.index);
this.setState({
edit: !this.state.edit
})
}
remove() {
this.props.removeComment(this.props.index);
}
constructor(props, context) {
super(props, context);
this.state = {edit: false};
}
normal() {
return (
<div>
<h1>{this.props.children}</h1>
<button className="btn btn-info" onClick={this.edit.bind(this)}>
edit
</button>
<button className="btn btn-danger" onClick={this.remove.bind(this)}>
remove
</button>
</div>
);
}
editing() {
return (
<div>
<textarea ref="newtext" defaultValue={this.props.children}></textarea>
<br/>
<button className="btn btn-success" onClick={this.save.bind(this)}>
save
</button>
</div>
);
}
render() {
if (this.state.edit) {
return this.editing();
} else {
return this.normal();
}
}
}

the issue is you are losing your react context. Change your constructor for the child class to this
constructor(props, context) {
super(props, context);
this.state = {edit: false};
this.normal = this.normal.bind(this)
this.editing = this.editing .bind(this)
}
you call .bind(this) on your remove call... however the this you are binding doesn't have the react context with state and props
A few optimizations I would suggest..
define your functions as inline lambdas so you dont have to call .bind(this) on every function every time... aka
edit = () => {
this.setState({
edit: !this.state.edit
})
}
save = () => {
var value = this.refs.newtext.value;
this.props.editComment(value,this.props.index);
this.setState({
edit: !this.state.edit
})
}
remove = () => {
this.props.removeComment(this.props.index);
}
normal = () => {
return (
<div>
<h1>{this.props.children}</h1>
<button className="btn btn-info" onClick={this.edit}>
edit
</button>
<button className="btn btn-danger" onClick={this.remove}>
remove
</button>
</div>
);
}
editing = () => {
return (
<div>
<textarea ref="newtext" defaultValue={this.props.children}></textarea>
<br/>
<button className="btn btn-success" onClick={this.save}>
save
</button>
</div>
);
}
in the parent class change how you pass the functions. Try to always avoid inline lambdas as properties on an element or react class (in the render). It will cause performance issues as the site gets more complex.
eachComment = (elem, i) => {
return (
<Navbar key={i} index={i} editComment={this.editComment} removeComment={this.removeComment}>
{elem}
</Navbar>
);
}
if you needed to pass custom variables to a function that were defined inline you can use .bind to pass them through instead of a lambda (bind is a lot more performant than a lambda... aka
someList.map( (item, i) => <SomeElement onUserClick={this.handleUserClick.bind(null, item) />);
.bind()'s first argument is the context this you can pass null to not override the context. and then you can pass any other arguments to the function to be arguments extended on the invoked call.

Related

Cant render Component in switch statement in React

I have a registration view where in my table i have command to show modal with confirmation:
(...)
render: (rowData) => (
<button
onClick={() => RenderModals(rowData, 'DELETE_USER_MODAL')}
>
Remove
</button>
),
(...)
My RenderModals function looks like this:
type RenderModalProps = {
data: any;
modalCommand: string;
};
export const RenderModals = (data, modalCommand) => {
console.log(data);
switch (modalCommand) {
case 'DELETE_USER_MODAL':
return <DeleteUserModal data={data} />;
case 'SOME_MODAL':
return console.log('some modal');
default:
undefined;
}
};
and I can see console.log(data) in the example above. But... I cant see any console.log from DeleteUserModal component.
DeleteUserModal:
type DeleteUserModalProps = {
data: any;
};
export const DeleteUserModal = ({ data }: DeleteUserModalProps) => {
console.log(`show data ${data}`);
return <div>some text...</div>;
};
I can't figure out what I'm doing wrong ?
Why console.log from DeleteUserModal doesn't trigger?
The way you currently have things set up, this would work:
class RegistrationExampleOne extends React.Component {
constructor(props) {
super(props);
this.state = {component: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({component: RenderModals(rowData, 'DELETE_USER_MODAL')})}>Remove</button>
{this.state.component}
</div>
);
}
}
Option one is not necessarily the better way of doing things, but it is more dynamic.
Option two (as mentioned in the comments by #Brian Thompson) would be similar to this:
import DeleteModal from './path';
class RegistrationExampleTwo extends React.Component {
constructor(props) {
super(props);
this.state = {showDeleteModal: null};
}
render() {
return (
<div>
<button onClick={() => this.setState({showDeleteModal: true})}>Remove</button>
{this.state.showDeleteModal && <DeleteModal data={rowData} />}
</div>
);
}
}

ReactJS this.props.* is not a function when I pass a function to child component

I write messaging app. When I call the passed functions from the child component, I get the following errors:
TypeError: this.props.createNewChat is not a function.
TypeError: this.props.chooseChat is not a function.
I looked through many topics, tried what I could try and nothing worked.
Will be grateful for any suggestions as I'm a beginner in coding.
Here are parts of my code:
Parent component:
class DashboardComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
chats: [],
email: null,
selectedChat: null,
chatVisible: true
}
this.createNewChat = this.createNewChat.bind(this);
this.chooseChat = this.chooseChat.bind(this);
}
render () {
return (
<main className='dashboard-cont'>
<div className='dashboard'>
<ChatListComponent
newChat={this.createNewChat}
select={this.chooseChat}>
history={this.props.history}
chats={this.state.chats}
userEmail={this.state.email}
selectedChatIndex={this.state.selectedChat}>
</ChatListComponent>
</div>
</main>
)
}
createNewChat = () => {
this.setState({
chatVisible: true,
selectedChat: null
});
}
chooseChat = async(index) => {
await this.setState({
selectedChat: index,
chatVisible: true
});
}
Child component:
class ChatListComponent extends React.Component {
constructor(props) {
super(props);
this.select = this.select.bind(this);
this.newChat = this.newChat.bind(this);
}
render () {
if(this.props.chats.length > 0) {
return (
<main className='listOfChats'>
{
this.props.chats.map((_chat, _index) => {
return (
<div key={_index}>
<div className='chatListItem'
onClick={() => this.select(_index)}
selected={this.props.selectedChatIndex === _index}>
<div className='avatar-circle'>
<h1 className='initials'>{_chat.users.filter(_user => _user = this.props.userEmail)[1].split('')[0]}</h1>
</div>
<div className='text'>
<p id='textLine1'>{_chat.users.filter(_user => _user = this.props.userEmail)[1]}</p>
<br></br>
<p>"{_chat.messages[_chat.messages.length - 1].message.slice(0, 25)}..."</p>
</div>
</div>
</div>
)
})
}
<button className='newChatButton'
onClick={this.newChat}>
New Chat</button>
</main>
);
} else {
return (
<button className='newChatButton'
onClick={this.newChat}>
New Chat</button>
);
}
}
newChat = () => {
this.props.createNewChat();
}
select = (index) => {
this.props.chooseChat(index);
}
};
export default ChatListComponent;
You are passing them as newChat and select
<ChatListComponent
newChat={this.createNewChat}
select={this.chooseChat}>
so these are the names of the properties in the ChatListComponent
You should access them as this.props.newChat and this.props.select
newChat = () => {
this.props.newChat();
}
select = (index) => {
this.props.select(index);
}
You should use
this.props.newChat instead of this.props.createNewChat &
this.props.select instead of this.props.chooseChat
because You are passing them as newChat and select
<ChatListComponent
newChat={this.createNewChat}
select={this.chooseChat}>
history={this.props.history}
chats={this.state.chats}
userEmail={this.state.email}
selectedChatIndex={this.state.selectedChat}>
</ChatListComponent>
In child component
newChat = () => {
this.props.newChat();
}
select = (index) => {
this.props.select(index);
}
You don't have such a property in your component
<ChatListComponent
newChat={this.createNewChat}
select={this.chooseChat}>
history={this.props.history}
chats={this.state.chats}
userEmail={this.state.email}
selectedChatIndex={this.state.selectedChat}>
Your property is newChat and not createNewChat
You need to change the button's onClick to call the properties' method
<button className='newChatButton'
onClick={this.props.newChat}>
New Chat</button>
</main>
and
onClick={() => this.props.select(_index)}

Run function in child component after action in parent component

I'm using refs for calculating block height in child component, it works fine inside, so after each removeHandler() function "doCalculating" is called
But if I tried to call this into parent component, doCalculating() always return the initial value. Like just after componentDidMount()
Seems like doCalculating() into parent component refers to this.refs.tagList.clientHeight just once and not recalc even after child component update
React version 14.7 is used here, so I cannot use hooks
class ChildComponent extends Component {
componentDidMount() {
this.doCalculating()
}
doCalculating = () => {
const defaultHeight = 50
const newHeight = this.refs.tagList.clientHeight
if (newHeight > defaultHeight ) {
// do logic
}
}
render() {
return (
<ul
ref={"tagList"}
>
{array.map((item, index) => (
<li key={index}>
<button>
{item}
<span onClick={
(e) => {
this.removeHandler()
this.doCalculating()
}
} ></span>
</button>
</li>
)
)}
</ul>
)
}
}
class ParentComponent extends Component {
actionFunc = () => {
// some logic
// call recalculate function, that always return initial value
this.responsesTags.doCalculating()
}
render() {
return (
<div>
<ChildComponent
ref={instance => { this.responsesTags = instance }}
/>
<button onClick={() => this.actionFunc()} />
</div>
)
}
}
What is missing to recalculate a function when called in the parent component?
In my opinion your code works correctly, I've fiddle with your example (a little different), maybe it will be useful to you: https://jsfiddle.net/tu7vxfym/ . If I calculate height of the ul from child and parent component it will calculate correctly.
class ChildComponent extends React.Component {
constructor(){
super();
this.doCalculating = this.doCalculating.bind(this);
this.addDiv = this.addDiv.bind(this);
this.state = {
list: [],
height:undefined
}
}
componentDidMount() {
this.doCalculating()
}
doCalculating (){
const defaultHeight = 50
const newHeight = this.refs.tagList.clientHeight;
this.setState(state=>{
return state.height = this.refs.tagList.clientHeight
})
console.log(newHeight)
}
addDiv(){
this.setState(function(state){
return state.list.push(this.refs.tagList.clientHeight)
})
}
render() {
return (
<div>
<ul ref={"tagList"}>
{this.state.list.map((e,i)=>{
return (<li key={i}>{e}</li>)
})}
</ul>
<h1>Calculated height: {this.state.height}</h1>
<button onClick={this.addDiv}>Add list</button>
<button onClick={this.doCalculating}>Child button</button>
</div>
)
}
}
class ParentComponent extends React.Component {
constructor(){
super();
this.actionFunc = this.actionFunc.bind(this)
}
actionFunc(){
this.responsesTags.doCalculating()
}
render() {
return (
<div>
<ChildComponent ref={instance => { this.responsesTags = instance }}/>
<button onClick={this.actionFunc}>Parent button</button>
</div>
)
}
}

Handling event on array item won't work - ReactJS

I try to map an array and put click event on the array items. I know it's a bit different because of how JavaScript handles functions but I can't make it work. I get the error: Cannot read property 'saveInStorage' of undefined. Can anyone tell me what I'm doing wrong? Thanks in advance! Here is my code:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
this is undefined in renderUser()
You need to bind this for renderUser() in your constructor.
Also, you are calling saveInStorage() every time the component is rendered, not just onClick, so you'll need to use an arrow function in renderUser
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this);
this.renderUser = this.renderUser.bind(this);
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
);
}
render() {
return (
<div>
{
this.state.users.map(this.renderUser)
}
</div>
);
}
}
Instead of binding you can also use an arrow function (per mersocarlin's answer). The only reason an arrow function will also work is because "An arrow function does not have its own this; the this value of the enclosing execution context is used" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). The enclosing execution in your case is your render, where this is defined.
You need to make two changes to your code which are outlined below.
You are invoking the function when the component is rendered. To fix this update this line to the following
<p key={i} onClick={() => this.saveInStorage(user)}>
This means that the function will only be invoked when you click on the item.
You also need to bind the renderUser in your constructor or else use an arrow function.
this.renderUser = this.renderUser.bind(this);
See working example here.
Your onClick event handler is wrong.
Simply change it to:
onClick={() => this.saveInStorage(user)}
Don't forget to also bind renderUser in your constructor.
Alternatively, you can choose arrow function approach as they work the same as with bind:
class Gebruikers extends React.Component {
constructor(props) {
super(props)
this.state = {
users: [{ id: 1, name: 'user1' }, { id: 2, name: 'user2' }],
}
}
saveInStorage = (e) => {
alert("test")
}
renderUser = (user, i) => {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>
{user.name}
</p>
)
}
render() {
return (
<div>{this.state.users.map(this.renderUser)}</div>
);
}
}
ReactDOM.render(
<Gebruikers />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Paul Fitzgeralds answer is the correct one, although I'd like to propose a different way of handling this, without all the binding issues.
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
}
saveInStorage = (e) => {
console.log("test");
};
render() {
return (
<div>
{this.state.users.map((user, i) => {
return (<p key={i} onClick={() => this.saveInStorage(user)}>f</p>);
})}
</div>
);
}
}
With saveInStorage = (e) => {}; you are binding the saveInStorage function to the this context of your class. When invoking saveInStorage you'll always have the (at least I guess so in this case) desired this context.
The renderUser function is basically redundant. If you return one line of JSX, you can easily do this inside your render function. I think it improves readability, since all your JSX is in one function.
You are not sending the parameters to this.renderUser
this.state.users.map((user, i) => this.renderUser(user, i))
Also your onClick function should be slightly changed. Here's the full code changed:
import React from "react";
const data = require('../data.json');
export default class Gebruikers extends React.Component {
constructor() {
super();
this.state = {
users: data.users
};
this.saveInStorage = this.saveInStorage.bind(this)
}
saveInStorage(e){
console.log("test");
}
renderUser(user, i) {
return(
<p key={i} onClick={() => this.saveInStorage(user)}>f</p>
);
}
render() {
return (
<div>
{
this.state.users.map((user, i) => this.renderUser(user, i))
}
</div>
);
}
}

Unmount component on click in child component button // React

I am struggling with successfully removing component on clicking in button. I found similar topics on the internet however, most of them describe how to do it if everything is rendered in the same component. In my case I fire the function to delete in the child component and pass this information to parent so the state can be changed. However I have no idea how to lift up the index of particular component and this is causing a problem - I believe.
There is a code
PARENT COMPONENT
export class BroadcastForm extends React.Component {
constructor (props) {
super(props)
this.state = {
numberOfComponents: [],
textMessage: ''
}
this.UnmountComponent = this.UnmountComponent.bind(this)
this.MountComponent = this.MountComponent.bind(this)
this.handleTextChange = this.handleTextChange.bind(this)
}
MountComponent () {
const numberOfComponents = this.state.numberOfComponents
this.setState({
numberOfComponents: numberOfComponents.concat(
<BroadcastTextMessageForm key={numberOfComponents.length} selectedFanpage={this.props.selectedFanpage}
components={this.state.numberOfComponents}
onTextChange={this.handleTextChange} dismissComponent={this.UnmountComponent} />)
})
}
UnmountComponent (index) {
this.setState({
numberOfComponents: this.state.numberOfComponents.filter(function (e, i) {
return i !== index
})
})
}
handleTextChange (textMessage) {
this.setState({textMessage})
}
render () {
console.log(this.state)
let components = this.state.numberOfComponents
for (let i = 0; i < components; i++) {
components.push(<BroadcastTextMessageForm key={i} />)
}
return (
<div>
<BroadcastPreferencesForm selectedFanpage={this.props.selectedFanpage}
addComponent={this.MountComponent}
textMessage={this.state.textMessage} />
{this.state.numberOfComponents.map(function (component) {
return component
})}
</div>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastForm))
CHILD COMPONENT
import React from 'react'
import { createContainer } from 'react-meteor-data'
import { withRouter } from 'react-router'
import { BroadcastFormSceleton } from './BroadcastForm'
import './BroadcastTextMessageForm.scss'
export class BroadcastTextMessageForm extends React.Component {
constructor (props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.unmountComponent = this.unmountComponent.bind(this)
}
handleChange (e) {
this.props.onTextChange(e.target.value)
}
unmountComponent (id) {
this.props.dismissComponent(id)
}
render () {
console.log(this.props, this.state)
const textMessage = this.props.textMessage
return (
<BroadcastFormSceleton>
<div className='textarea-container p-3'>
<textarea id='broadcast-message' className='form-control' value={textMessage}
onChange={this.handleChange} />
</div>
<div className='float-right'>
<button type='button'
onClick={this.unmountComponent}
className='btn btn-danger btn-outline-danger button-danger btn-small mr-3 mt-3'>
DELETE
</button>
</div>
</BroadcastFormSceleton>
)
}
}
export default withRouter(createContainer(props => ({
...props
}), BroadcastTextMessageForm))
I am having problem with access correct component and delete it by changing state. Any thoughts how to achieve it?
Please fix the following issues in your code.
Do not mutate the state of the component. Use setState to immutably change the state.
Do not use array index as the key for your component. Try to use an id field which is unique for the component. This will also help with identifying the component that you would need to unmount.
Try something like this. As mentioned before, you don't want to use array index as the key.
class ParentComponent extends React.Component {
constructor() {
this.state = {
// keep your data in state, as a plain object
textMessages: [
{
message: 'hello',
id: '2342334',
},
{
message: 'goodbye!',
id: '1254534',
},
]
};
this.handleDeleteMessage = this.handleDeleteMessage.bind(this);
}
handleDeleteMessage(messageId) {
// filter by Id, not index
this.setState({
textMessages: this.state.textMessages.filter(message => message.id !== messageId)
})
}
render() {
return (
<div>
{this.state.textMessages.map(message => (
// Use id for key. If your data doesn't come with unique ids, generate them.
<ChildComponent
key={message.id}
message={message}
handleDeleteMessage={this.handleDeleteMessage}
/>
))}
</div>
)
}
}
function ChildComponent({message, handleDeleteMessage}) {
function handleClick() {
handleDeleteMessage(message.id)
}
return (
<div>
{message.message}
<button
onClick={handleClick}
>
Delete
</button>
</div>
);
}

Categories