I'm not able to understand how to call a function from parent component to a child component in React. It just calls the functions from TestEvent and renders the old data on form. Control not going to ManageComment's component functions.
class TestEvent extends Component{
constructor(props){
super(props);
this.state={
editing:false,
comment:this.props.comment
};
this.commentRef=React.createRef(); // to create a ref to the DOM element
}
edit(){
this.setState({editing:true}) ;
}
save(){
this.setState({editing:false});
this.props.updatecomment.bind(this.commentRef.current.value,this.props.index);
}
remove(){
this.props.deletecomment.bind(this.props.index);
}
renderNormal(){
return(
<div className="commentContainer">
<div className="commentText">{this.state.comment}</div>
<div className="commentBtn">
<button className="b1" onClick={this.edit.bind(this)} >Edit</button>
<button className="b2" onClick={this.remove.bind(this) } >Remove</button>
</div>
</div>
);}
renderForm(){
return(
<div className="commentContainer">
<textarea ref={this.commentRef} defaultValue={this.state.comment}></textarea>
<div className="commentBtn">
<button className="b3" onClick={this.save.bind(this)} >Save</button>
</div>
</div>
);
}
render(){
if(this.state.editing){
return this.renderForm();
}else{
return this.renderNormal();
}
}
}
// Need to manage comments from separate component here
class ManageComment extends Component{
constructor(props){
super(props);
this.state= {
comments:[
'First comment',
'Second Comment',
'Third comment'
]}
}
// add functions to remove,edit comments from child
// passing functions as a props
updateComment(newComment,i){
var arr=this.state.comments;
arr[i]=newComment;
this.setState({comments:arr});
}
deleteComment(i){
var arr=this.state.comments;
arr.splice(i,1);
this.setState({comments:arr});
}
render(){
return(
<div className="manageComment">
{
this.state.comments.map(function(text,i){
return (
<TestEvent
key={i}
index={i}
comment={text}
updatecomment={this.updateComment.bind(text,i)}
deletecomment={this.deleteComment.bind(i)}> </TestEvent>);
},this)
}
</div>
);
}
}
.
I've created a form to enter a comment in textarea,and giving options to edit/save/remove a comment.
Details of my code :
I've created 'TestEvent' component,wherein I've 3 options,Save,Remove and Edit to operate on Comment-Text.
Then I've 'ManageComment' component, where I'm rendering 'TestEvent' with some props as well as I'm passing functions with props called updatecomment and deletecomment.
Finally,I'm rendering 'ManageComment' component.
so basically here I've parent and child component and I'm trying to invoke function of 'ManageComment' component from 'TestEvent'.
The problem is that you don't call the function but just bind() something to your functions. For example in your function you do following:
save = () => {
this.setState({editing:false});
this.props.updatecomment.bind(this.commentRef.current.value,this.props.index);
}
The problem is that this.props.updatecomment.bind(this.commentRef.current.value,this.props.index); does not execute your function but just binds the context. I would suggest you have another look at bind. What you want to do is to call your function instead of binding something to it e.g. this.props.updatecomment(this.commentRef.current.value,this.props.index);.
Why exactly do you need a commentRef? You could just access the value of your textarea through the event which gets called when you save it. If you call the event you can find the data in event.target. Check in it where the value for your text area is.
save = (event) {
this.setState({editing:false});
const commentValue = event.target
this.props.updatecomment(commentValue, this.props.index)
}
Also a suggestion from my side to make your code more readable I would suggest you use arrow functions if you want to bind the context of your component
class ManageComment extends Component{
constructor(props){
super(props);
this.state= {
comments:[
'First comment',
'Second Comment',
'Third comment'
]}
}
// add functions to remove,edit comments from child
// passing functions as a props
updateComment = (newComment,i) =>{
var arr=this.state.comments;
arr[i]=newComment;
this.setState({comments:arr});
}
deleteComment = (i) => {
var arr=this.state.comments;
arr.splice(i,1);
this.setState({comments:arr});
}
render(){
return(
<div className="manageComment">
{
this.state.comments.map(function(text,i){
return (
<TestEvent
key={i}
index={i}
comment={text}
updatecomment={this.updateComment}
deletecomment={this.deleteComment}> </TestEvent>);
},this)
}
</div>
);
}
}
Another suggestion is that you use let and const instead of var. There is a lot of literature out there on why it makes sense to no longer use var -> literature. As a rule of thumb you can just always use const and when the compiler complains switch to let;
You have already passed updateComment() and deleteComment() as props named updatecomment and deletecomment for TestEvent component. But, those functions should be bound like so:
updatecomment={this.updateComment.bind(this)}
deletecomment={this.deleteComment.bind(this)}
You don't need to bind those functions again. It's only a matter of using it like so:
class TestEvent extends Component {
....
save() {
this.setState({editing:false})
this.props.updatecomment(this.commentRef.current.value,this.props.index))
}
remove() {
this.props.deletecomment(this.props.index)
}
....
}
Related
Hello I am trying to create a navbar and have succeeded in labeling elements from an ArrayList such as [{a:['a','b','c','d'},{b:['hello','world']}] to a navbar.the only issue is the links are not collapsing inside the tag. I am using.jsx
the input given to this component is navbar=[{geolocations['country','region','area','city','neighborhood']}];
import React,{Component} from "react";
import 'bootstrap/dist/css/bootstrap.min.css';
import { MDBDropdown, MDBDropdownMenu, MDBDropdownToggle, MDBDropdownItem } from 'mdb-react-ui-kit';
const stopClickPropagation = event => event.stopPropagation();
class NavbarItem extends Component{
constructor(props) {
super(props);
// Don't call this.setState({) here!
this.state={navbarContent:""}
this.createCategory= this.createCategory.bind(this);
}
createCategory(newObject){
let currentDropDown="";
let category=Object.keys(newObject);
let subCategories=Object.values(newObject);
//for the navbar's button's name
currentDropDown+= "<MDBDropdown> <MDBDropdownToggle>"+category[0]+"</MDBDropdownToggle><MDBDropdownMenu>";
//for the list of menu items.
subCategories.map(e=>{
e.map(i=>{
currentDropDown+= "<MDBDropdownItem href=http://localhost:3000/"+i+"'>"+i+"</MDBDropdownItem>";
})
});
currentDropDown+= " </MDBDropdownMenu></MDBDropdown>";
return currentDropDown;
}
async componentDidMount(){
let currentDropDown="";
let navbarList=Object.values(this.props.navbar);
navbarList.map(e=>
{currentDropDown+=this.createCategory(e);}
)
await this.setState({navbarContent:currentDropDown});
}
render(){
return(
<div dangerouslySetInnerHTML={{ __html: this.state.navbarContent }}>
</div>
);
}
}
export default NavbarItem;
I have a factory smart contract that deploys another smart contract.
In the factory contract I have this function
address[] public migrations;
...
function getMigrations() public view returns (address[] memory) {
return migrations;
}
after the migration is pushed in this array, I want to display a list of addresses on the first page but I really can't.
This is what I've tried:
import React from "react";
import web3 from "./web3";
import factory from "./factory";
import AccordionExampleStyled from "./components/Acordeon";
import { Card, Button } from "semantic-ui-react";
import 'semantic-ui-css/semantic.min.css'
class App extends React.Component {
static async getInitialProps() {
const migration = await factory.methods.getMigrations().call();
return { migration };
};
renderCampaigns() {
const items = this.props.migration.map((address) => {
return {
header: address,
description: (
<Button>See migration</Button>
),
fluid: true,
};
});
return <Card.Group items={items} />;
}
render() {
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p></p>
{this.renderCampaigns()}
</p>
</center>
</div>
);
}
};
export default App;
I got this error: Cannot read properties of undefined (reading 'map')
P.S. I am a newbie in React.js and web3
Edit: I also tried that option, but nothing
import React from "react";
import web3 from "./web3";
import factory from "./factory";
import AccordionExampleStyled from "./components/Acordeon";
import 'semantic-ui-css/semantic.min.css'
class App extends React.Component {
static async getInitialProps() {
const migrations = await factory.methods.getMigrations().call();
return { migrations };
}
renderMigrations(){
const items = this.props.migrations.map((migrations) => {return{migrations}});
return <li>{items}</li>;
}
render() {
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p>{this.props.renderMigrations}</p>
</p>
</center>
</div>
);
}
};
export default App;
The problem
migrations is not present on the first render. so mapping through it, cause the error.
The Solution
Just need to check the migrations data has arrived and has content before mapping it:
class App extends React.Component {
static async getInitialProps() {
const migrations = await factory.methods.getMigrations().call();
return { migrations };
}
const renderMigrations = () => {
return this.props.migrations.map((migration) => {
return(
<div>
<li>{migration.name}</li>)
<p>{migration.detail}</li>)
</div>
)
})
}
render() {
const items = this.props.migration;
return (
<div>
<center>
<h2>MigrationHelper by DRIVENecosystem</h2>
<p>
<p><AccordionExampleStyled /></p>
<p>List of all migrations </p>
<p>
{
items?.length > 0 && renderMigrations
}
</p>
</p>
</center>
</div>
);
}
};
export default App;
In the renderMigrations method, I used e sample to show the migration detail with li and p elements, since the migrations array does not exist in your question, I just take an example. You should change it with your requirement in the application.
I want to create a navigation component for my project. The shell fetches a json with chapter info, these are passed to nav-element, which recursively calls itself to render the navigation tree.
shell.js
import { LitElement, html, css } from 'lit-element';
import {until} from 'lit-html/directives/until.js';
import './nav-element.js';
export class Shell extends LitElement {
static get properties() {
return {
configjson : { type: Array }
};
}
constructor() {
super();
this.configjson = fetch('./src/convertjson_test.json').then(res => res.json());
}
render() {
return html`
<main>
some content
<nav-element .chapters=${until(this.configjson, [])} root></nav-element>
</main>
`;
}
}
customElements.define('shell', Shell);
nav-element.js
import { LitElement, html, css } from 'lit-element';
import {until} from 'lit-html/directives/until.js';
import {repeat} from 'lit-html/directives/repeat.js';
export class NavElement extends LitElement {
static get properties() {
return {
chapters: {type: Array},
root: {type: Boolean} //to mark the root node
};
}
static get styles() {
return css`
.navheader {
display: none;
}
.navheader[active] {
display: block;
}
`;
}
render() {
return html`
<div class="navHeader" ?active="${this.root}">header</div>
${until(repeat(this.chapters, (chapter) => chapter.pos, (chapter) => html`<div>${chapter.n}<nav-element .chapters=${chapter.c}></nav-element></div>`))}
`;
}
}
customElements.define('nav-element', NavElement);
The problem is, that the configjson Promise is passed as property and not yet resolved by the time the nav-element is called, so i get the error:
Uncaught (in promise) TypeError: this.chapters is undefined
Searched all lit-element and lit-html documentation, the until directive resolved the issue in the shell, but not in the nav-element.
The same coding pattern worked fine in Polymer 2 (&3, although with ajax instead of fetch). Does anyone know how to solve this using lit-element only?
There is a time frame between the construction of NavElement and the assignment of the chapters property where chapters is undefined. It might be safe to initialize chapters in the component itself rather than in Shell's until directive, or at least provide a fallback value in the template:
export class NavElement extends LitElement {
static get properties() {
return {
chapters: {type: Array},
// ...
};
}
constructor() {
super();
this.chapters = [];
}
// or
render() {
return html`
...
${repeat(this.chapters || [], chapter => ...)}
`;
}
}
Also, you've (correctly) declared the chapters property as an Array but you're wrapping the repeat directive in an until (as if it was a Promise I guess?). Here there are two things going on:
the repeat() call returns a DirectiveFn and not a Promise like until expects. If chapters was a Promise, the correct way to combine until and repeat would have been:
until(
this.chaptersPromise.then(chapters => repeat(chapters, chapter => html`...`)),
html`Loading...`,
)
but...
chapters is not a Promise: the until call in the parent component resolves it and passes the result.
As for the Shell component: until used in this way should work, however its intended use is to
Render placeholder content until the final content is available
[from lit-html docs]
To make the most of it, use it to temporarily render a loading template instead of <nav-element>:
render() {
return html`
${until(
this.configJson.then(chapters => html`<nav-element .chapters=${chapters}></nav-element>`),
html`Loading chapters`,
)}
`;
}
Also, not a big deal, but here the configJson property is declared as an Array but is actually a Promise.
I have much experience in Angular. Just learned React.
Very curious about data binding methods in React.
I think the most effective method is to use ContextAPI.
ContextAPI is not to bind data in component, I think. It's just for sharing data across all levels of the application. Especially can be used to send data to the child components from parent component without passing all paths of DOM tree like when using props.
import React, { Component } from 'react';
class FormControl extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleChange = this.handleChange.bind(this); // binding this
}
handleChange({ target: { value }) {
this.setState({ value });
}
render() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange} />
);
}
}
Or making the handleChange func with arrow func is also another way.
From v16.8, we can use React Hooks(like useState, useEffect, ...) to bind data..
See this link
The docs state that bulkActions don't get selected records of a List component, just the selected ids, but i need to check a specific field in each selected record from a button's click handler of the bulkActionsButtons.
Any ideas of how this could be achieved?
I used the aside prop that is passed to the List component. Your ref based solution did not work for me.
https://marmelab.com/react-admin/List.html#aside-component
Check the above link.
The component passed as the aside prop to List component receives selectedIds and the data as part of the props.
Just to expand on #Sudharsan-Ravikumar's answer, the ref solution didn't work in my situation either (react-admin 3.14.1, using classes instead of functions+hooks mostly). I used aside like this...
import React, {Fragment} from 'react';
import {List, Datagrid, TextField, useListContext} from 'react-admin';
import Button from '#material-ui/core/Button';
import AccountTreeIcon from '#material-ui/icons/AccountTree'
import dataProvider from './dataProvider';
export class MyList extends React.Component {
list = null
handleStartMyTask = async () => {
if (!this.list || !this.list.ids || this.list.ids.length===0) return;
// console.log(`LIST DATA:`, this.list.data)
try {
const result = await dataProvider.doMyRemoteTask(this.list.data)
console.log(result)
}
catch(err) {
console.log()
}
}
/**
* This was the only way i could figure out how to get the list details.
* That is, the props you get access to when you do useListContext().
* Refs didn't work.
*/
Aside = () => {
this.list = useListContext()
// We don't actually want an aside component for the list.
return null;
}
render = () => {
return <Fragment>
/* A little card outside the list with some useful buttons */
<div class="card">
<Button
variant="outlined" color="primary" size="medium"
onClick={this.handleStartMyTask}
>
<AccountTreeIcon/> Start my task now!
</Button>
</div>
<List {...this.props} aside={<this.Aside/>} >
<Datagrid>
<TitleField source="title" />
</Datagrid>
</List>
</Fragment>
}
}
Probably absolute heresy to hooks dorks but life is short!
Ok, this is what i did and it works. A combination of a render prop and a ref.
Please if anyone have a better idea, please let me now.
import React, {Component} from 'react';
import {
List,
Datagrid,
TextField,
Button
} from 'react-admin';
class MyBulkButtons extends Component {
handleClick = () => {
const {getSelectedRecords} = this.props;
const records = getSelectedRecords();
const selectedRecords = records.filter(i => i.title === 'Extra action!');
this.processExtraActions(selectedRecords);
};
processExtraActions(selectedRecords) {
//...
}
render() {
return (
<Button onClick={this.handleClick} label={"Check for extra actions"}/>
);
}
}
export class MyList extends Component {
constructor(props) {
super(props);
this.myDataGrid = React.createRef();
}
getSelectedRecords() {
const gridProps = this.myDataGrid.current.props;
return gridProps.selectedIds.map(id => gridProps.data[id]);
}
render() {
return (
<List {...this.props}
bulkActionButtons={<MyBulkButtons getSelectedRecords={this.getSelectedRecords.bind(this)}/>}>
<Datagrid ref={this.myDataGrid}>
<TextField source="id"/>
<TextField source="title"/>
</Datagrid>
</List>
);
}
}