Call a method pass from parent component to child component in React - javascript

I have a parent stateful react component that has a function that will change when an html span is clicked within the child component. I want to pass that method to the child component and call it when the snap is clicked I then want to pass it back up to the parent component and updated the state based on what is passed back up. I am having trouble passing down the method and calling it within the child component...
parent component:
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
dates: [],
workouts: [],
selectedDate: '',
selectedWorkouts: []
}
this.updateDateAndWorkouts = this.updateDateAndWorkouts.bind(this)
axios.defaults.baseURL = "http://localhost:3001"
}
updateDateAndWorkouts = () => {
console.log('clicked')
}
render() {
return (
<div>
<DateBar data={this.state.dates}/>
<ClassList workouts={this.state.selectedWorkouts} updateDate={this.updateDateAndWorkouts}/>
</div>
)
}
This is the child component:
export default function Datebar(props) {
return (
<div>
{props.data.map((day, index) => {
return (
<div key={index}>
<span onClick={props.updateDate}>
{day}
</span>
</div>
)
})}
</div>
)
}
What I want to happen is when the method is called in thechild component, it calls the method that was passed and pass the text within the span div...

You have to actually pass function to child component
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
dates: [],
workouts: [],
selectedDate: '',
selectedWorkouts: []
}
this.updateDateAndWorkouts = this.updateDateAndWorkouts.bind(this)
axios.defaults.baseURL = "http://localhost:3001"
}
updateDateAndWorkouts = () => {
console.log('clicked')
}
render() {
return (
<div>
<DateBar data={this.state.dates} updateDate={this.updateDateAndWorkouts} />
<ClassList workouts={this.state.selectedWorkouts} updateDate={this.updateDateAndWorkouts}/>
</div>
)
}

You have to call that method in child component
props.updateDate()
export default function Datebar(props) {
return (
<div>
{props.data.map((day, index) => {
return (
<div key={index}>
<span onClick={props.updateDate()}>
{day}
</span>
</div>
)
})}
</div>
)
}

Related

ReactJS: passing function to child component results in TypeError

I'm trying to pass a callback function from parent->child, but when the child component is rendered I get the error: TypeError: this.props.setCurrentWindow is not a function.
Parent component where I am trying to pass the function setCurrentWindow
class Parent extends Component {
constructor(props){
super(props);
this.setCurrentWindow = this.setCurrentWindow.bind(this);
}
setCurrentWindow(){
console.log("called")
}
render(){
return(
<Child
setCurrentWindow={this.setCurrentWindow}
/>)}
}
child component where I am trying to call setCurrentWindow
class Child extends Component{
constructor(props){
super(props)
}
render(){
return(
<div
onClick={()=>{this.props.setCurrentWindow()}}>
{this.props.children}
</div>
)}
}
Why is setCurrentWindow not being recognized as a function here?
Please check this example where I only found the difference is to have child element like <div><h1>Hello</h1></div> that was not in your code. other than this everything is working fine. When I click on the div, it writes called in console
export default class Parent extends Component {
constructor(props) {
super(props);
this.setCurrentWindow = this.setCurrentWindow.bind(this);
}
setCurrentWindow() {
console.log("called")
}
render() {
return (
<Child
setCurrentWindow={this.setCurrentWindow}
>
<div>
<h1>Hello</h1>
</div>
</Child>
)
}
}
class Child extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div onClick={() => {
this.props.setCurrentWindow()
}}>
{this.props.children}
</div>
);
}
}
Try this:
parent.jsx:
class Parent extends Component {
// code omitted for brevity
handleSetWindow = () => {
//
};
render() {
return (
<div>
<Child onSetWindow={this.handleSetWindow}
/>
</div>
);
}
}
child.jsx:
class Child extends Component {
render() {
return (
<div>
<button onClick={() => this.props.onSetWindow()} >
Set
</button>
</div>
);
}
}
A stupid answer but the final solution here is that not all instances of my child components were being passed this.setCurrentWindow hence the undefined error. Durrr! Thanks for the responses!

Why child component doesn't get updated in components tree?

I try to pass state to the child, to update the list of objects.
When I add an entry, it's not rendered in the child component.
I also checked that state.contacts actually gets replaced with new array, but it didn't work.
constructor(props) {
this.super(props);
}
removeContact(event) {
this.setState((state) => {
state.contacts = state.contacts.filter((contact) => contact.key !== event.target.key )
return state;
})
}
render() {
return (
<Fragment>
<span>{this.props.contact.name}</span>
<span>{this.props.contact.phone}</span>
<span>{this.props.contact.adress}</span>
<a href="#" onClick={this.removeContact}>X</a>
</Fragment>
)
}
}
class Contacts extends Component {
constructor(props) {
super(props);
this.state = { contacts: props.contacts };
}
render() {
console.log(this.state.contacts); // always displays empty array
return (
<div>
{this.state.contacts.map((contact, index) =>
<div>
<Contact key={index} contact={contact} contacts={this.state.contacts}/>
</div>
)}
</div>
)
}
}
class App extends Component {
state = {
time: new Date(),
name: "",
phone: "",
adress: "",
contacts: []
}
change = (event) => {
let nameOfField = event.target.name;
this.setState({[nameOfField]: event.target.value})
}
// click = () => {
// this.setState((state) => {
// state.time = new Date();
// return state;
// })
// }
addContact = () => {
let name = this.state.name;
let phone = this.state.phone;
let adress = this.state.adress;
this.setState((state) => {
return {contacts: [ ... state.contacts.concat([{name, adress, phone}])]}
});
}
render() {
return (
<div className="App">
<Timestamp time={this.state.time}/>
<Contacts contacts={this.state.contacts}/>
<input name="name" value={this.state.name} onChange={this.change} placeholder="Name"/>
<input name="phone" value={this.state.phone} onChange={this.change} placeholder="Phone"/>
<input name="adress" value={this.state.adress} onChange={this.change} placeholder="Adress"/>
<button onClick={this.addContact}>Add contact</button>
</div>
)
}
}
ReactDOM.render(<App time={Date.now().toString()}/>, document.getElementById('root'));
If values are passed to Components you should render them as props. There is no need to copy into the child component state:
class Contacts extends Component {
render() {
console.log(this.props.contacts); // use props instead of state
return (
<div>
{this.props.contacts.map((contact, index) =>
<div>
<Contact key={index} contact={contact} contacts={this.props.contacts}/>
</div>
)}
</div>
)
}
}
Using this.props is good practice because it allows React to deterministically render (If the same props are passed, the same render result is returned).
You are currently modifying the state in Contacts from it's child component Contact. You can't update a parents state directly from within a child component.
What you could do is create a removeContact function in your Contacts component and pass the entire function down to your Contact component. That way when you call removeContact in your child component, it will actually call it from the parent, modify the parents state, and update all it's children with the new state.

