I have simplified the code to isolate and reproduce the issue, so it may not make sense in real implementations:
import React, { Component } from 'react'
const obj = {
objProp: true
};
export default class MyButtonContainer extends Component {
render() {
return (
<MyButton
onClick={() => {obj.objProp = !obj.objProp;}}
text={obj.objProp.toString()}
/>
);
}
}
class MyButton extends Component {
render() {
return (
<button
onClick={this.props.onClick}
>
{this.props.text}
</button>
)
}
}
You can see that obj.objProp is assigned into MyButton.props.text, and it's value gets toggled when you click on an instance of MyButton. The value of obj.objProp does change as expected, but MyButton doesn't update and rerender.
My question is why is MyButton is not updating, and what is the proper way to implement such logic?
In addition, if the solution is to push obj into MyButtonContainer.state, why MyButton would of update if I have used Redux, which injects data only into props without changing the state?
Thanks :)
What you need is a state, You should not use variable this way, it needs to be on state and changing that state asynchronously.
Change your button container to this.
export default class MyButtonContainer extends Component {
constructor() {
this.state = {
objProp: true
}
this.onclick = this.onclick.bind(this);
}
onclick() {
this.setState({ objProp: !this.state.objProp })
}
render() {
return (
<MyButton
onClick={() => { this.onclick() }}
text={this.state.objProp.toString()}
/>
);
}
}
Demo
Use state to hold your objProp
React will rerender when there is setstate is called, it won't get rerendered automatically.
export default class MyButtonContainer extends Component {
state = {
objProp: true
}
onclick = () => {
this.setState({ objProp: !this.state.objProp })
}
render() {
return (
<MyButton
onClick={() => { this.onclick() }}
text={this.state.objProp.toString()}
/>
);
}
}
}
Whenever there is something where you want the UI to change it should be either through its State or by props passed to it.
Both the given answers are right, if you want to re-render your component you must use this.setState. so there is two way to get your updated data in React Component.
1) put your object in to state and setState.
2) if you really dont want to use your object in state, you can do a workaround like take a variable i in your state and when assigning the data in your object just do this.setState({i+1}), so due to change in state will re-render your component although this is not good way to resolve it, because to re-render you must setState.
import React, { Component } from 'react'
constructor(props){
super(props);
this.state = {i:0}
}
const obj = {
objProp: true
};
export default class MyButtonContainer extends Component {
render() {
return (
<MyButton
onClick={() => {
obj.objProp = !obj.objProp ;
let {i} = this.state ;
i = i + 1;
this.setState(i)}}
text={obj.objProp.toString()}
/>
);
}
}
class MyButton extends Component {
render() {
return (
<button
onClick={this.props.onClick}
>
{this.props.text}
</button>
)
}
}
Related
I am looking to create a "delete-able" / removable React component that I can use in multiple different places.
From researching, I can see it is kind of an anti-pattern to create a component that deletes itself and the correct way to do things is for the parent to manipulate the child components rather than child components modifying themselves.
This has led me to write code somewhat along the following lines:
class ParentComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: [ XXX ]
};
}
removeFunc = (index) => {
const test = this.state.data.filter((_,i) => i !== index);
this.setState({data: test});
}
render() {
return (
<div>
{this.state.data.map((el,i) =>
<ChildComponent removeFunc={() => this.removeFunc(i)}/>
)
}
</div>
);
}
}
export default ParentComponent;
class ChildComponent extends Component {
constructor(props) {
super(props);
this.state = {
removeFunc: props.removeFunc
};
}
render() {
return (
<div>
<button onClick={this.state.removeFunc}>Delete Me</button>
</div>
);
}
}
export default ChildComponent;
The issue I have with this is that I have to keep re-writing the removeFunc function in every parent component.
I am VERY new to React, so I'm just curious if there is there a better / different way to do this or is this the correct way?
I have an react app with primereact installed and I am using primereact/captcha.
Maybe I have misunderstood something, but isn't the following code supposed to work (console.log('Child component did update'))?
import React from 'react';
import { Captcha } from 'primereact/captcha';
export default function App() {
return (
<div className="App">
<ParentComponent/>
</div>
);
}
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
class ParentComponent extends React.Component {
constructor() {
super();
this.state = {
captchaSovled: false,
key : Math.random()
}
}
render() {
let output;
if (this.state.captchaSolved) {
output = <Child key={this.state.key} />;
} else {
output =<Captcha siteKey="xxxxxxx" onResponse={() => this.setState({ key : Math.random(), captchaSolved: true })} />
}
return (
<div>
<h1>Parent component</h1>
{output}
</div>
);
}
}
From React doc
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
In your code, the Child component is mounted after captchaSolved state is set, therefore only componentDidMount is fired on Child component.
componentDidUpdate is fired, if there is any change in the state or props. As of your component child:
class Child extends React.Component {
componentDidUpdate () {
console.log('Child component did update');
}
render() {
return (<h2>Child component</h2>);
}
}
There is no state or props which are changing, that's why componentDidUpdate never get's invoked.
I want to know if there is possible to get the component instance as I need.
I put my new component as a children in the main state, but is no the same object in both files.
I need to reach children state in my MainComponent. Looking in google for componenet instance doesnt help, maybe I am out of focus and the name of this is different.
Here is my MainComponent:
import React, { Component } from 'react';
import AnotherComponent from './../whatever/AnotherComponent';
class MainComponent extends Component {
constructor(props) {
super(props);
this.state = {
children: [],
};
}
addChild() {
const { children } = this.state;
this.setState({
children: children.push(<AnotherComponent />)
});
}
getChildrenState(component) {
return component.state(); // this doesn't work!
}
render() {
const { children } = this.state;
return (
<div>
{(children.map(i => (<div key={this.getChildrenState(i).id}>{i}</div>))}
</div>
)
}
And This is AnotherComponent
import React, { Component } from 'react';
class AnotherComponent extends Component {
constructor(props) {
super(props);
this.state = {
id: 144,
};
}
render() {
return (
<div>
Here it is my cHild!
</div>
)
}
Putting <AnotherComponent/> to the state doesn't make sense because it's React element object that isn't associated with specific component instance.
Accessing children state in parent component breaks the encapsulation and indicates design problem.
The instance of class component should be retrieved with a ref, and doing so to access instance state is the last resort that may be needed to extend third-party components that don't provide desired functionality.
If AnotherComponent is first-party component, it should be designed accordingly, to not require state to be accessed from the outside:
render() {
return (
<div key={this.state.id}>{this.state.id}</div>
)
}
If the output needs to be more flexible, it can make use of render prop pattern:
render() {
const render = React.Children.only(children);
return (
<div key={this.state.id}>{render(this.state.id)}</div>
)
}
And used like:
<AnotherComponent>{id => <div>{id}</div>}</AnotherComponent>
If you want to access the state of the child component ( here AnotherComponent ) then you can either :
Maintain the state inside the AnotherComponent and pass the value to the parent ( here MainComponent ) on a change listener ( whenever the state changes ), or;
Maintain the state in the parent ( here MainComponent ) and pass the value to the child as prop.
Let me know if you want me to give an example implementation.
I am developing a simple React JS application for learning purpose. I just started learning React JS a few days ago. Now, I am having a problem with Flux Store. I need to share the change event across two child components on the same hierarchical level.
I have the parent component, called TodoComponent with the following definition
//Create
class TodoComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return (
<div>
<div>
<ListComponent />
</div>
<AddItemComponent />
</div>
)
}
}
It has two child components called, ListComponent and the AddItemComponent. Moreover, I have a store with this definition.
import { EventEmitter } from 'events';
class DataStore extends EventEmitter{
constructor()
{
super();
this.todos = [
"Eat",
"Sleep",
"Die",
"Shower"
];
}
getAll(){
return this.todos;
}
addItem(newItem)
{
this.todos.push(newItem);
this.emit("change")
}
}
const dataStore = new DataStore;
export default dataStore;
It has a function for adding new item into the array and a function for fetching the array.
This is the ListComponent that is displaying the array of items from the DataStore flux store.
import React from 'react';
import TodoItem from './TodoItem';
import DataStore from './data.store';
class ListComponent extends React.Component{
constructor(props)
{
super(props)
this.state = { todos : DataStore.getAll() };
}
componentWillMount(){
DataStore.on('change', () => {
//do somethif
this.state = { todos : DataStore.getAll() };
})
}
render()
{
var deleteItem = (item) => {
this.deleteItem(item);
}
var editItem = (item) => {
this.editItem(item);
}
var addItem = (newItem) => {
this.addItem(newItem);
}
var todos = this.state.todos.map((item, index) => {
return (
<TodoItem item={item} addItem={addItem.bind(this)} deleteItem={deleteItem} editItem={editItem} />
)
});
return (
<ul>
{todos}
</ul>
)
}
deleteItem(item)
{
this.setState({ todos: this.state.todos.filter((listItem, index) => {
return listItem !== item;
}) });
}
editItem(item)
{
alert(item)
}
addItem(newItem)
{
DataStore.addItem(newItem);
}
}
module.exports = ListComponent;
It is updating the items in the change event of the DataStore store. But I am not calling the addItem function in the ListComponent. I am calling it in the AddItemComponent.
This is the definition of the AddItemComponent.
import React from 'react';
import DataStore from './data.store';
class AddItemComponent extends React.Component{
constructor(props)
{
super(props)
}
render()
{
return (
<form id="form-todo" onSubmit={this.addItem.bind(this)} action="post">
<input type='text' ref="newItem" />
<button>ADD</button>
</form>
);
}
addItem(e)
{
e.preventDefault();
DataStore.addItem(this.refs.newItem.value);
}
}
module.exports = AddItemComponent;
But when I trigger the addItem function in the AddItemComponent, the change event of the DataStore in the ListComponent is not triggered. Therefore, how can I synchronize the change event of Flux Store between two components exist on the same hierarchical level?
The solution I can think of is having the DataStore in the TodoComponent (parent component) and send the data and functions as props to the child component. I think, the code will become a bit messy in that way. Is that the only solution to do that?
Welcome to React! I recreated your example and your "change" event is firing in ListComponent, but to update the state in a component you should use this.setState(changes) rather than this.state = {changes}. Only use this.state = {} in the constructor to set the initial state. The setState method properly flows through the React lifecycle and causes the component to re-render using the new state. There is an official guide on React's state and lifecycle hooks here.
I have a scenario where I want to create an HOC that detects mouse events (e.g. mouseenter, mouseleave) when they occur on the HOC's WrappedComponent, then pass the WrappedComponent a special prop (e.g. componentIsHovered). I got this working by using a ref callback to get the wrapped component instance, then adding event listeners to the wrapped instance in my HOC.
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
export default (WrappedComponent) => {
return class DetectHover extends Component {
constructor(props) {
super(props)
this.handleMouseEnter = this.handleMouseEnter.bind(this)
this.handleMouseLeave = this.handleMouseLeave.bind(this)
this.bindListeners = this.bindListeners.bind(this)
this.state = {componentIsHovered: false}
this.wrappedComponent = null
}
componentWillUnmount() {
if (this.wrappedComponent) {
this.wrappedComponent.removeEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.removeEventListener('mouseleave', this.handleMouseLeave)
}
}
handleMouseEnter() {
this.setState({componentIsHovered: true})
}
handleMouseLeave() {
this.setState({componentIsHovered: false})
}
bindListeners(wrappedComponentInstance) {
console.log('wrappedComponentInstance', wrappedComponentInstance)
if (!wrappedComponentInstance) {
return
}
this.wrappedComponent = ReactDOM.findDOMNode(wrappedComponentInstance)
this.wrappedComponent.addEventListener('mouseenter', this.handleMouseEnter)
this.wrappedComponent.addEventListener('mouseleave', this.handleMouseLeave)
}
render() {
const props = Object.assign({}, this.props, {ref: this.bindListeners})
return (
<WrappedComponent
componentIsHovered={this.state.componentIsHovered}
{...props}
/>
)
}
}
}
The problem is that this only seems to work when WrappedComponent is a class component — with functional components the ref is always null. I would just as soon place the WrappedComponent inside <div></div> tags in my HOC and carry out the event detection on that div wrapper, but the problem is that even plain div tags will style the WrappedComponent as a block element, which doesn’t work in my use case where the HOC should work on inline elements, too. Any suggestions are appreciated!
You can pass the css selector and the specific styles you need to the Higher Order Component like this:
import React, {Component} from 'react';
const Hoverable = (WrappedComponent, wrapperClass = '', hoveredStyle=
{}, unhoveredStyle={}) => {
class HoverableComponent extends Component {
constructor(props) {
super(props);
this.state = {
hovered: false,
}
}
onMouseEnter = () => {
this.setState({hovered: true});
};
onMouseLeave = () => {
this.setState({hovered: false});
};
render() {
return(
<div
className={wrapperClass}
onMouseEnter= { this.onMouseEnter }
onMouseLeave= { this.onMouseLeave }
>
<WrappedComponent
{...this.props}
hovered={this.state.hovered}
/>
</div>
);
}
}
return HoverableComponent;
};
export default Hoverable;
And use Fragment instead of div to wrap your component:
class SomeComponent extends React.Component {
render() {
return(
<Fragment>
<h1>My content</h1>
</Fragment>
)
}
And then wrap it like this
const HoverableSomeComponent = Hoverable(SomeComponent, 'css-selector');