The proper way of exchanging objects between sibling components in React - javascript

I am trying to build a simple React app and stuck into trouble. I have the following structure:
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<PlanBuilder />
<PlanMenu />
</div>
);
}
}
PlanMenu.js
class PlanMenu extends React.Component {
render() {
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
PlanBuilder.js
class PlanBuilder extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
return (
<Stage>
<Layer>
{
this.state.objects.map(function(object) {
return object.render();
})
}
</Layer>
</Stage>
);
}
So the main idea is that I have two sibling components: the drawing area and the menu. When the button on the menu is pressed, I want to create a new object and send it to the drawing area element. So, the question is how to pass PlanBuilder.addObject method to the PlanMenu class. I came from the C world, and what I think about is to pass kinda function pointer to PlanMenu. However, I am not sure this is an appropriate solution. Would you please recommend me the proper way of doing this in React? Thank you in advance.

In this case you have two ways.
The simpler one is to move the logic you have on PlanBuilder to App, and pass the necessary props to PlanBuilder and PlanMenu, like:
class PlanMenu extends React.Component {
render() {
const { addObject } = this.props
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects.map(function(object) {
return object.render();
})}
</Layer>
</Stage>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
this.addObject = this.addObject.bind(this)
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
const { objects } = this.state
return (
<div className="App">
<PlanBuilder objects={objects} />
<PlanMenu addObject={this.addObject} />
</div>
);
}
}
The other alternative is to create a "Container" to hold the logic instead adding it to App, like:
class PlanMenu extends React.Component {
render() {
const { addObject } = this.props
return (
<div className="PlanMenu">
<button type="button"
onClick={addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}>Wall
</button>
</div>
);
}
}
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects.map(function(object) {
return object.render();
})}
</Layer>
</Stage>
)
}
}
class PlanContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
objects: []
};
this.addObject = this.addObject.bind(this)
}
addObject(object) {
this.setState({
objects: [...this.state.objects, object]
});
}
render() {
const { objects } = this.state
return (
<div>
<PlanBuilder objects={objects} />
<PlanMenu addObject={this.addObject} />
</div>
)
}
}
class App extends React.Component {
render() {
return (
<div className="App">
<PlanContainer />
</div>
);
}
}
In my opinion, creating a Container makes your code more readable, reusable and cleaner :)
Hope it help!

Related

I want to create some program with react js, about adding strings in window, but I am having an error

