say I have a component connected to the redux store. Within this store, there's a list of objects. For instance something like this:
ReduxStore: {
dataList: [
{name:'bla'},
{name:'blub'},
]
}
Is it actually an anti pattern to create and add a filter function within the reducer to create something like this:
ReduxStore: {
dataList: {
data:[
{name:'bla'},
{name:'blub'}
],
isNameAvailable: (name) => {/* search for name */}
}
}
It works great, but I'm not sure whether this was the intended way to go.
It's an anti-pattern because your store only cares about data, not computation. Once you start adding functions to your store, you lose the ability to serialize the data inside it.
However, it's pretty trivial to pull these kinds of helper functions out and turn them into standalone utility selectors which you can use to achieve the same thing.
function isNameAvailable(store, name) {
/* search for `name` in `store` */
}
Now the function works independently of the store and you can keep them separate for testing.
From here, you can take a look at Reselect which allows you to turn your isNameAvailable function into a cached selector, meaning you'll only need to re-calculate as and when the appropriate data in the store is changed.
From Redux documentation:
It’s very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
so I think if you want to declare a filter function isNameAvailable, in my opinion, you have two options:
use the connect function to filter
// the solution of connect
import React, { Component } from 'react';
import { connect } from 'react-redux';
const App = () => (
<div>the example</div>
);
function isNameAvailable(state) {
// your code
}
function mapStateToProps(state) {
return isNameAvailable(state)
}
module.exports = connect(mapStateToProps)(App);
declare a filter function in action
In my experience,I think the first one is better. I hope this can help you.
Related
I am making a game editor. All it needs is a way to save and read data from a common store, such as sprites and tool settings.
The problem is Vuex just seems really messy to me. Maybe it's because I'm not building a standard SPA which Vuex was designed for, but it just seems that every time I want to do something simple it adds 50+ lines of code in getters, actions, and mutations that would otherwise be unnecessary. On top of that, it has limitations such as not being able to modify states from getters which would be really helpful when generating unique asset IDs. I also have no need for the dynamic loading/unloading of modules.
So my question, if I replaced Vuex with an imported object like the following:
class MyStore_Class{
constructor(){
this.val = 0;
}
//other methods and stuff to manipulate data
}
let MyStore = new MyStore();
export default MyStore;
Then imported this MyStore object into the components where I needed it, would I lose anything?
I ran some simple tests and it seems like it works perfectly as a drop in replacement for Vuex, but I'm afraid there might be some kind of downside that I would notice only later down the line.
EXIT: Pretty much all data for the app is local, so the separation of actions/mutations tends to mean that the only action code I am writing is commit('doMutation', newData) over and over again
Your solution could be a vue observable, really easy to do and lightweight in term of architecture ;)
Create a store.js file in your src/root folder
Create the state value/s you wish to have globally
Create the methods you needs for his interaction
Set it up in your components and there you go
Setup store.js
import Vue from "vue";
const state = Vue.observable({ val: 0 });
export const increment = () => state.counter++;
export const decrement = () => state.counter--;
export default state;
In your component
<template>
<div>
<p>The value is {{val}}</p>
<button #click="inc">+</button>
<button #click="dec">-</button>
</div>
</template>
<script>
import store, { increment, decrement } from "./store";
export default {
computed: {
// your getter in some way
counter() {
return store.counter;
}
},
methods: {
inc() {
increment();
},
dec() {
decrement();
}
}
};
</script>
I took these examples on this article, where you could read more about vue observable if you want, but i use it a lot on small projects where i need just few values accessible globally and that doesn't require a vuex architecture.
https://medium.com/better-programming/how-to-manage-vues-state-with-vue-observable-25988a88938b
In React-Redux project, people usually create multiple actions & reducers for each connected component. However, this creates a lot of code for simple data updates.
Is it a good practice to use a single generic action & reducer to encapsulate all data changes, in order to simplify and fasten app development.
What would be the disadvantages or performance loss using this method. Because I see no significant tradeoff, and it makes development much easier, and we can put all of them in a single file! Example of such architecture:
// Say we're in user.js, User page
// state
var initialState = {};
// generic action --> we only need to write ONE DISPATCHER
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer --> we only need to write ONE ACTION REDUCER
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// define component
var User = React.createClass({
render: function(){
// Here's the magic...
// We can just call the generic setState() to update any data.
// No need to create separate dispatchers and reducers,
// thus greatly simplifying and fasten app development.
return [
<div onClick={() => setState({ someField: 1 })}/>,
<div onClick={() => setState({ someOtherField: 2, randomField: 3 })}/>,
<div onClick={() => setState({ orJustAnything: [1,2,3] })}/>
]
}
});
// register component for data update
function mapStateToProps(state){
return { ...state.user };
}
export default connect(mapStateToProps)(User);
Edit
So the typical Redux architecture suggests creating:
Centralized files with all the actions
Centralized files with all the reducers
Question is, why a 2-step process? Here's another architectural suggestion:
Create 1 set of files containing all the setXField() that handle all the data changes. And other components simply use them to trigger changes. Easy. Example:
/** UserAPI.js
* Containing all methods for User.
* Other components can just call them.
*/
// state
var initialState = {};
// generic action
function setState(obj){
Store.dispatch({ type: 'SET_USER', data: obj });
}
// generic reducer
function userReducer = function(state = initialState, action){
switch (action.type) {
case 'SET_USER': return { ...state, ...action.data };
default: return state;
}
};
// API that we export
let UserAPI = {};
// set user name
UserAPI.setName = function(name){
$.post('/user/name', { name }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ name });
});
};
// set user picture URL
UserAPI.setPicture = function(url){
$.post('/user/picture', { url }, function({ ajaxSuccess }){
if (ajaxSuccess) setState({ url });
});
};
// logout, clear user
UserAPI.logout = function(){
$.post('/logout', {}, function(){
setState(initialState);
});
};
// Etc, you got the idea...
// Moreover, you can add a bunch of other User related methods,
// like some helper methods unrelated to Redux, or Ajax getters.
// Now you have everything related to User available in a single file!
// It becomes much easier to read through and understand.
// Finally, you can export a single UserAPI object, so other
// components only need to import it once.
export default UserAPI
Please read through the comments in the code section above.
Now instead of having a bunch of actions/dispatchers/reducers. You have 1 file encapsulating everything needed for the User concept. Why is it a bad practice? IMO, it makes programmer's life much easier, and other programmers can just read through the file from top to bottom to understand the business logic, they don't need to switch back and forth between action/reducer files. Heck, even redux-thunk isn't needed! And you can even test the functions one by one as well. So testability is not lost.
Firstly, instead of calling store.dispatch in your action creator, it should return an object (action) instead, which simplifies testing and enables server rendering.
const setState = (obj) => ({
type: 'SET_USER',
data: obj
})
onClick={() => this.props.setState(...)}
// bind the action creator to the dispatcher
connect(mapStateToProps, { setState })(User)
You should also use ES6 class instead of React.createClass.
Back to the topic, a more specialised action creator would be something like:
const setSomeField = value => ({
type: 'SET_SOME_FIELD',
value,
});
...
case 'SET_SOME_FIELD':
return { ...state, someField: action.value };
Advantages of this approach over your generic one
1. Higher reusability
If someField is set in multiple places, it's cleaner to call setSomeField(someValue) than setState({ someField: someValue })}.
2. Higher testability
You can easily test setSomeField to make sure it's correctly altering only the related state.
With the generic setState, you could test for setState({ someField: someValue })} too, but there's no direct guarantee that all your code will call it correctly.
Eg. someone in your team might make a typo and call setState({ someFeild: someValue })} instead.
Conclusion
The disadvantages are not exactly significant, so it's perfectly fine to use the generic action creator to reduce the number of specialised action creators if you believe it's worth the trade-off for your project.
EDIT
Regarding your suggestion to put reducers and actions in the same file: generally it's preferred to keep them in separate files for modularity; this is a general principle that is not unique to React.
You can however put related reducer and action files in the same folder, which might be better/worse depending on your project requirements. See this and this for some background.
You would also need to export userReducer for your root reducer, unless you are using multiple stores which is generally not recommended.
I mostly use redux to cache API responses mostly, here are few cases where i thought it is limited.
1) What if i'm calling different API's which has the same KEY but goes to a different Object?
2) How can I take care if the data is a stream from a socket ? Do i need to iterate the object to get the type(as the type will be in the header and response in the payload) or ask my backend resource to send it with a certain schema.
3) This also fails for api's if we are using some third party vendor where we have no control of the output we get.
It's always good to have control on what data going where.In apps which are very big something like a network monitoring application we might end up overwriting the data if we have same KEY and JavaScript being loosed typed may end this to a lot weird way this only works for few cases where we have complete control on the data which is very few some thing like this application.
Okay i'm just gonna write my own answer:
when using redux ask yourself these two questions:
Do I need access to the data across multiple components?
Are those components on a different node tree? What I mean is it isn't a child component.
If your answer is yes then use redux for these data as you can easily pass those data to your components via connect() API which in term makes them containers.
At times if you find yourself the need to pass data to a parent component, then you need to reconsider where your state lives. There is a thing called Lifting the State Up.
If your data only matters to your component, then you should only use setState to keep your scope tight. Example:
class MyComponent extends Component {
constructor() {
super()
this.state={ name: 'anonymous' }
}
render() {
const { name } = this.state
return (<div>
My name is { name }.
<button onClick={()=>this.setState({ name: 'John Doe' })}>show name</button>
</div>)
}
}
Also remember to maintain unidirectional data flow of data. Don't just connect a component to redux store if in the first place the data is already accessible by its parent component like this:
<ChildComponent yourdata={yourdata} />
If you need to change a parent's state from a child just pass the context of a function to the logic of your child component. Example:
In parent component
updateName(name) {
this.setState({ name })
}
render() {
return(<div><ChildComponent onChange={::this.updateName} /></div>)
}
In child component
<button onClick={()=>this.props.onChange('John Doe')}
Here is a good article about this.
Just practice and everything will start to make sense once you know how to properly abstract your app to separate concerns. On these matter composition vs ihhertitance and thinking in react are a very good read.
I started writing a package to make it easier and more generic. Also to improve performance. It's still in its early stages (38% coverage). Here's a little snippet (if you can use new ES6 features) however there is also alternatives.
import { create_store } from 'redux';
import { create_reducer, redup } from 'redux-decorator';
class State {
#redup("Todos", "AddTodo", [])
addTodo(state, action) {
return [...state, { id: 2 }];
}
#redup("Todos", "RemoveTodo", [])
removeTodo(state, action) {
console.log("running remove todo");
const copy = [...state];
copy.splice(action.index, 1);
return copy;
}
}
const store = createStore(create_reducer(new State()));
You can also even nest your state:
class Note{
#redup("Notes","AddNote",[])
addNote(state,action){
//Code to add a note
}
}
class State{
aConstant = 1
#redup("Todos","AddTodo",[])
addTodo(state,action){
//Code to add a todo
}
note = new Note();
}
// create store...
//Adds a note
store.dispatch({
type:'AddNote'
})
//Log notes
console.log(store.getState().note.Notes)
Lots of documentation available on NPM. As always, feel free to contribute!
A key decision to be made when designing React/Redux programs is where to put business logic (it has to go somewhere!).
It could go in the React components, in the action creators, in the reducers, or a combination of those. Whether the generic action/reducer combination is sensible depends on where the business logic goes.
If the React components do the majority of the business logic, then the action creators and reducers can be very lightweight, and could be put into a single file as you suggest, without any problems, except making the React components more complex.
The reason that most React/Redux projects seem to have a lot of files for action creators and reducers because some of the business logic is put in there, and so would result in a very bloated file, if the generic method was used.
Personally, I prefer to have very simple reducers and simple components, and have a large number of actions to abstract away complexity like requesting data from a web service into the action creators, but the "right" way depends on the project at hand.
A quick note: As mentioned in https://stackoverflow.com/a/50646935, the object should be returned from setState. This is because some asynchronous processing may need to happen before store.dispatch is called.
An example of reducing boilerplate is below. Here, a generic reducer is used, which reduces code needed, but is only possible the logic is handled elsewhere so that actions are made as simple as possible.
import ActionType from "../actionsEnum.jsx";
const reducer = (state = {
// Initial state ...
}, action) => {
var actionsAllowed = Object.keys(ActionType).map(key => {
return ActionType[key];
});
if (actionsAllowed.includes(action.type) && action.type !== ActionType.NOP) {
return makeNewState(state, action.state);
} else {
return state;
}
}
const makeNewState = (oldState, partialState) => {
var newState = Object.assign({}, oldState);
const values = Object.values(partialState);
Object.keys(partialState).forEach((key, ind) => {
newState[key] = values[ind];
});
return newState;
};
export default reducer;
tldr It is a design decision to be made early on in development because it affects how a large portion of the program is structured.
Performance wise not much. But from a design perspective quite a few. By having multiple reducers you can have separation of concerns - each module only concerned with themselves. By having action creators you add a layer of indirection -allowing you to make changes more easily. In the end it still depends, if you don't need these features a generic solution helps reduce code.
First of all, some terminology:
action: a message that we want to dispatch to all reducers. It can be anything. Usually it's a simple Javascript object like const someAction = {type: 'SOME_ACTION', payload: [1, 2, 3]}
action type: a constant used by the action creators to build an action, and by the reducers to understand which action they have just received. You use them to avoid typing 'SOME_ACTION' both in the action creators and in the reducers. You define an action type like const SOME_ACTION = 'SOME_ACTION' so you can import it in the action creators and in the reducers.
action creator: a function that creates an action and dispatches it to the reducers.
reducer: a function that receives all actions dispatched to the store, and it's responsible for updating the state for that redux store (you might have multiple stores if your application is complex).
Now, to the question.
I think that a generic action creator is not a great idea.
Your application might need to use the following action creators:
fetchData()
fetchUser(id)
fetchCity(lat, lon)
Implementing the logic of dealing with a different number of arguments in a single action creator doesn't sound right to me.
I think it's much better to have many small functions because they have different responsibilities. For instance, fetchUser should not have anything to do with fetchCity.
I start out by creating a module for all of my action types and action creators. If my application grows, I might separate the action creators into different modules (e.g. actions/user.js, actions/cities.js), but I think that having separate module/s for action types is a bit overkill.
As for the reducers, I think that a single reducer is a viable option if you don't have to deal with too many actions.
A reducer receives all the actions dispatched by the action creators. Then, by looking at the action.type, it creates a new state of the store. Since you have to deal with all the incoming actions anyway, I find it nice to have all the logic in one place. This of course starts to be difficult if your application grows (e.g. a switch/case to handle 20 different actions is not very maintainable).
You can start with a single reducer, the move to several reducers and combine them in a root reducer with the combineReducer function.
I'm having a little conceptual difficulty with a certain aspect of the React/Flux architecture, I know, crazy, right. It has to do with how a Container should pass the Store to a Component, and how the Component should read from the Store, which as far as I see are interdependent.
As an example - I have a simple chart which updates the x and y range depending on changes to a form.
I have a simple Store, updated from Dispatch events, "XRANGE_CHANGE" and "YRANGE_CHANGE", of an Action.
import Immutable from "immutable";
import { ReduceStore } from "flux/utils";
import Dispatcher from "../Dispatch";
class ChartStore extends ReduceStore {
constructor() {
super(Dispatcher);
}
getInitialState() {
return Immutable.OrderedMap({
xRange: [],
yRange: []
});
}
reduce(state, action) {
switch(action.type) {
case "XRANGE_CHANGE":
return state.set("xRange", action.item);
case "YRANGE_CHANGE":
return state.set("yRange", action.item);
default:
console.error("Action type not found");
return state;
}
}
}
export default new ChartStore();
And a Container, which will pass this Store to the Chart component;
import React from "react";
import { Container } from "flux/utils";
import ChartAction from "./ChartAction";
import ChartStore from "./ChartStore";
import Chart from "./Component";
class ExampleContainer extends React.Component {
static calculateState() {
const chartStore = ChartStore.getState(),
xRange = chartStore.get("xRange"),
yRange = chartStore.get("yRange")
return {
xRange: xRange,
yRange: yRange,
xChange: ChartAction.xChange,
yChange: ChartAction.yChange
};
}
static getStores() {
return [ChartStore];
}
render() {
const state = this.state;
return <Chart
// actions
xChange={state.xChange}
yChange={state.yChange}
// !!!!! here's where my confusion lies !!!!!
//store={state.chartStore}
// ammended
xRange={state.xRange}
yRange={state.yRange}
/>
</div>;
}
}
export default Container.create(FinanceContainer);
The commented exclamation marks above indicate where I lose track of the "accepted" React way of doing things.I'm not quite sure of the best way to pass the Store to the Chart component, which will dictate how I read the Store within the component. I have a few options as far as I see, all work but could be completely wrong.
As above, I pass the entire store to the Component and in the Components' render function read store.get("xRange") or store.get("yRange")
In the Container I define xRange={chartStore.get("xRange")} etc.
In Either the Container or the Component I perform store.toJSON()/toObject() and read directly from the result.
I could be completely way off the mark with any of these scenarios. Or any of these ways could be fine.
Any advice would be appreciated. As I continue on I'd like to know I'm carrying out a sensible procedure. Thanks in advance.
As you've noticed, this isn't something with a definitive answer. But I think a good way of determining the "right" methodology is by looking at libraries that are written "for flux" and how they handle these problems. In particular, I would take a look at Redux (a flux implementation) and Reselect (an extension of Redux that addresses this issue further).
The pattern that these libraries use is essentially that your calculateState method ought to transform the flux state into the relevant state information for that container. It should grab relevant information (e.g. state.get('xRange')) as well as possibly performing tranformations on the data held in state if helpful (e.g. range: {x: state.get('xRange'), y: state.get('yRange')}).
As with most things in the flux pattern, the idea here is to provide a definitive "source of truth". You want every sub-component to interpret the flux state in the same way, and you want to have a single method to modify, should the data need to be computed differently. By doing an ETL of the flux state into the container, you achieve that. Should there be some future change to which piece of the flux state is needed for this section of your app, you would merely need to modify this calculateState method, as opposed to all lower usages of that data.
I am learning a react/redux and have an application with two main pieces of state:
An array of items
An object that contains user-specified filters for those items
I have three functions/actions, createFilter, updateFilter, and deleteFilter that modify the state of #2. I have an action filterItems that modifies #1 based on the state of #2. So whenever #2 changes, this action needs to be dispatched.
This is the component I am working with:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { createFilter } from '../actions/actions'
import { updateFilter } from '../actions/actions'
import { deleteFilter } from '../actions/actions'
import { filterItems } from '../actions/actions'
class ItemList extends Component {
createFilter(input) {
this.props.createFilter(input)
this.props.filterItems()
}
updateFilter(input) {
this.props.updateFilter(input)
this.props.filterItems()
}
deleteFilter() {
this.props.deleteFilter()
this.props.filterItems()
}
...
// Render method
...
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ createFilter, updateFilter, deleteFilter, filterItems }, dispatch)
}
function mapStateToProps({ itemList }) {
return { itemList }
}
export default connect(mapStateToProps, mapDispatchToProps)(ItemList)
What I have found is that when one of the filter methods are sent, the store (state #2) is not yet updated by the time filterItems() is called.
So I need to asynchronously execute the filter functions, and once the store is updated call filterItems.
I am struggling on how to do this with react-thunk. If the first function was an ajax promise I would use .then():
export function updateFilterAndEvaluate(input) {
return (dispatch, getState) => {
updateFilter(input).then(dispatch(filterItems(getState().filters)))
}
}
But these are just functions, and don't have a .then() method. I am trying to figure out what my best course of action is for this implementation. Can I wrap Redux actions in a promise? Am I misusing Thunk? Or should I attempt a different pattern entirely?
I have an action filterItems that modifies #1 based on the state of #2.
This is, generally speaking, an anti-pattern. Since the result array can be computed from the source array and the currently active filters, you shouldn’t be keeping it in the state.
Redux actions should generally look like “events” (e.g. what happened). “Filter was created” and “filter was updated” are good actions. “Filter them now!” looks more like a command, this is usually a sign that it shouldn’t have been an action in the first place, and should be something the components do as they select the data to render.
Instead, do the filtering as part of your mapStateToProps() function when you prepare data for the components. If it gets expensive, look into using Reselect to compute derived data efficiently.
As for your specific question,
What I have found is that when one of the filter methods are sent, the store (state #2) is not yet updated by the time filterItems() is called.
This is incorrect and indicates some other problem in your code. (It’s hard to tell where because the example is incomplete). In Redux, dispatch() is synchronous (unless you have some middleware that delays or batches it which usually isn’t the case), so you don’t need to “wait” for it to finish if it just operates on the local data.
However, in any case, filterItems() is not a very good fit for an action, and I suggest you to look into filtering in mapStateToProps() as I wrote above.
I'm working on a page whose 'Data Model' is a collection, for example, an array of people. They are packed into React Components and tiled on the page. Essentially it's like:
class App extends React.Component {
constructor() {
super();
this.state = { people: /* some data */ };
}
render () {
return (
<div>
{this.state.people.map((person) =>
<People data={person}></People>)}
</div>);
}
}
Now I want to attach an edit section for each entry in <People> component, which allows the user to update the name, age ... all kinds of information for a specific entry.
Since React does not support mutating props inside components, I searched and found that adding callbacks as props can solve the problem of passing data to parent. But since there are many fields to update, there would be many callbacks such as onNameChanged, onEmailChanged... which could be very ugly (also more and more verbose as the number of fields keeps growing).
So what is the right way for it?
Honestly? The best way is Flux (back to that in a minute).
If you start to get into the process of passing data down the tree in the form of props, then passing it back up to be edited using callbacks, then you're breaking the unidirectional data flow that React is built around.
However, not all projects need to be written to ideal standards and it is possible to build this without Flux (and sometimes it might even be the right solution).
Without Flux
You can implement this without the need for a mass of callbacks, by passing down a single edit function as a prop. This function should take an id and a new person object, then update the state inside the parent component whenever it runs. Here's an example.
editPerson(id, editedPerson) {
const people = this.state.people;
const newFragment = { [id]: editedPerson };
// create a new list of people, with the updated person in
this.setState({
people: Object.assign([], people, newFragment)
});
},
render() {
// ...
{this.state.people.map((person, index) => {
const edit = this.editPerson.bind(this, index);
return (
<People data={person} edit={edit}></People>
);
})}
// ...
}
Then inside your person component, any time you make a change to the person, simply pass the person back up to the parent state with the callback.
However, if you visualize the flow of data through your application, you've now created a cycle that looks something like this.
App
^
|
v
Person
It's no longer trivial to work out where the data in app came from (it is still quite simple in such a small app, but obviously the bigger it gets the harder it is to tell.
With Flux
In the beginning, Facebook developers wrote React applications with unidirectional data flows and they saw that it was good. However, a need arose for data to go up the tree, which resulted in a crisis. How shall our data flow be unidirectional and still return to the top of the tree? And on the seventh day, they created Flux(1) and saw that it was very good.
Flux allows you to describe your changes as actions and pass them out of your components, to stores (self contained state boxes) which understand how to manipulate their state based on the action. Then the store tells all the components that care about it that something has changed, at which point the components can fetch new data to render.
You regain your unidirectional data flow, with an architecture that looks like this.
App <---- [Stores]
| ^
v |
Person --> Dispatcher
Stores
Rather than keeping your state in your <App /> component, you would probably want to create a People store to keep track of your list of people.
Maybe it would look something like this.
// stores/people-store.js
const people = [];
export function getPeople() {
return people;
}
function editPerson(id, person) {
// ...
}
function addPerson(person) {
// ...
}
function removePerson(id) {
// ...
}
Now, we could export these functions and let our components call them directly, but that's bad because it means that our components have to have knowledge of the design of the store and we want to keep them as dumb as possible.
Actions
Instead, our components create simple, serializable actions that our stores can understand. Here are some examples:
// remove person with id 53
{ type: 'PEOPLE_REMOVE', payload: 53 }
// create a new person called John Foo
{ type: 'PEOPLE_ADD', payload: { name: 'John Foo' } }
// edit person 13
{
type: 'PEOPLE_EDIT',
payload: {
id: 13,
person: { name: 'Unlucky Bill' }
}
}
These actions don't have to have these specific keys, they don't even have to be objects either, this is just the convention from Flux Standard Actions.
Dispatcher
Now, we have tell our store how to deal with these actions when they arrive.
// stores/people-store.js
// ...
dispatcher.register(function(action) {
switch(action.type) {
case 'PEOPLE_REMOVE':
removePerson(action.payload);
case 'PEOPLE_ADD':
addPerson(action.payload);
case 'PEOPLE_EDIT':
editPerson(action.payload.id, action.payload.person);
}
});
Phew. Lot of work so far, nearly there.
Now we can start to dispatch these actions from our components.
// components/people.js
// ...
onEdit(editedPerson) {
dispatcher.dispatch({
type: 'PEOPLE_EDIT',
payload: {
id: this.props.id,
person: editedPerson
}
});
}
onRemove() {
dispatcher.dispatch({
type: 'PEOPLE_REMOVE',
payload: this.props.id
});
}
// ...
When you edit the person, call the this.onEdit method and it will dispatch the appropriate action to your stores. Same goes for removing a person. Normally you'd move this stuff into action creators, but that's a topic for another time.
Ok, finally getting somewhere! Now our components can create actions that update the data in our stores. How do we get that data back into our components?
Initially, it's very simple. We can require the store in our top level component and simply ask for the data.
// components/app.js
import { getPeople } from './stores/people-store';
// ...
constructor() {
super();
this.state = { people: getPeople() };
}
We can pass this data down in exactly the same way, but what happens when the data changes?
The official stance from Flux is basically "Not our problem". Their examples use Node's Event Emitter class to allow stores to accept callback functions that are called when the store updates.
This allows you to write code that looks something like this:
componentWillMount() {
peopleStore.addListener(this.peopleUpdated);
},
componentWillUnmount() {
peopleStore.removeListener(this.peopleUpdated);
},
peopleUpdated() {
this.setState({ people: getPeople() });
}
Really, the ball is in your court on this one. There are many other strategies for getting the data back into your program. Reflux creates the listen method for you automatically, Redux allows you to declaratively specify which components receive which parts of the store as props, then it handles the updating. Spend enough time with Flux and you'll find a preference.
Now, you're probably thinking, blimey — this seems like a lot of effort to go to just to add edit functionality to a component; and you're right, it is!
For small applications, you probably don't need Flux.
Sure there are lots of benefits, but the additional complexity just isn't always warranted. As your application grows, you'll find that if you've fluxed it up, it will be much easier to manage, maintain and debug.
The trick is to know when it's appropriate to use the Flux architecture and hopefully when the time comes, this overly long, rambling answer will have cleared things up for you.
This isn't actually true.