React how to send method to childComponent - javascript

Hi i have trouble to send method from parrent to child in react. I did this before and it works ... Why it no works anymore?
I am rendering this:
<div>
<ScheduleSelectModal colupdate={this.updateColumn.bind(this)} subject={this.state.selectedSubject} subjectslist={this.state.mySubjects} show={this.state.modalShow} onHide={this.changeModalState.bind(this)}>
</ScheduleSelectModal>
</div>
This is my method:
updateColumn(newSubject,dayId){
console.log("tu som");
console.log(this.state.schedule);
}
My modal:
ScheduleSelectModal extends Component {
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("modal props:");
console.log(this.props.subject);
}
update(){
console.log("updating...");
this.props.updatecolumn("test","test");
}
createList() {
let items = [];
if (this.props.subjectslist !== null)
this.props.subjectslist.map(subject =>
items.push(<Button key={subject.id} block className={"my-1"} onClick={this.update.bind(this)}>{subject.name} </Button>)
);
return items;
}
render() {
return (
<Modal
{...this.props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
{this.renderHeader()}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<ButtonGroup vertical className={"w-100"}>
{this.createList()}
</ButtonGroup>
</Modal.Body>
</Modal>
);
}
}
And this is warning i am getting:
index.js:1375 Warning: Invalid value for prop `colupdate` on <div> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.
Really don't know what the issue is. Thx for help

class ParentComponent extends Component {
passedFunction = () => {}
render() {
<ChildComponent passedFunction={this.passedFunction}/>
}
}
class ChildComponent extends Component {
render() {
<div onClick={this.props.passedFunction}></div>
}
}
You can use arrow function to avoid all the bindings. If you want to bind it, bind it in the constructor like so... in the parent component.
constructor() {
this.passedFunction = this.passedFunction.bind(this)
}
<ChildComponent passedFunction={this.passedFunction}/>
I could see that, in your child component you are using :
update(){
console.log("updating...");
this.props.updatecolumn("test","test");
}
but your props for that function is colupdate i.e. you should be using
update(){
console.log("updating...");
this.porps.colupdate("test","test");
}
Hope this helps!

Related

How to pass state value from form to its child modal?

