ChatGPT解决这个技术问题 Extra ChatGPT

How can I communicate between related react components?

I just got started with ReactJS and am a little stuck on a problem that I have.

My application is essentially a list with filters and a button to change the layout. At the moment I'm using three components: <list />, < Filters /> and <TopBar />, now obviously when I change settings in < Filters /> I want to trigger some method in <list /> to update my view.

How can I make those 3 components interact with each other, or do I need some sort of global data model where I can just make changes to?

Are all three sibling components or is one within the other?
They're all three components, I've already re-arranged my application so that they now all have the same parent who can provide them with data.
This is where you could use flux or pubsub pattern. Based on the docs in react docs they leave a somewhat ambiguous sentence: "For communication between two components that don't have a parent-child relationship, you can set up your own global event system." facebook.github.io/react/tips/…
@BingeBoy is right Flux is great way of writing reactjs apps, that can handle the problem of data flow, data sharing by many components.
If you don't want to get into Flux or Redux, this is an awesome article on this topic andrewhfarmer.com/component-communication

M
Michael LaCroix

The best approach would depend on how you plan to arrange those components. A few example scenarios that come to mind right now:

is a child component of Both and are children of a parent component and live in separate root components entirely.

There may be other scenarios that I'm not thinking of. If yours doesn't fit within these, then let me know. Here are some very rough examples of how I've been handling the first two scenarios:

Scenario #1

You could pass a handler from <List /> to <Filters />, which could then be called on the onChange event to filter the list with the current value.

