Sharing store change event between same hierarchical level child components - reactjs

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.

Related

Create a removable React component without having to re-create remove function

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?

React component not updating from props

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

Better practice for identifying what part of state to change when having one event handler for multiple dynamically created fields?

In React, callback functions are often used to send a signal from a child to a parent that something has happened.
I am building an application where the user is able to dynamically create more fields, and all change events for all fields are served by the same callback function. See my code below. As you can see I am using event, name, and key to know what part of my state to change.
Editable component passes name to parent
import React, { Component } from "react";
export default class Editable extends Component {
constructor(props) {
super(props);
this.onBlur = this.onBlur.bind(this)
}
onBlur(event) {
this.props.handleBlur(event, this.props.name)
}
render() {
return (
<div>
<div
contentEditable={true}
onBlur={this.onBlur}
dangerouslySetInnerHTML={{ __html: this.props.html }}
/>
</div>
);
}
}
Subject component passes name and id to parent
import React, { Component } from "react";
import Editable from "./Editable";
export default class Subject extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur.bind(this);
}
handleBlur(event, name) {
this.props.handleBlur(event, name, this.props.skillId);
}
render() {
return (
<div>
<p>subject</p>
<Editable
handleBlur={this.handleBlur}
html={this.props.subject}
name="subject"
/>
</div>
);
}
}
The callback in the parent has the callback it needs to update the correct skill[property] in the state.
import Skill from "./Skill";
export default class Skills extends Component {
constructor(props) {
this.handleBlur = this.handleBlur.bind(this);
}
handleBlur(event, name, key) {
// Shange the state
}
render() {
var skills = [];
this.props.skills.map(element => {
skills.push(
<Skill
key={element.id}
skillId={element.id}
subject={element.subject}
ability={element.ability}
handleBlur={this.props.handleBlur}
/>
);
});
return <div>{skills}</div>;
}
}
What is a better practice for identifying what part of state to change when having one event handler for multiple dynamically created fields?

Setting the state with React + TypeScript

I cannot figure out how to set the state of my React component with TypeScript.
I'm making a simple Todo list
I have a component for the entire list: TodoList
I want to seed the list with some items to play around with
I figured I'd send in a simple array as the props to the TodoList component, and then immediately set that as the state so I have something to work off of
import * as React from "react";
import { TodoItem, ITodoItem } from "../TodoItem";
interface ITodoListProps {
items: ITodoItem[];
}
interface ITodoListState {
stateItems: ITodoItem[];
}
export class TodoList extends React.Component<ITodoListProps, Partial<ITodoListState>> {
constructor(props: ITodoListProps) {
super(props);
// Trouble figuring this part out
// I'd like to set the state to the list from the
// props, as a seed.
this.setState({
stateItems: this.state.stateItems
});
}
public render() {
return (
<div>
<ul>
// This should probably be displaying the items from the state, and not the props.
{this.props.items.map((todo, i) => {
return <TodoItem key={i} name={todo.name} />
})}
</ul>
</div>
);
}
}
You need to pass the props.items to the state when constructing the initial state:
export class TodoList extends React.Component<ITodoListProps, Partial<ITodoListState>> {
constructor(props: ITodoListProps) {
super(props);
this.state = {
stateItems: props.items
};
}
...
}
In the constructor when setting the initial state, simply assign the value you want to this.state:
constructor() {
// ...
this.state = { stateItems: ... /* initial value */ }
}
Later in the lifecycle methods or event listeners you can change the state the way you are doing it in the constructor with setState

Child componenet not updating when parent component is updating its array in react

I am having a list of item in parent react component and in which i am added new item and updating items. Child component will receive the items in props and render it.
When parent state is getting updated , child component is not updating its value.
Do i need to update the state in child component state in "componentWillReceiveProps" ? What is the correct way of doing it.
Code Example
// parent component
import React, { Component } from "react";
import TestList from '../controls/testlistview'
export default class TestView extends Component {
constructor(props) {
super();
this.state = {
items: []
};
}
render() {
return (<div>
<button onClick={this.addItem.bind(this)}> Add item</button>
<button onClick={this.changeFirstItemText.bind(this)}> Change item</button>
<TestList items={this.state.items} index={index}/>
</div>);
}
addItem() {
var items = this.state.items.map(s=> s);
items.push('new one');
this.setState({
items: items
});
}
changeFirstItemText() {
var items = this.state.items.map(s=> s);
items[0] = "changed text";
this.setState({
items: items
});
}
}
//Child component
import React, { Component } from "react";
export default class TestList extends Component {
constructor(props) {
super();
debugger;
this.state = {
rootNodes: props.items
};
}
componentWillReceiveProps(nextProps){
debugger;
}
render() {
var items = this.state.rootNodes.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
}
Instead of
render() {
var items = this.state.rootNodes.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
you get the items from props
render() {
var items = this.props.items.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
You don't have to assign props to TestList state again, otherwise you will need to do setState() again from TestList in order to trigger the render again. (which is not necesary step)
http://codepen.io/kossel/pen/ObQLoR
In the TestList class you shouldn't assign the props to the Component's state - this is a surefire way to cause major issues in React, and is the cause of your issue here. See my answer here for why this is a bad idea.
If you change your TestItem to the following, then it should work fine.
export default class TestList extends Component {
constructor(props) {
super();
debugger;
}
componentWillReceiveProps(nextProps){
debugger;
}
render() {
var items = this.props.items.map((s) => {
return <div>{s}</div>;
});
return <div>{items}</div>;
}
}
Reason is you are creating the ui element by the state of child component.
There are two ways of solving this issue:
1. update the state value in componentWillReceiveProps() funtion like this
componentWillReceiveProps(newProps){
this.setState({
rootNodes: newProps.items
});
}
2.Create the ui element directly from the props values like this-
render() {
var uiItems = this.props.items.map((item) => {
return <div>{item}</div>;
});
return (<div>{uiItems}</div>);
}

Resources