react-admin: Access <List/> records from bulkActions - bulk

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>
);
}
}

Related

my react bootstrap navbar menu isnt having its elements collapsed inside their respective buttons, advice?

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;

Fetch data from a smart contract array

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.

How do I export a function in a class component into another page?

I want to change the text of a message in (Message.js) from 'Not Updated' to 'Updated' when a button on another page (PageButton.js) has been clicked.
Message.js
import React from 'react';
class Message extends React.Component {
constructor (){
super()
this.state = {
message: 'Not Updated'
}
}
changeMessage(){
this.setState =({
message: 'Updated'
}
)
}
render(){
return (
<h1>{this.state.message}</h1>
)
}
}
export default Message
Pagebutton.js
import React from 'react';
import { changeMessage } from "./Message"; //this does not work
import Message from "./Message"; //this does not work too
class Pagebutton extends React.Component {
render(){
return (
<div>
<button onClick={()=>this.changeMessage()}>Change Text</button>
</div>
)
}
}
export default Pagebutton;
In Pagebutton.js, 'changeMessage' is not defined, how do I make it work? If this way of importing functions is not correct, please teach me an alternative way of doing it :)
Thank you so much in advance for your help!
You can't export things inside functions or classes in javascript. In order to get access, you'd have to pass down changeMessage as a prop from a component that both Message and Pagebutton are descendants of. See: https://reactjs.org/docs/components-and-props.html
An example setup would be:
Container.js
import React from 'react';
import Pagebutton from "./Pagebutton";
import Message from "./Message";
class Container extends React.Component {
constructor (){
super()
this.state = {
message: 'Not Updated'
}
}
changeMessage(){
this.setState =({
message: 'Updated'
}
)
}
render(){
return (
<Message message={this.state.message} />
<Pagebutton changeMessage={this.changeMessage.bind(this)} />
)
}
}
export default Container
Pagebutton.js
import React from 'react';
class Pagebutton extends React.Component {
render(){
return (
<div>
<button onClick={()=>this.props.changeMessage()}>Change Text</button>
</div>
)
}
}
export default Pagebutton;
Message.js
import React from 'react';
class Message extends React.Component {
render(){
return (
<h1>{this.props.message}</h1>
)
}
}
export default Message
Addition to answer by #Kyruski, you can also use Context API, which enables "magically passing props".
https://reactjs.org/docs/context.html
Documentation has many examples, that you can try.

How to use React DnD with styled component?

When wrapping my styled component in connectDragSource I get the following error:
Uncaught Error: Only native element nodes can now be passed to React
DnD connectors.You can either wrap PaneItemText__StyledItem into a
<div>, or turn it into a drag source or a drop target itself.
The first suggestion from this message is to wrap my styled component in a <div>, but this will mess with my layout and would prefer not to do this.
I'm not sure what the second option is suggesting - would anybody be able to clarify?
Below is an rough example what I am doing:
import React, { Component } from 'react';
import styled from 'styled-components';
import { DragSource } from 'react-dnd';
const StyledComponent = syled.div`
...
`;
class MyComponent extends Component {
...
render() {
const { connectDragSource } = this.props;
return connectDragSource(<StyledComponent />)
}
}
const itemSource = {
beginDrag(props) {
/* code here */
},
endDrag(props) {
/* code here */
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
export default DragSource('foo', itemSource, collect(MyComponent);
You should use Styled Component's innerRef to get the underlying DOM node, then you can call your connectDragSource to it.
In your case, it should be like this:
class MyComponent extends Component {
...
render() {
const { connectDragSource } = this.props;
return (
<StyledComponent
innerRef={instance => connectDragSource(instance)}
/>
)
}
}
You can also look at my implementation of Knight component for the official chess tutorial as a reference.
It is also accessible through CodeSandbox.
If you are using multiple connectors you can do the following:
<MyStyledComponent
innerRef={instance => {
connectDragSource(instance);
connectDropTarget(instance);
}}
/>
Source: https://github.com/react-dnd/react-dnd/issues/347#issuecomment-221703726

passing function as a props

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)
}
....
}

Resources