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
Related
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.
Let's say I have a React Component like the following :
import React, { Component } from 'react';
class test extends Component {
__method01() {
//.... sime implentation
}
__method02(){
//.... sime implentation
}
render() {
return (
<div>
</div>
);
}
}
export default test;
What I want to achieve is to implement function that intercepts all or some methods call and avoid executing them under certain circumstances.
I was thinking about a Proxy pattern. But I'm not sure what could be the best (elegant) implementation for React.
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>
);
}
}
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
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)
}
....
}