I am creating a program with React Js, where I need to write something in prompts and it needs to appear in window. Now I have created the function of adding prompts and pasting it in window, but it's giving an error.
please help me if you can : )
export default class Clock extends React.Component {
state = {items: ['Hakob', 'Arman']};
Add(){
const newitems = this.state.items.concat([prompt('a')])
this.setState({items:newitems})
}
render(){
return <div>
<Clock2/>
</div>
}
}
class Clock2 extends React.Component {
render(){
return(
<>
<button onClick={this.Add}>click</button>
<div> {this.state.items.map((e, i) => {
return <div key = {e + i}> {e} </div>
} )} </div>
</>
)
}
}
you have not defined any state in class clock2 so, the line # 798 giving you an error for cannot read property of items as it is not defined in class
class Clock2 extends React.Components {
state = {
items : //
}
}
and the second error is you are trying to return in the return function that is not correct if you want to map items you have to define map function in render
{
const items = this.state.items.map((e,i ) => {
//
}
return (
<items/>
)
So let's write your code here.
export default class Clock extends React.Component {
state = { items: ['Hakob', 'Aram']};
Add() {
const newItems = this.state.items.concat([prompt('a')])
this.setState({items:newItems})
}
render() {
return <div><Clock2/><div>
}
}
class Clock2 extends React.Component {
render() {
return (
<>
<button onClick={this.Add}>Click</button>
<div>{ this.state.items.map( (e,i) => {
return <div key={ e + i}>{e}</div>
})}
</div>
</>
)
}
}
You made a mistake, state are internals to component, as well as method.
This should work
export default class Clock extends React.Component {
state = { items: ['Hakob', 'Aram']};
Add() {
const newItems = this.state.items.concat([prompt('a')])
this.setState({items:newItems})
}
render() {
return (
<>
<button onClick={this.Add}>Click</button>
<div>{ this.state.items.map( (e,i) => {
return <div key={ e + i}>{e}</div>
})}
</div>
</>
)
}
}
Or you can pass values from top component to his child.
export default class Clock extends React.Component {
state = { items: ['Hakob', 'Aram']};
Add() {
const newItems = this.state.items.concat([prompt('a')])
this.setState({items:newItems})
}
render() {
return (<div><Clock2 add={this.Add} values={this.state.items}/><div>)
}
}
class Clock2 extends React.Component {
render() {
return (
<>
<button onClick={this.prop.add}>Click</button>
<div>{ this.props.values.map( (e,i) => {
return <div key={ e + i}>{e}</div>
})}
</div>
</>
)
}
}

Rendering objects dynamically with KonvaJs-ReactJs

I want to render an array of React components with the help of KonvaJs without knowing exactly which object I draw at a particular moment. To be more specific, here is my code:
One of the React components I want to render, wall.js:
class CWall extends React.Component {
render() {
return (
<React.Fragment>
<Rect /*some props*/></Rect>
<Transformer /*some props*/></Transformer>
</React.Fragment>
)
}
}
In the other component I create CWall when the button is clicked, planmenu.js:
class PlanMenu extends React.Component {
render() {
...
return (
...
<button type="button"
onClick={() => { addObject(
new CWall({
x: 100,
y: 100,
length: 200
}))}}>Wall
</button>
)
}
}
The created objects are passed to the component which should display them, planbuilder.js:
import CWall from './objects/wall'
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{
objects.map(function(object) {
var ObjectType = object.constructor.name;
/* the following line gives an error to me */
return <ObjectType {...object.props} key={object.id} />;
}, this)
}
</Layer>
</Stage>
);
}
}
The specified line throws an error:
konva has no node with the type CWall
However, if I render one CWall directly, I get it on the screen as expected. It seems to me like evidence that konva is able to render CWall objects:
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
<CWall x={100} y={100} length={200} />
</Layer>
</Stage>
);
}
}
So my question is: what is the proper way of rendering objects without knowing their exact types?
Thank you in advance.
In general, you should not add React components directly into the state. Instead, just add pure data about your app, and then just render from that data. It can be like this:
class PlanMenu extends React.Component {
render() {
...
return (
...
<button type="button"
onClick={() => { addObject({ x: 10, y: 10, type: 'wall' })}}
</button>
)
}
}
import CWall from './objects/wall'
const TYPES = {
'wall' : CWall
};
class PlanBuilder extends React.Component {
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{
objects.map(function(object) {
const Component = TYPES[object.type];
return <Component {...object} key={object.id} />;
}, this)
}
</Layer>
</Stage>
);
}
}
You can use array of JSX.Element, when button clicks you will create JSX.Element and push it to array of JSX.Element and then put array into render for example
<button type="button"
onClick={() => { addObject(
<CWall
x: 100,
y: 100,
length: 200
/>)}
</button>
render() {
const { objects } = this.props
return (
<Stage>
<Layer>
{objects}
</Layer>
</Stage>
);
}

How can I make a list of integers click on element with corresponding id?

I have a list of ids (integer) and I have multiple components.
After a request to my API, the component receives a list of ids that should already be active.
I want to simulate a click on each element with the same id as the one in my array. I know I can use refs to do that, but I don't undertstand how to make it works with a list of elements.
Here's my code :
import React, { Component } from 'react'
import InterestBox from './InterestBox'
import Axios from 'axios'
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList
import React, { Component } from 'react'
export class InterestBox extends Component {
constructor(props) {
super(props);
this.images = require('../../img/interests/*.svg');
this.state = {activated: false};
this.interest_box_content = React.createRef();
this.interest_text = React.createRef();
this.handleClick = this.handleClick.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleClick() {
this.props.handleClick(this.props.id, this.props.title);
this.setState(prevState => ({
activated: !prevState.activated
}))
}
updateDimensions() {
console.log((window.getComputedStyle(this.refs.interest_box_content).width))
this.refs.interest_text = (window.getComputedStyle(this.refs.interest_box_content).width)
}
render() {
return (
<div className="column is-one-fifth-desktop is-half-touch">
<div className="interest-box">
<div className="interest-box-adjuster">
<div ref={"interest_box_content"} className={"interest-box-content " + (this.state.activated == true ? 'interest-box-activated' : '')} onClick={this.handleClick}>
<img className="interest-icon" src={this.images[this.props.icon]} style={{'height': '50%'}}></img>
<i className="activated-icon fas fa-check"></i>
<span ref={"interest_text"} className="interest-text">{this.props.title}</span>
</div>
</div>
</div>
</div>
)
}
}
export default InterestBox
In the InterestList "componentDidUpdate" method, the value of the item is an integer.
I want to use this integer to "click" on the InterestBox with the corresponding "id".
How can I achieve this ?
You can store an array of elements in one ref, like this:
constructor(props) {
super(props);
this.state = {pinterests: []}
this.pinterestRefs = React.createRef()
}
...
render() {
return (
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} icon={pinterest.picture_src} title={pinterest.name} ref={pinterestRef => this.refs.pinterestRefs.push(pinterestRef)} />
})}
</React.Fragment>
)
}
and then call the click function on each in a componentDidMount function:
componentDidMount() {
if (this.refs.pinterestRefs.length) {
this.refs.pinterestRefs.forEach(pinterestEl => {
pinterestEl.click();
});
}
}
Since this.pinterestRefs is a ref and not an array, the push method is not available. Unfortunately, we do not have a definite length so we can't declare the refs preemptively. However, we can add it to this.refs object and the convert it to an array:
export class InterestList extends Component {
constructor(props) {
super(props);
this.state = {pinterests: []}
}
componentDidMount() {
Axios.get('http://localhost:8000/api/interests')
.then((success) => {
this.setState({pinterests: success.data.data.interests});
})
}
componentDidUpdate(prevProps) {
console.log(Object.values(this.refs)); // Array with all refs
console.log(JSON.stringify(prevProps));
console.log(JSON.stringify(this.props))
if(this.props.alreadyChecked != prevProps.alreadyChecked) {
this.props.alreadyChecked.forEach((item) => {
console.log(item)
})
}
}
render() {
return (
{/*I'm assuming each item has a unique id, if not, create one*/}
<React.Fragment>
{Object.keys(this.state.pinterests).map((interest) => {
var pinterest = this.state.pinterests[interest];
return <InterestBox id={pinterest.id} onClick={this.props.onClick} ref={pinterest.id} icon={pinterest.picture_src} title={pinterest.name} />
})}
</React.Fragment>
)
}
}
export default InterestList;