How to move an index of a clicked item to another component that is not a parent?

Expecting effect: click <li> --> take index --> send this index to component Watch.
When I click <li>, I grab the index and move it to theWatch component. However, when I click the second li it returns the index of the one I clicked for the first time. I think this is because it updates this index via componentDidMount. How can I reference this index after componentDidMount?
Todo
class Todo extends Component {
render () {
return (
<div className = "itemTodos" onClick={()=> this.props.selectTodo(this.props.index)}>
</div>
)
}
}
export default Todo;
App
class App extends Component {
constructor(){
super();
this.state {
selectedTodoIndex: index
}
}
selectTodo = (index) => {
this.setState({
selectedTodoIndex: index
})
}
render () {
return (
<div>
<ul>
{
this.state.todos
.map((todo, index) =>
<Todo
key={index}
index={index}
todo={todo}
selectTodo ={this.selectTodo}
/>
)
}
</ul>
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
</div>
)
}
}
export default App;
Watch
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
First of all you you use selectedTodoIndex in
<Watch
selectedTodoIndex = {selectedTodoIndex}
/>
but it not specified in your render code. Add
const {selectedTodoIndex} = this.state;
in render function.
Second, use componentDidUpdate in Watch for update inner state on props update:
class Watch extends Component {
constructor(){
super();
this.state = {
selectIndex: null
}
}
componentDidMount() {
this.setState({
selectIndex: this.props.selectedTodo
});
}
componentDidUpdate (prevProps) {
if (prevProps.selectedTodo !== this.props.selectedTodo)
this.setState({
selectIndex: this.props.selectedTodo
});
}
render () {
return (
<div>
</div>
)
}
}
If i am not wrong your Todo component is in watch??. So Watch component should be like this :
render () {
return (
<div>
<Todo index={this.state.selectedIndex} selectedTodo={this.props.selectedTodoIndex}/>
</div>
)
}
Here i made codesandbox of this code . Feel free to checkout and let me know if you any doubt. Code link : https://codesandbox.io/s/frosty-chaplygin-ws1zz
There are lot of improvements to be made. But I believe what you are looking for is getDerivedStateFromProps lifeCycle method in Watch Component. So the code will be:
getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.selectedTodoIndex !== prevState.selectedTodoIndex) {
return { selectIndex: nextProps.selectedTodoIndex }
}
}
This will check if the selected index has changed in App Component, if yes it will update the state in Watch Component.

Easy communication of image between siblings

I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>

React child component's state is undefined

I have a parent component, PlanList:
class PlanList extends Component {
constructor(props) {
super(props);
this.renderPlans = this.renderPlans.bind(this);
this.planListFilter = <PlanListFilter onChange={this.handleFilterChange.bind(this)} />
}
loadPlans() {
console.log(this.planListFilter);
// returns: Object {$$typeof: Symbol(react.element), key: null, ref: null, props: Object, _owner: ReactCompositeComponentWrapper…}
console.log(this.planListFilter.state);
// returns: undefined
// I expect it to return the state object i defined in the PlanListFilter constructor
// here I would apply the filters to the PlanTable
}
handleFilterChange(event) {
this.loadPlans();
}
render() {
return (
<div className="PlanList">
{this.planListFilter}
<PlanTable />
</div>
)
}
}
and a child component, PlanListFilter:
class PlanListFilter extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
};
this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange(event) {
this.setState({search: event.target.value});
this.props.onChange(event);
}
render() {
return (
<FormControl type="text" placeholder="Search" onChange={this.handleSearchChange} value={this.state.search} />
);
}
}
When changing the text on the FormControl, the onChange property is fired as expected, but in the parent object, the state of the child is undefined. I expect it would be populated with the correct state.
In React data flows in one direction, if your parent should know about changes in the child, you have to pass a handler as a prop to the child, so it will be called from within the child.
class Papa extends React.Component {
constructor(p, c) { super(p, c) }
handleFilterChange(valueFromChild) {
//
}
render() {
return <Child filterHandler={this.handleFilterChange} />
}
}
const Child = ({filterHanlder}) => (
<button onClick={() => filterHandler('valueToParent') } >Click Me</button>
)

Categories