JSFiddle for #1 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    var content;
    if (displayedItems.length > 0) {
      var items = displayedItems.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

React.renderComponent(<List />, document.body);

Scenario #2

Similar to scenario #1, but the parent component will be the one passing down the handler function to <Filters />, and will pass the filtered list to <List />. I like this method better since it decouples the <List /> from the <Filters />.

JSFiddle for #2 →

/** @jsx React.DOM */

var Filters = React.createClass({
  handleFilterChange: function() {
    var value = this.refs.filterInput.getDOMNode().value;
    this.props.updateFilter(value);
  },
  render: function() {
    return <input type="text" ref="filterInput" onChange={this.handleFilterChange} placeholder="Filter" />;
  }
});

var List = React.createClass({
  render: function() {
    var content;
    if (this.props.items.length > 0) {
      var items = this.props.items.map(function(item) {
        return <li>{item}</li>;
      });
      content = <ul>{items}</ul>
    } else {
      content = <p>No items matching this filter</p>;
    }
    return (
      <div className="results">
        <h4>Results</h4>
        {content}
      </div>
    );
  }
});

var ListContainer = React.createClass({
  getInitialState: function() {
    return {
      listItems: ['Chicago', 'New York', 'Tokyo', 'London', 'San Francisco', 'Amsterdam', 'Hong Kong'],
      nameFilter: ''
    };
  },
  handleFilterUpdate: function(filterValue) {
    this.setState({
      nameFilter: filterValue
    });
  },
  render: function() {
    var displayedItems = this.state.listItems.filter(function(item) {
      var match = item.toLowerCase().indexOf(this.state.nameFilter.toLowerCase());
      return (match !== -1);
    }.bind(this));

    return (
      <div>
        <Filters updateFilter={this.handleFilterUpdate} />
        <List items={displayedItems} />
      </div>
    );
  }
});

React.renderComponent(<ListContainer />, document.body);

Scenario #3

When the components can't communicate between any sort of parent-child relationship, the documentation recommends setting up a global event system.


The nice thing with #2 is that they only rely on a parent who passes a prop to each component: a function as updateFilter to <Filters /> and an array as items to <List />. You could use those child components in other parents with different behaviors, either together or solo. For example, if you wanted to display a dynamic list but didn't need filtering.
@woutr_be Not sure if it would fit into your requirement but when in a similar situation sometime back, we made use of following two functions to sort of communicate between the child and parent components:- listenTo: function(eventName, eventCallback) { $(window.document).bind(eventName,eventCallback);} triggerEvent: function(eventName, params) { $.event.trigger(eventName, params);} Hope it helps! (sorry could not format it better)
For scenario 3, is there a recommended approach? Any docs or examples on this by generating custom synthetic events? I have not found anything in the main docs.
Scenario #2 makes a lot of sense... until you have to jeopardize design (if only, Layout) -- then you realize the necessity for an EventHub/PubSub.
The scenario #3 link is dead and redirects to an unrelated React docs page now.
S
Sebastien Lorber

There are multiple ways to make components communicate. Some can be suited to your usecase. Here is a list of some I've found useful to know.

React

Parent / Child direct communication

const Child = ({fromChildToParentCallback}) => (
  <div onClick={() => fromChildToParentCallback(42)}>
    Click me
  </div>
);

class Parent extends React.Component {
  receiveChildValue = (value) => {
    console.log("Parent received value from child: " + value); // value is 42
  };
  render() {
    return (
      <Child fromChildToParentCallback={this.receiveChildValue}/>
    )
  }
}

Here the child component will call a callback provided by the parent with a value, and the parent will be able to get the value provided by the children in the parent.

If you build a feature/page of your app, it's better to have a single parent managing the callbacks/state (also called container or smart component), and all childs to be stateless, only reporting things to the parent. This way you can easily "share" the state of the parent to any child that need it.

Context

React Context permits to hold state at the root of your component hierarchy, and be able to inject this state easily into very deeply nested components, without the hassle to have to pass down props to every intermediate components.

Until now, context was an experimental feature, but a new API is available in React 16.3.

const AppContext = React.createContext(null)

class App extends React.Component {
  render() {
    return (
      <AppContext.Provider value={{language: "en",userId: 42}}>
        <div>
          ...
          <SomeDeeplyNestedComponent/>
          ...
        </div>
      </AppContext.Provider>
    )
  }
};

const SomeDeeplyNestedComponent = () => (
  <AppContext.Consumer>
    {({language}) => <div>App language is currently {language}</div>}
  </AppContext.Consumer>
);

The consumer is using the render prop / children function pattern

Check this blog post for more details.

Before React 16.3, I'd recommend using react-broadcast which offer quite similar API, and use former context API.

Portals

Use a portal when you'd like to keep 2 components close together to make them communicate with simple functions, like in normal parent / child, but you don't want these 2 components to have a parent/child relationship in the DOM, because of visual / CSS constraints it implies (like z-index, opacity...).

In this case you can use a "portal". There are different react libraries using portals, usually used for modals, popups, tooltips...

Consider the following:

<div className="a">
    a content
    <Portal target="body">
        <div className="b">
            b content
        </div>
    </Portal>
</div>

Could produce the following DOM when rendered inside reactAppContainer:

<body>
    <div id="reactAppContainer">
        <div className="a">
             a content
        </div>
    </div>
    <div className="b">
         b content
    </div>
</body>

More details here

Slots

You define a slot somewhere, and then you fill the slot from another place of your render tree.

import { Slot, Fill } from 'react-slot-fill';

const Toolbar = (props) =>
  <div>
    <Slot name="ToolbarContent" />
  </div>

export default Toolbar;

export const FillToolbar = ({children}) =>
  <Fill name="ToolbarContent">
    {children}
  </Fill>

This is a bit similar to portals except the filled content will be rendered in a slot you define, while portals generally render a new dom node (often a children of document.body)

Check react-slot-fill library

Event bus

As stated in the React documentation:

For communication between two components that don't have a parent-child relationship, you can set up your own global event system. Subscribe to events in componentDidMount(), unsubscribe in componentWillUnmount(), and call setState() when you receive an event.

There are many things you can use to setup an event bus. You can just create an array of listeners, and on event publish, all listeners would receive the event. Or you can use something like EventEmitter or PostalJs

Flux

Flux is basically an event bus, except the event receivers are stores. This is similar to the basic event bus system except the state is managed outside of React

Original Flux implementation looks like an attempt to do Event-sourcing in a hacky way.

Redux is for me the Flux implementation that is the closest from event-sourcing, an benefits many of event-sourcing advantages like the ability to time-travel. It is not strictly linked to React and can also be used with other functional view libraries.

Egghead's Redux video tutorial is really nice and explains how it works internally (it really is simple).

Cursors

Cursors are coming from ClojureScript/Om and widely used in React projects. They permit to manage the state outside of React, and let multiple components have read/write access to the same part of the state, without needing to know anything about the component tree.

Many implementations exists, including ImmutableJS, React-cursors and Omniscient

Edit 2016: it seems that people agree cursors work fine for smaller apps but it does not scale well on complex apps. Om Next does not have cursors anymore (while it's Om that introduced the concept initially)

Elm architecture

The Elm architecture is an architecture proposed to be used by the Elm language. Even if Elm is not ReactJS, the Elm architecture can be done in React as well.

Dan Abramov, the author of Redux, did an implementation of the Elm architecture using React.

Both Redux and Elm are really great and tend to empower event-sourcing concepts on the frontend, both allowing time-travel debugging, undo/redo, replay...

The main difference between Redux and Elm is that Elm tend to be a lot more strict about state management. In Elm you can't have local component state or mount/unmount hooks and all DOM changes must be triggered by global state changes. Elm architecture propose a scalable approach that permits to handle ALL the state inside a single immutable object, while Redux propose an approach that invites you to handle MOST of the state in a single immutable object.

While the conceptual model of Elm is very elegant and the architecture permits to scale well on large apps, it can in practice be difficult or involve more boilerplate to achieve simple tasks like giving focus to an input after mounting it, or integrating with an existing library with an imperative interface (ie JQuery plugin). Related issue.

Also, Elm architecture involves more code boilerplate. It's not that verbose or complicated to write but I think the Elm architecture is more suited to statically typed languages.

FRP

Libraries like RxJS, BaconJS or Kefir can be used to produce FRP streams to handle communication between components.

You can try for example Rx-React

I think using these libs is quite similar to using what the ELM language offers with signals.

CycleJS framework does not use ReactJS but uses vdom. It share a lot of similarities with the Elm architecture (but is more easy to use in real life because it allows vdom hooks) and it uses RxJs extensively instead of functions, and can be a good source of inspiration if you want to use FRP with React. CycleJs Egghead videos are nice to understand how it works.

CSP

CSP (Communicating Sequential Processes) are currently popular (mostly because of Go/goroutines and core.async/ClojureScript) but you can use them also in javascript with JS-CSP.

James Long has done a video explaining how it can be used with React.

Sagas

A saga is a backend concept that comes from the DDD / EventSourcing / CQRS world, also called "process manager". It is being popularized by the redux-saga project, mostly as a replacement to redux-thunk for handling side-effects (ie API calls etc). Most people currently think it only services for side-effects but it is actually more about decoupling components.

It is more of a compliment to a Flux architecture (or Redux) than a totally new communication system, because the saga emit Flux actions at the end. The idea is that if you have widget1 and widget2, and you want them to be decoupled, you can't fire action targeting widget2 from widget1. So you make widget1 only fire actions that target itself, and the saga is a "background process" that listens for widget1 actions, and may dispatch actions that target widget2. The saga is the coupling point between the 2 widgets but the widgets remain decoupled.

If you are interested take a look at my answer here

Conclusion

If you want to see an example of the same little app using these different styles, check the branches of this repository.

I don't know what is the best option in the long term but I really like how Flux looks like event-sourcing.

If you don't know event-sourcing concepts, take a look at this very pedagogic blog: Turning the database inside out with apache Samza, it is a must-read to understand why Flux is nice (but this could apply to FRP as well)

I think the community agrees that the most promising Flux implementation is Redux, which will progressively allow very productive developer experience thanks to hot reloading. Impressive livecoding ala Bret Victor's Inventing on Principle video is possible!


A
Alireza

OK, there are few ways to do it, but I exclusively want focus on using store using Redux which makes your life much easier for these situations rather than give you a quick solution only for this case, using pure React will end up mess up in real big application and communicating between Components becomes harder and harder as the application grows...

So what Redux does for you?

Redux is like local storage in your application which can be used whenever you need data to be used in different places in your application...

Basically, Redux idea comes from flux originally, but with some fundamental changes including the concept of having one source of truth by creating only one store...

Look at the graph below to see some differences between Flux and Redux...

https://i.stack.imgur.com/nUM2P.jpg

Consider applying Redux in your application from the start if your application needs communication between Components...

Also reading these words from Redux Documentation could be helpful to start with:

As the requirements for JavaScript single-page applications have become increasingly complicated, our code must manage more state than ever before. This state can include server responses and cached data, as well as locally created data that has not yet been persisted to the server. UI state is also increasing in complexity, as we need to manage active routes, selected tabs, spinners, pagination controls, and so on. Managing this ever-changing state is hard. If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. At some point, you no longer understand what happens in your app as you have lost control over the when, why, and how of its state. When a system is opaque and non-deterministic, it's hard to reproduce bugs or add new features. As if this wasn't bad enough, consider the new requirements becoming common in front-end product development. As developers, we are expected to handle optimistic updates, server-side rendering, fetching data before performing route transitions, and so on. We find ourselves trying to manage a complexity that we have never had to deal with before, and we inevitably ask the question: is it time to give up? The answer is no. This complexity is difficult to handle as we're mixing two concepts that are very hard for the human mind to reason about: mutation and asynchronicity. I call them Mentos and Coke. Both can be great in separation, but together they create a mess. Libraries like React attempt to solve this problem in the view layer by removing both asynchrony and direct DOM manipulation. However, managing the state of your data is left up to you. This is where Redux enters. Following in the steps of Flux, CQRS, and Event Sourcing, Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. These restrictions are reflected in the three principles of Redux.


How can redux help? if I have a modal for a datepicker (as a component) and that component can be loaded from multiple components living on the same page, then how would the datepicker component know which Action to dispatch to redux? This is the essence of the problem, linking an action in one componentbto another and NOT any other component. (take into consideration the datepicker itself is a deep, deep component within the modal component itself)
@vsync don't think reudx as a single static value, redux is actually can have objects, arrays...so you can save them as object or array or whatever in your store, it can be mapdispatchtoprops and each get saved in object array like: [{name: "picker1", value:"01/01/1970"}, {name: "picker2", value:"01/01/1980"}] and then use mapstatetoprops in parent and pass it down to the each components or whereever you want, not sure if it answers your question, but without seeing the code...if they are in separate groups, you can object with more details also... but it's all up to how you wanna group them..
The question is not about redux and what you can store, but HOW to pass the action deep down to what needs to trigger it. How does a deep component knows what EXACTLY needs to be triggered? since in the example I gave a general component that should trigger to a specific reducer, depending on the scenario, so it could be different reducer since a datepicker modal can be used for any component.
k
knowbody

This is the way I handled this. Let's say you have a for Day. The number of days depends on the selected month.

Both lists are owned by a third object, the left panel. Both