React - How to pass props to a component passed as prop

I have a React component (React v15.5.4) that you can pass other components to:
class CustomForm extends React.Component {
...
render() {
return (
<div>
{this.props.component}
</div>
);
}
}
And I have a different component that uses it:
class SomeContainer extends React.Component {
...
render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
component={<SomeInnerComponent someProp={'someInnerComponentOwnProp'}/>}
object={someObjectVariable}
/>
);
}
}
Everything renders fine, but I want to pass someObjectVariable prop to the child component inside CustomForm (in this case that'll be SomeInnerComponent), since in the actual code you can pass several components to it instead of just one like the example.
Mind you, I also need to pass SomeInnerComponent its own props.
Is there a way to do that?
You can achieve that by using React.cloneElement.
Like this:
class CustomForm extends React.Component {
...
render() {
return (
<div>
{React.cloneElement(this.props.component,{ customProps: this.props.object })}
</div>
);
}
}
Working Code:
class Parent extends React.Component{
render() {
return(
<Child a={1} comp={<GChild/>} />
)
}
}
class Child extends React.Component{
constructor(){
super();
this.state = {b: 1};
this.updateB = this.updateB.bind(this);
}
updateB(){
this.setState(prevState => ({b: prevState.b+1}))
}
render(){
var Comp = this.props.comp;
return (
<div>
{React.cloneElement(Comp, {b: this.state.b})}
<button onClick={this.updateB}>Click to update b</button>
</div>
);
}
}
const GChild = props => <div>{JSON.stringify(props)}</div>;
ReactDOM.render(
<Parent />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='container' />
You can do in the same as you did for SomeInnerComponent.
Just pass named props.
Inside CustomForm,
render() {
const MyComponent = this.props.component; //stored it in some variable
return (
<div>
<MyComponent customProps = {this.props.object} /> //access object here and passed it or passed individual props
</div>
);
}
EDIT :
Please find the working demo here.
You have a couple of options to achieve what your asking.
class SomeContainer extends React.Component {
...
render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
component={<SomeInnerComponent propFromParent={someObjectVariable}/>}
object={someObjectVariable}
/>
);
}
}
Or you can clone the component prop and apply the new props as Mayank said. In your case
class CustomForm extends React.Component {
...
render() {
return (
<div>
{React.cloneElement(this.props.component,
{propFromParent:this.props.someObjectVariable})}
</div>
);
}
}
You can use react-overrides for this.
Create CustomForm:
import o from "react-overrides";
const InnerComponent = () => null; // default
class CustomForm extends React.Component {
...
render() {
return (
<div>
<InnerComponent {...o} />
</div>
);
}
}
Pass props and component of InnerComponent at overrides prop:
class SomeContainer extends React.Component {
...
render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
object={someObjectVariable}
overrides={{
InnerComponent: {
component: SomeInnerComponent,
props: {
someProp: 'someInnerComponentOwnProp'
}
}
}}
/>
);
}
}
<TextField place={"India"}> </TextField>
and in your component TextField
class TextField extends Component {
constructor(props){
super(props);
}
render() {
return (
<div>
<input />
<button> {this.props.place} </button>
</div>
)
}
}
i think what you are trying to achieve is something like this you have to pass your InnerComponent as an arrow function () => ..
class SomeContainer extends React.Component { ... render() {
let someObjectVariable = {someProperty: 'someValue'};
return (
<CustomForm
component={() => <SomeInnerComponent someProp={'someInnerComponentOwnProp'}/>}
object={someObjectVariable}
/>
); } }

