Keep data retrieved from Store within controller-view or not? - reactjs

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.

Related

React: Filtering table based on array of objects state?

So im working on a pretty simple personal project to learn react. The state within my "SensorTable" component (Which is essentially just a react-bootstrap table currently that has a listing of my sensors (Which have values like id/temp/humidity/etc...).
Right now this components state is just an array of objects (That being my sensors) that I retrieve via axios/fetch from a GET request to JSON API that has all these objects. The GET request is made on componentDidMount.
Im really not fully comfortable with redux at this point, and this is simply a small toy app to get acquainted with React. But would I be correct in guessing the correct way to sort/filter this table would be to have a function that:
Retrieves the latest data from the API (Maybe?)
Updates the state either by sorting the array of objects in a specific order OR filtering them based off whatever filter type I want to use (Maybe filter by sensors that have a specific object key that is above or below or certain value). And then returns the new array to that state?
This sounds simple in theory to me...but wouldn't the componentDidMount just fire again once this function happens (resetting the data to how it was originally?). Or is there a way to make it NOT re-fire that method if I update the state in one of these filter/sorting functions?
componentDidMount only fires when the component is initialized and not on further renders, so your request will only fire once until you unmount and remount the component (by navigating away from and returning to the page, for example).
As for updating state, let's say you store the response data in a state variable called sensorData. If you set sensorData every time the user applies a filter, you are losing data, because you are effectively removing items from the sensorData array. This means that when the filter is removed, a refetch is required to regain the data you filtered out.
A better approach would be to store all the response data in sensorData, and apply the filter inline when rendering the table.
For example:
{sensorData.filter(d => d.someProp === "someVal").map(d => <TableRowItem />)}
This way, when removing the filter, you do not have to hit the server again and the UI is updated immediately.
In the simplest component case, yes, make API calls (or any number of side-effects) in the componentDidMount lifecycle function. componentDidMount is called only once when the component is mounted. componentDidUpdate however, is called upon any changes to state or props.
A sample component could look like this:
class MyComponent extends Component {
this.state = {
data: [];
};
componentDidMount() {
getDataFromAPI().then(
response => setState({
data: response.data.filter(someFilterFn) // filter result array and store in state
});
);
}
render() {
<div>
{this.state.data.map(mapElementFn)}
</div>
}
}
This is just an example, you could also just store all the data in state and filter (as needed) in the render function. It's really up to you and your app's needs.
componentDidMount triggers only ones.
Using state, you can maintain two keys one with intialList and modifiedList after filter / sort.
Local sort is based on normal js array sort.
ref : https://moduscreate.com/blog/ext-js-to-react-load-sort-and-filter-data-with-react/
You can filter in your local or rely on remote thing
When you use remote api calls to sort, then you need to use an event handler and invoke the fetch call explicity.
Ref: https://codereviewvideos.com/course/pagination-filtering-and-sorting/video/react-sorting
Ref: Sample https://github.com/codereviewvideos/react-symfony-3.example/blob/pagination-sort-filter/src/containers/list.js
Same applies for filter as well.

Statefull components interaction with store that might be async

I’m working on a React Flux project where async interaction is an ongoing thing and I’m still a bit wonky when it comes to the react lifecycle.
In my statefull components I have a getStateFromStores function, if there is no state in store, this returns a false bool, otherwise it returns the state.
As I have it right now the cycle is as following:
I have one bound function (ES6 react class) in my components called getState(), this simply invokes getStateFromStores and checks that the return is not false, and if not, invokes setState() with the state.
Since notifications of state change can come from different sources, I like to declare the logic in one place (getState)
State change can come from the following:
ComponentDidMount (should I move this to initialState?)
This is the first thing that happens in the components lifecycle. (And unless state already exists in our store, we get false in return)
ComponentWillReceiveProps
If state is changed in parent component, we notify the component that it should fetch new state with the new props.
Store change listener
When store has new state, getState is invoked as the callback (although a bit problematic, because emit can happened for reasons other than as a consequence of the given component’s call – so I might implement an onStoreChange as the callback who in turn calls getState)
Im toying around with a state prop called fetching which I can use to control the flow, but is this problematic? – because every time I change state it triggers a render (?) — could I use static for this?
I imagine something of the following; DidMount triggers getState(), getState sets fetching to true, and now all other invoke-tries will fail because of the bool check.
Then I’d implement a resetState, that brings back the state to default (fetching false) and then getState can be triggered again.
I’d abstract all this functionality in a “Higher-Order component”
Thanks!
I take a different approach. I've found that one of the great advantages to writing React components is that you're able to think of the component as a self-contained unit -- with its own HTML, JavaScript, data-fetching -- even styles. It relies on Flux stores to keep track of data, as opposed to passing that data as props.
Suppose you have a user log in and receive back some information. Now, let's say the user wants to manage their account. We could do something like this...
<Account user_info={this.state.user_info} />
That will work fine, but can't the Account get the user_info it needs if it knows the user_id? That would lead us to...
<Account user_id={this.state.user_info.user_id}>
But, really, doesn't the store know who the logged-in user is? After all, it was integral in getting/storing that data. So why not just...
<Account />
...and let Account get the data it needs from the store -- no user_id needed, since the store already knows the logged-in user.
Anyway, just a different way to think about things.
What I've found working really well for me in most cases is to have a root "controller component". Let's call it Controller for now. It's basically a React component just as any other – but its responsibility differs a bit.
I see it as solely delegating. It's only responsibility is to get data (state) from the stores and pass it down (as props) to its child components and let them do all the work.
This is done by setting the data from the stores as the Controller's state. Any changes in the stores are reported back to (only) the Controller, which updates its state. Since the state is passed down as props to the children, a change in a store will trigger all child components using that data to re-render (if they have to).
I try to pass down only relevant data to the child components, but don't worry to much about them getting to much data (or about some extra re-rendering).
In practice the Controller would look something like this:
function getStateFromStores() {
return {
players: PlayerStore.getData(),
cards: CardStore.getData()
};
}
var Controller = React.createClass({
getInitialState: function() {
return getStateFromStores();
},
componentDidMount: function() {
PlayerStore.addChangeListener(this._onChange);
CardStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
PlayerStore.removeChangeListener(this._onChange);
CardStore.removeChangeListener(this._onChange);
},
_onChange: function() {
this.setState(getStateFromStores());
},
render: function() {
return (
<Component1
players={this.state.players}
cards={this.state.cards}
/>
<Component2
players={this.state.players}
/>
<Component3
cards={this.state.players}
/>
);
}
});
module.exports = Controller;

How to add and load an object in a store using React Flux on component creation

Question
When I load a React Component, how I can add an object to a store, and then immediately load it into the component's state using the Flux pattern?
Background
I have a Flux store (TableStore) that manages objects called Tables, and React components called TableList and TableEditor.
The TableList component lists the tables in the store, and each item has an edit button that navigates to /tables/editor/:tableId, which loads the TableEditor component, and TableEditor gets the table from the store via the tableId in the URL.
TableList also has a "Create new table" button. Which navigates to /tables/editor/new. When this URL is loaded, I need to add a new Table to the store and then load it into TableEditor's state.
Using the Flux pattern, components aren't supposed to call functions like this directly; they're supposed to call Actions which dispatch actions to the Store. Unfortunately, if an action is called in a component's getInitialState(), the function in the store is executed before componentWillMount() is executed in the component, and the component misses the change event from the store, so my TableEditor component doesn't load the table from TableStore.
I assume this is a pretty common need, so there must be a standard way of getting around this (I hope).
Any ideas?
I think you are making a bit of confusion with the flux pattern.
When I load a React Component, how I can add an object to a store, and
then immediately load it into the component's state using the Flux
pattern?
how I can add an object to a store?
You should always add an object to store emitting a new create action. The store register itself to the dispatcher to listen to actions. When a action is dispatched, an handler is called to perform that action. This is the only way to mutate the state of a store. For example, in your case, the view should emit a TABLE_CREATE action. The TableStore should list for table TABLE_CREATE and save the new table. A bit of code:
Table List View
onSave: function(table) {
TableActions.create(table);
}
Table Actions
var TodoActions = {
create: function(text) {
AppDispatcher.handleTableAction({
actionType: TableConstants.TABLE_CREATE,
table:table
});
}
Table Store
switch(action.actionType) {
case TableConstants.TABLE_CREATE:
text = action.table();
if (!isEmpty(table)) {
create(table);
TableStore.emitChange();
}
break;
As you can see, the above code is following the flux pattern:
VIEW --(Action)--> DISPATCHER --(Action)--> STORE
Immediately load it into the component's state using the Flux pattern?
You fetch it directly from the store. We can read the stores from the views as we are not performing any state mutation. State mutation should be propagate by means of actions. Therefore, your component should use the function getInitialState to read the value from the TableStore. Probably, you need to use some sort of id. Code:
getInitialState() {
return {
table : TableStore.getTableById(id)
}
}
According to what you wrote, you have a store which contains all tables.
When a user clicks the button create a new action should be dispatched to the table store in order to add the new table to the store. The table store should be listening for this action. Now, the user is likely redirect to the editor page where he can edit the newly table. The edit table view fetches the value from the store in the getInitialState and reders the edit. Make sense?
One solution you could try is to always keep a new and empty table object (or just a 'free' table ID) in your table store.
Couple the NEW button (or whatever it is that user does to create a new table) to a /tables/editor/:tableId that holds your new empty Id.
That way, your do not need a fancy redirect from /new to /:tableId etc.

Flux actions for UI events

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

React + Flux - should store data be stored in a component state, or props?

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

Resources