I am using react and flux in my application, In my application I have two components: TreeView and TableView and their is fetched from two different stores.
I want to add an option - when I click on a row in my TableView it will open the relevant node in my TreeView.
My question is: How those two components should talk to each other? In AngularJS/Backbone I will use an Event Aggreator pattern and will broadcast an event like - "objectSelected" with it's id.
How should I do this in react with flux architecture - I thought using the flux way - and creating an action, but then my store will save UI state (which nodes are open) and I think that the component (in this case TreeView) should save this state (and not the store).
So what's the correct for talking between two components in react?
If you don't want to use flux, then you would pass actions as props through a parent component.
The only flux implementation I've used is Fluxxor. Using fluxxor (and probably most flux implementations) you can have both a component state and a store state. If your table and tree are always going to be linked (both open or closed at the same time), I think it would be okay to save it in a store.
You might do something like ..
TableComponent = React.createClass({
getInitialState: function() {
return {
//component specific state
};
},
getStateFromFlux: function() {
// flux store state
return {
appStore: this.getFlux().store('AppStore').state
}
},
handleClick: function(tree) {
this.getFlux().actions.appActions.toggleTree(tree);
}
});
Related
What is the state of the art in React to implement an event based pattern ( Publisher / Subscriber).
From one side we have components that listen to those events. Some of these components are visual, e.g. a chart that draws the result of a query other might not be visual, e.g. if we want to have components that manage queries and results.
From the other, any component can generate events.
Our first idea is adding in Redux the full list of events and their values and on each component implement in shouldComponentUpdate() a smart code that stops if the component does not listen to the changed events.
This doesn't look very elegant as all components listen to all events. Is there a more elegant solution (kind of state of the art) ?
Of course we want to be able to go back, forwars, save state and all this small details :-)
I think what you need to know is not so much "What is the state of the art" as "What is the canonical way" of doing publish and subscribe with React and Redux.
The short answer is that if you have a complex enough application, you should organize your store so that the application state is divided into slices and use the container pattern to separate the responsibility of publishing and subscribing. This way you avoid the problem you mentioned where you don't know what in the code base is changing the store and what is responding to the changes. You can look at a container component and see how it handles UI events from its children and produces a state change event. Or you can see how the container handles a state change and then drill-down the hierarchy. If you can make a Redux slice the responsibility of a single container, it's that much easier to think about events. All other components are not subscribed to the events per se, instead they receive the changes they need to render from props originating from the container component. And they notify the container component of their own events through callbacks passed down through props, so the container can publish them. This can go a long, long way, and if you feel that you need to pass down props too many levels, you can use React.children to flatten the nesting a bit, or in rare cases context.
The longer answer is a bit more difficult, since publish and subscribe is not super meaningful when talking about React. React should ultimately be responsible for rendering. As you mentioned, not all events are UI events. But if you model things such that all events that you can publish and subscribe to boil down to changing the store or reacting to a store change, then you can build your app focused more on Redux. Managing the queries and results you're talking about should be done in simple JavaScript modules without React. Then you can use a container component to glue the store to that module.
There are additional patterns people use like action creators and selectors. Those are good because at least the intent is to keep code bases familiar. But things are still moving a bit fast, with React moving towards the Hooks API and React-Redux trying to catch-up. But slices and container components aren't going anywhere, they are a natural way to separate concerns.
redux to store the events
reselect/connect to subscribe to them (https://github.com/reduxjs/reselect#connecting-a-selector-to-the-redux-store)
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibleTodos } from '../selectors'
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
Now you have your subscribed event as a prop in the component, the component will re-render only if the prop changes. No need for shouldComponentUpdate because your component doesn't have to listen to the whole store, it will only get the part of the store you define in the selector.
Basically, I have a component called Timeperiod which is a dropdown where users will select values such as "today", "yesterday", "last week", etc.
I want to re-use the <Timeperiod> component in a lot of other components and the problem is I wouldn't be able to access which value they chose if I call it in another.
I know an alternative would be to call my other components inside of Timeperiod and passing them properties based on Timeperiod state but wouldn't that mean I need to create multiple Timeperiod like classes?
Would redux or something be a good way to handle these situations?
Here's my codesandbox: https://codesandbox.io/s/ypyoq8zoz
Right now I only have App.js calling Timeperiod, but I will have many more classes calling it too. I want to be able to access the date chosen in App.js while making the Timeperiod class re-usable.
To decide if you need Redux and how much of Redux do you need, you have to consider carefully the whole application you are developing. Redux adds some boilerplate, which might be too much for the benefits you would gain.
You can however solve your problem without Redux.
Without Redux
One of the key ideas under React is called "Lifting state up". This means that if several components need access to the same piece of data, you should definitely move the storage of that piece of data in a component that is a common ancestor of all those components (see https://reactjs.org/docs/lifting-state-up.html).
In your case, it should not be responsibility of Timeperiod to keep memory of the selected date, but of App (indeed, the closest common ancestor of all components that need access to the selected period). Timeperiod should take the selected date from app via a property, and notify App when user changes the selected date using an event (again, a prop containing a function).
class App {
constructor(props) {
super(props);
this.state = {
start: new Date(), // initial value
end: new Date(), // initial value
}
}
render() {
return (
<Timeperiod
start={this.state.start}
end={this.state.end}
onStartChange={newValue => this.setState({ start: newValue })}
onEndChange={newValue => this.setState({ end: newValue })}
/>
);
}
}
With Redux
Redux allows you to keep a global state and access it in special components called Containers. You can put how many containers you want, in any point of the document tree. This seems great, but has several drawbacks:
Too many container components degrade performance
Having full access to whole state in any point of the tree could create problems if you are not super careful. A component could modify some data it should not be allowed to access, and so on.
Redux introduces some boilerplate that might be too much if the application is simple
For any update you have to define some actions capable of handling it, create a reducer to produce the next state from the previous state and the action, and connect together all these pieces
Conclusion
It really depends on your application whether Redux is good or bad, but if your component hierarchy is not too deep and your app not too complex, vanilla way could be better
So, to follow up our comments. Here's how you could approach it:
The Timeperiod component expects a getDate function that expects a title argument. When you render the Timerperiod component, each time it has a separate state and props.
Check out how I rendered more than one(in the app.js), to show that in action.
Using redux what you could do is have a within your state a timePeriod sub state handled by a dedicated reducer which stores the user's choice.
Then each of your TimePeriod component will be hooked to this state using something like
const ConnectedTimePeriod = connect(
state => state.timePeriod,
{
onTimePeriodChoose (timePeriod) {
return {
type: "CHOOSE_TIME_PERIOD",
timePeriod
}
}
}
)(TimePeriod);
Which hydrates your component with the global shared state and provides a function as a prop to update the time period.
The ConnectedTimePeriod can then be used everywhere and will share the global state.
I would suggest you take a look at the react-redux docs for example and details.
I don't feel right to store a function in a reactjs state, but not sure any disadvantage? Could anyone help analyze it?
view1: function() {
return <div>view1</view>
}
view2: fucntion() {
return <div>view2</view>
}
goView1: function() {
this.setState({contentView: view1})
}
goView2: function() {
this.setState({contentView: view2})
}
render: function () {
return(
{this.state.contentView()}
)
}
There're a few common gotchas about state in react:
Don't mutate state directly, use setState instead
Don't use state when not referenced in render, use instance variable instead.
Well, it seems fine to use state as function, since it's used in your render function to return what to render? Alright, it might not be an anti-pattern, but a better place to put those functions might be in your props, aka render prop, a few reasons:
Think state as your component's data model.
Data are not stale, it changes over time, it could be results from async operations, ui states after user interactions, controlled forms values etc.
Just like redux stores your app's global state, react state should store your component's local state that changes over time, while functions most of time, does nothing more than deferring execution.
Render props is different, it offers IoC (Inversion of Control) for free.
Props most of time similar to local state, should be concerned with UI rendering. However, unlike state owned/managed by component itself, props are passed from parents/clients of the component.
Event handlers for example, event target (usually DOM elements like button, input etc) doesn't need to know how to handle events when creating the component, instead it yields control back to component client/consumer who might need to use setState in its handleEventName handler to update certain container's state shared across children.
Some library (e.g., react router4) also shifted from HOC to render props (e.g., react 16.3 new context API - provider/consumer pair) in favor of its flexibility and explicitness. Instead of composing HOCs before runtime for static version of final enhanced component, you can declare what to render using render props and composing them at runtime in render functions. Thus, you get a clear separation between data (state/props) provider and consumer (where render props are passed from).
Regarding any performance (re-render) concerns, i think it's totally fine to use state as function, since most performance tricks are done by using shouldComponentUpdate hook with shallow compare state/props references (e.g. React.PureComponent, Redux Connect etc), so it's where immutability and memoization comes into play.
According to React docs, states should be used for UI state only, thus it's confusing whether data retrieved from Store is UI state or not.
Let's say we got a list of items from the Store and it needs to be rendered to component.
Should it be:
Stored as a state
Stored within component's property (requires forceUpdate)
I can also think of data being retrieved from Store within render method, this doesn't require to keep data within component at all, but still does require forceUpdate to be called on Store change.
With Flux architecture the flow is unidirectional, which ultimately means that views will never update the store directly. The store provides state to your views. If you are trying to add, remove, update, or delete data from your views, you would trigger an action which would be dispatched to your stores. Your stores then would receive the data being dispatched, do something with that data and emit a 'change' event. All views subscribed to the store will be notified an updated accordingly. For example this is a view.
var Events = React.createClass({
getInitialState: function() {
this.store = this.props.context.getStore('EventStore');
return this.store.getState();
},
componentDidMount: function() {
this.store.addChangeListener(this._setState);
},
componentWillUnmount: function () {
this.store.removeListener('change', this._setState);
},
_setState: function () {
this.setState(this.store.getState());
}
});
You can follow this tutorial (http://facebook.github.io/flux/docs/todo-list.html) to get a better understanding, but the key and the immediate benefit to using an architecture like FLUX is the unidirectional flow.
If that the flux store is a singleton that maintains the state of the data why do the components use setState and not setProps when accessing the stores? Wouldn't it just mean that I started saving the application state in two (or more) places?
Both the Flux / React documentation and Examples seem to point to setState as the preferred solution, but I've had an interesting conversation with a few colleagues at work and wondered if anyone else came across this
Edit:
You can see what I'm talking about in this url:
https://github.com/facebook/flux/blob/master/examples/flux-chat/js/components/ThreadSection.react.js
Notice how ThreadSection is a child component, that is fetching data directly from a store and using it as a state.
If you follow the React "way" I would have expected the state to be managed by the store - not a child component.
The solution we thought of is to fetch all stores in the top level component (as props) and pass them down to the child components as needed. But that gets rather ugly rather quickly.
We do that because setProps does not work on child components
Understand that you should have 2 kinds of components. Stateful components and view components.
Stateful components can have 3 kinds of states: initial state, user input state, and data store state.
Stateful components are like small entry points in the "widget" that you're assembling. There is no single application-wide entry point anymore for downstream dependency or data injection, because all of these widgets have their own isolated lifecycles. That's why they themselves need to access & listen to stores.
Besides behavorial properties, stateful components do not receive actual data via upstream properties.
Stateful components manage their own state and pass it to their children to render through downstream properties.
Stateful components do not normally render html DOM elements themselves directly. They're more like the controllers in MVC, and use other dumber components, the ones like views in MVC, to actually render DOM elements.
Dumber components are like views so they only contain logic to render DOM elements. Think of them as handlebars.js templates that only receive properties, and simply render those into DOM elements possibly with loops etc. They are stateless renderers.
Hope this answers your question.
According to formal documentation, a store should update the parent component's state, and pass it down via his children props:
When it receives the event from the store, it first requests the new data it needs via the stores' public getter methods. It then calls its own setState() or forceUpdate() methods, causing its render() method and the render() method of all its descendants to run.
We often pass the entire state of the store down the chain of views in a single object, allowing different descendants to use what they need. In addition to keeping the controller-like behavior at the top of the hierarchy, and thus keeping our descendant views as functionally pure as possible, passing down the entire state of the store in a single object also has the effect of reducing the number of props we need to manage.
(facebook flux docs - Overview)
It makes more sense to put store data in the component's state, this is because props may change by a parent component with componentWillReceiveProps. So it makes sense to update the state whenever:
the store's change event is fired and
whenever the props change (putting derivative data related only to the component itself to the state)
Below is a sample component that updates listening to a reflux store and also on props change. I rarely use this.props in the render function, instead I amend them (create derivative data that is only used within the component itself) as new props come in. I constantly run into this pattern so might as well write this down:
var SampleComponent = React.createClass({
mixins: [Reflux.ListenerMixin],
// reusable helper function to build state object
buildStateFromProps: function(props) {
return {
actualHeight: props.height + 20
}
},
// default props if no such was set by a parent component
getDefaultProps: function() {
return {
height: 100
};
},
// initial state with all value set to something default
// even using buildStateFromProps with default props
getInitialState: function() {
// this.props is built before this.state
var state = buildStateFromProps(this.props);
// append default data from store
state.text = '';
},
// happens when the parent component send different
// props data
componentWillReceiveProps: function(nextProps) {
// building derivative data from new props
// reusing buildStateFromProps
this.setState(buildStateFromProps(nextProps));
},
// setting up store to be used by the component
componentDidMount: function() {
// this.listenTo is a helper function ListenerMixin
this.listenTo(sampleStore, sampleUpdated);
},
// is called from the sampleStore update
sampleUpdated: function(sampleData) {
this.setState({
text: sampleData.text
});
},
render: function() {
return (
// ...
// using this.state.text from store updates and
// this.state.height from prop updates
);
}
});
The reason I send props data to state is to avoid cluttering up the render function. Otherwise the render function will contain a lot of code that is not really related to "rendering" the component. Furthermore if this derivative data is used in other parts of the application then it is easy to pull it out from the component and put it into the store.
Hope this helps.
A valid answer to this question sits hidden in the comments to a previous answer:
#idolize you can also pass stores in using React contexts (a hidden,
not yet officially documented feature). It's really nice because you
don't have to do all that passing down the hierarchy. There are a few
articles about contexts, search for it online! – Andy Jul 17 '15 at
18:41