Update the state of the grandparent component

I'd like to update a state value of the grandparent component:
class GrandChild extends React.Component {
changeNumber() {
// this.setState(prevState => ({
// number: prevState.number + 1 // Add 1
// }));
// I want to set the state of the App Component instead of the GrandChild component
}
render() {
return(
<div>
<h1>The number is {this.props.number}</h1>
<button onClick={this.changeNumber}>Increase number by 1</button>
</div>
)
}
}
class Child extends React.Component {
render() {
return(
<div>
<GrandChild number={this.props.number}/>
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
number: 1
}
}
render() {
return (
<Child number={this.state.number}/>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
I did code this in CodePen for those who want to test the code: https://codepen.io/anon/pen/oEeQdr
I hope there's a simple solution for this.
Thanks in advance.
class GrandChild extends React.Component {
changeNumber=()=> {
this.props.changeNumber();//call parent `changeNumber` method
}
render() {
return(
<div>
<h1>The number is {this.props.number}</h1>
<button onClick={this.changeNumber}>Increase number by 1</button>
</div>
)
}
}
class Child extends React.Component {
render() {
return(
<div>
<GrandChild number={this.props.number} changeNumber={this.props.changeNumber} /> //passed `changeNumber` as it is to `GrandChild`
</div>
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
number: 1
}
}
changeNumber=()=>{
this.setState((prevState)=>{
console.log(prevState);
return {
number : prevState.number + 1
}
});
}
render() {
return (
<Child number={this.state.number} changeNumber = {this.changeNumber}/> //passed `changeNumber` to Child
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Working codepen
Check working code below:
import React from 'react';
import ReactDOM from 'react-dom';
class GrandChild extends React.Component {
render() {
return (
<div>
<h1>The number is {this.props.number}</h1>
<button onClick={this.props.incNumber}>Increase number by 1</button>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<GrandChild number={this.props.number} incNumber={this.props.incNumber} />
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
number: 1
};
}
incrementNumber = () => {
this.setState({ number: this.state.number + 1 });
};
render() {
return (
<Child number={this.state.number} incNumber={this.incrementNumber} />
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
You can send method body as a prop to Child Class, as a pointer to it, and when you wil lcal this method in the Child, it will properly executed in the Parent, where was declared.
class GrandChild extends React.Component {
render() {
return (
<div>
<h1>The number is {this.props.passedProps.number}</h1>
<button onClick={() =>
this.props.passedProps.increaseNumber()}>Increase number by 1</button>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<GrandChild passedProps={this.props}/>
</div>
);
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
number: 1
}
}
increaseNumber = () => {
this.setState({ number: this.state.number + 1 });
}
render() {
return (
<Child number={this.state.number} increaseNumber={this.increaseNumber}/>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));

Categories

Resources