I am new to React and I'm not sure what would be the best approach to take.
I have a modal component to be displayed once the user fills out the values inside the form and click on Preview Voucher to print those values inside the modal.
I tried this code and below I have the Preview Voucher component with a constructor and events.
// PreviewVoucher.js
class PreviewVoucher extends React.Component {
constructor(props) {
super(props);
this.state = {
voucherNumber: "0",
//
addModalShow: false
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSelectChange = this.handleSelectChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(e) {
const target = e.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const inputName = target.name;
this.setState({
[inputName]: value
});
}
handleSelectChange(e) {
this.setState({ labelSize: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
}
}
And I want to render this form on the page that has a child component Modal Voucher
// PreviewVoucher.js
render() {
let addModalClose = () => this.setState({ addModalShow: false });
return (
<form onSubmit={this.handleSubmit}>
<div>
<p>Number of vouchers to create:</p>
<input
min="0"
type="number"
name="voucherNumber"
value={this.state.voucherNumber}
onChange={this.handleInputChange}
/>
</div>
<div>
<h4>Message:</h4>
<p>Some message</p>
<ButtonToolbar>
<Button onClick={() => this.setState({ addModalShow: true })}>
Preview Voucher
</Button>
<ModalVoucher
show={this.state.addModalShow}
onHide={addModalClose}
/>
</ButtonToolbar>
</div>
<input type="submit" value="Create voucher" />
</form>
);
}
And this is the child component - Modal Voucher that would contain some text and would like to display the dynamic values from the Preview Voucher component inside the < Modal Body >.
// ModalVoucher.js
class ModalVoucher extends React.Component {
render() {
return (
<Modal
{...this.props}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title-vcenter">
Voucher preview
</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>{/* update code here */}</div>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.props.onHide}>Close</Button>
</Modal.Footer>
</Modal>
);
}
}
How to render parent's state within child component
You will need to pass the state value from PreviewVoucher into its child ModalVoucher as a prop.
// PreviewVoucher.js
render() {
<ModalVoucher
show={this.state.addModalShow}
onHide={addModalClose}
voucherNumber={this.state.voucherNumber}
/>
}
Then use it inside of ModalVouchers render method.
// ModalVoucher.js
render() {
<Modal.Body>
<div>{this.props.voucherNumber}</div>
</Modal.Body>
}
I put together this sandbox showing this in action.
Other suggestions
After making this change, you might see this error in the console
Warning: React does not recognize the `voucherNumber` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `vouchernumber` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
This occurs because you are passing all the props directly into the Modal, which
the underlying library passes to a DOM element.
<Modal
{...this.props}
Because of this, I think it is usually a bad idea to directly forward all props
and I prefer to pass specific props instead.
<Modal
show={this.props.show}
onHide={this.props.onHide}

How to dynamically render a nested component in React?

I want to render a child element based on the state in its parent. I tried to do the following (simplified version of the code):
class DeviceInfo extends Component {
constructor(props) {
super(props);
this.state = {
currentTab: "General",
};
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
navToggle(tab) {
this.setState({ currentTab: tab });
}
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
render() {
return (
<React.Fragment>
<div className="container">
<Nav className="nav-tabs ">
<NavItem>
<NavLink
className={this.state.currentTab === "General" ? "active" : ""}
onClick={() => {
this.navToggle("General");
}}
>
General
</NavLink>
</div>
{ this.tabsMap[this.state.currentTab] }
</React.Fragment>
);
}
}
But it did not work properly. Only when I put the contents of the tabsMap straight in the render function body it works (i.e. as a react element rather then accessing it through the object). What am I missing here?
Instead of making tabsMap an attribute which is only set when the component is constructed, make a method that returns the object, and call it from render:
getTabsMap() {
return {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
}
};
render() {
...
{ this.getTabsMap()[this.state.currentTab] }
...
}
You defining instance property with this.tabsMap (should be syntax error):
export default class App extends React.Component {
tabsMap = { General: <div>Hello</div> };
// Syntax error
// this.tabsMap = { General: <div>World</div> };
render() {
// depends on props
const tabsMapObj = {
General: <div>Hello with some props {this.props.someProp}</div>
};
return (
<FlexBox>
{this.tabsMap['General']}
{tabsMapObj['General']}
</FlexBox>
);
}
}
Edit after providing code:
Fix the bug in the constructor (Note, don't use constructor, it's error-prone, use class variables).
Moreover, remember that constructor runs once before the component mount if you want your component to be synchronized when properties are changed, move it to render function (or make a function like proposed).
class DeviceInfo extends Component {
constructor(props) {
...
// this.props.id not defined in this point
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={props.id}
/>
</React.Fragment>
}
render() {
// make a function to change the id
this.tabsMap = {
General:
<React.Fragment>
<GeneralCard
id={this.props.id}
/>
</React.Fragment>
};
return (
<>
{ this.tabsMap[this.state.currentTab] }
</>
);
}
}
I think it's a this binding issue. Not sure if your tabsMap constant should have this in front of it.
Alternative answer... you can inline the expression directly in the render as
{ this.state.currentTab === 'General' && <GeneralCard id={this.props.id} /> }

Object is null in JSX tag

So I'm writing an application with a spring boot backend and react.js frontend. I am having an extremely annoying, and basic problem with react, and I'm not a very experienced JS developer...I'm a Java dev.
render() {
console.log(this.videoAreaData)
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>{libs}</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
I get the following output:
{
"libs": [
{
"name": "videos",
"library": null
}
]
}
So then I add
console.log(this.videoAreaData.libs)
and get this following output
[
{
"name": "videos",
"library": null
}
]
So to test printing the first element I should use this.videoAreaData.libs[0] obviously right? Apparently not
TypeError: this.videoAreaData.libs is undefined[Learn More]
What I want to do is iterate over the array in my JSX code using .map, but the object is literally always undefined! I've tried using setState with a const and all kinds of stuff! The data is passed from my parent app.js class which uses this:
componentDidMount() {
this.setState({isLoading: true})
libraryAccessor.listLibraries().then(libs => {
this.setState({ isLoading: false, show: false, libraries: libs })
})
}
libs is then pasted as a parameter into my code using a property like this
<LibraryLister libs={libraries} ref="libraries"></LibraryLister>
then it goes to the constructor of LibraryLister here
videoAreaData
constructor(videoAreaData) {
super(videoAreaData)
this.videoAreaData = videoAreaData
}
Now I assume all of that is done correctly, as it's non-null in my render method. If I put console.log(videoAreaData) within the JSX tags in a {} it's not null either, so it's definitely not supposed to be!
Here is what I finally want to do:
<Modal.Body>
{(this.videoAreaData.libs.map((library)=> {
return <p className="libname"> library.name </p>
}))}
</Modal.Body>
I feel like I'm doing something very very wrong here. That being said I have another project using the exact same stack, but made in typescript, and it works fine doing almost exactly this. Typescript is super irritating to use though, so I'd uh...prefer not to. Thanks in advance for any help
EDIT: Full code https://pastebin.com/daipM2gY
Also this alternate version of my render method prints the data as expected, so it...should not be undefined
render() {
console.log("PRINTING VID DATA: "+this.videoAreaData)
console.log(this.videoAreaData)
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>
{console.log("PRINTIN BODY")}
{console.log(this.videoAreaData)}
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
Output:
PRINTIN BODY
{
"libs": [
{
"name": "videos",
"library": null
}
]
}
So I uh figured out what I was doing wrong actually. Nothing to do with the state (the isLoading stuff on componentLoad worked fine as it did in another project) but the POJO I was sending.
So here's what happened: this.state.libraries[x] was in fact undefined, because this.state.libraries was actually an object. Containing another object called libraries which was the array from my POJO.
My POJO I was sending had "libraries" as the root object with an array inside of it. It was then being packed into the state as libraries, and so I had to use this.state.libraries.libraries[x]
Really stupid problem, but glad it's solved.
Thanks for your help guys!
Final code for those interested:
import React from 'react';
import { Modal, Button } from 'react-bootstrap';
import LibraryAccessor from '../accessors/LibraryAccessor'
import './navigator.css'
var libraryAccessor = new LibraryAccessor()
var librarylist = []
export default class LibraryLister extends React.Component {
state = { loadModal: false, libs: [] }
constructor(props) {
super(props)
this.state = {
...this.state,
libs: props
}
}
render() {
console.log(this.state.libs.libs[0])
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.state.libs.libraries.map(item=> {
console.log("ITEM")
})}
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
close = () => {
this.setState({ ...this.state, show: false });
}
open = () => {
this.setState({ ...this.state, show: true });
}
}
You have to study react
React’s props don’t work your post.
First you have to edit your videoAreaData constructor.
constructor(props) {
super(props)
this.videoAreaData = this.props.videoAreaData;
}
I recommend to read props in http://reactjs.org/docs
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { Button, Popover, Tooltip } from 'react-bootstrap';
// My components
import Navigator from './components/Navigator';
import LibraryLister from './components/LibraryLister';
import LibraryAccessor from './accessors/LibraryAccessor';
var loadingChildren = {};
var libraryAccessor = new LibraryAccessor();
var jsAccessor = new LibraryAccessor();
export default class App extends Component {
// React Component constructor parameter is `props`
constructor(props) {
super(props);
this.state = {
isLoading: true
}
}
open = () => {
this.refs.navigator.open()
}
openLibs = () => {
this.refs.libraries.open();
}
setLoading(refName, value) {
loadingChildren[refName].delete(refName);
if (loadingChildren.size < 1) {
// this.state = { isLoading: value }
// if you change state, you have to use setState; it's very very important.
// setState function is asynchronous because it relate performance.
this.setState({
isLoading: value
})
}
}
render() {
const { isLoading, libraries } = this.state;
console.log(libraries) // check please~!
if(isLoading) return <pre>Loading...</pre>
return (
<div className="App">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Your Server</h1>
</header>
<Button onClick={this.open}>Show Navigator</Button>
<Button onClick={this.openLibs}>Show Libraries</Button>
<Navigator ref="navigator"></Navigator>
<LibraryLister libs={libraries} ref="libraries"></LibraryLister>
</div>
);
}
getLibraries = (libraries) => {
this.setState({
isLoading: false,
libraries
});
}
componentDidMount() {
libraryAccessor
.listLibraries()
.then(this.getLibraries);
}
}
import React, { Component } from 'react';
import { Modal, Button } from 'react-bootstrap';
import LibraryAccessor from '../accessors/LibraryAccessor';
import './navigator.css';
var libraryAccessor = new LibraryAccessor();
var librarylist = [];
export default class LibraryLister extends Component {
constructor(props) {
super(props);
this.state = {
loadModal: false
};
this.videoAreaData = props.videoAreaData
};
render() {
console.log("PRINTING VID DATA: "+this.videoAreaData)
console.log(this.videoAreaData)
return (
<div className='lib-modal'>
<Modal show={this.state.show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>List of Available Libraries</Modal.Title>
</Modal.Header>
<Modal.Body>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.handleClose}>Close</Button>
</Modal.Footer>
</Modal>
</div>
)
}
close = () => {
this.setState({ ...this.state, show: false });
}
open = () => {
this.setState({ ...this.state, show: true });
}
}

How to call parent function in child component

I checked quite a lot of examples but found most of them having events fired in Child Component.
Can someone please suggest how can I call the Parent Component's function in child with the click event on Parent Component? Thanks.
Parent Component (app.js):
Class App extends Component {
handleClick = (e, id, text) => {
e.preventDefault();
this.setState({val: text})
}
render() {
return (
<div>
<Form val={this.state.val} Click={this.handleClick.bind(this) }/>
<Button onClick={(e) => this.handleClick(e, todo.id, todo.text)}>
<Icon>edit_icon</Icon>
</Button>
</div>
)
}
}
Child Component (form.js):
this.props.Click(); //where should i call this function since my button is in parent component
Class Form extends Component{
render() {
const { text } = this.state;
return (
<TextField
value={text}
color="secondary"
/>
)
}
}
}
If you want to call it in your Child component, you need an event to trigger it or maybe a condition. So, for example in your form.js, we will trigger it with a button click form your child component
render() {
const { text } = this.state;
return (
<TextField
value={text}
color="secondary"
/>
<Button onClick={this.props.Click} />
)
}
}
Maybe, using a Button in your child component is not a great choice for your case since you already have a Button to call the Click function in your parent component, this Button in child component I made is only for example
One way you can do this is use a ref to call the function..
// create the ref
constructor() {
super();
this.myFormRef = React.createRef()
}
// click handler for the button
handleClick = (e, id, text) => {
// here you have the child instance which gives you access to its functions
this.myFormRef.someChildMethodThatIsOnTheChildClass()
}
render() {
// notice here we use the ref on the form via... ref={this.myFormRef}
return (
<Form val={this.state.val} ref={this.myFormRef} Click={this.handleClick.bind(this) }/>
<Button onClick={(e) => this.handleClick(e, todo.id, todo.text)}>
<Icon>edit_icon</Icon>
</Button>
)
)
I would like to note though that it doesn't seem to make much sense as to why you want to do this. You should probably re-think your architecture. Also what is the button press supposed to be doing? submitting the form?

Value not getting passed from child to parent component in React

As you can see in the two components below, i want to delete the recipes(in app component) from a button click in the panelcomponent,
i have a method in app to delete the recipe, and a prop(onclick) send to child panelcomponent. Panel then gets the index from the map of recipes, and after the button click it executes the handleDelet method to send the index back to parent. but No this is not working !
class App extends React.Component {
state={
addRecipe:{recipeName:"",ingredients:[]},
recipes:[{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]}]
}
handleDelete = (index) => {
let recipes = this.state.recipes.slice();
recipes.splice(index,1); //deleting the index value from recipe
this.setState({recipes}) //setting the state to new value
console.log(index,recipes)
}
render() {
return (
<div className="container">
<PanelComponent recipes={this.state.recipes} onClick={()=>this.handleDelete(index)}/>
<ModalComponent />
</div>
);
}
}
class PanelComponent extends React.Component {
handleDelete = (index) => {
this.props.onClick(index); //sending index to parent after click
console.log(index)
}
render() {
return (
<PanelGroup accordion>
{this.props.recipes.map( (recipe,index) => {
return(
<Panel eventKey={index} key={index}>
<Panel.Heading>
<Panel.Title toggle>{recipe.recipeName}</Panel.Title>
</Panel.Heading>
<Panel.Body collapsible>
<ListGroup>
{recipe.ingredients.map((ingredient)=>{
return(<ListGroupItem>{ingredient}</ListGroupItem>);
})}
</ListGroup>
<Button bsStyle="danger" onClick={()=>this.handleDelete(index)}>Delete</Button>
<EditModalComponent />
</Panel.Body>
</Panel>
);
})}
</PanelGroup>
);
}
}
Thea actual error in your code is that while using arrow function in the onClick in parent, you are passing the wrong parameter, instead of {()=>this.handleDelete(index)} what you should write is
{(value)=>this.handleDelete(value)}, However, that also not necessary and you could simple write {this.handleDelete} in App since your handleDelete function is already binded and it received the values from the Child component.
render() {
return (
<div className="container">
<PanelComponent recipes={this.state.recipes} onClick={(value)=>this.handleDelete(value)}/>
<ModalComponent />
</div>
);
}
The difference in writing {()=>this.handleDelete(index)} vs {(value)=>this.handleDelete(value)} is that in the first case, you are explicitly passing the index that you get from the map function in your App component while in the second case, the value passed from the child component when you execute this.props.onClick(value) is being provided to the handleDelete function.
you are sending the function wrongly as props. you are sending the result of the function as props rather than the function itself
class App extends React.Component {
state={
addRecipe:{recipeName:"",ingredients:[]},
recipes:[{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]},
{recipeName:"Apple",ingredients:["apple","onion","spice"]}]
}
handleDelete = (index) => {
let recipes = this.state.recipes.slice();
recipes.splice(index,1); //deleting the index value from recipe
this.setState({recipes}) //setting the state to new value
console.log(index,recipes)
}
render() {
return (
<div className="container">
//change here
<PanelComponent recipes={this.state.recipes} onClick={this.handleDelete}/>
<ModalComponent />
</div>
);
}
}

Categories