I'm looking for the best practices of defining default props for Containers (which is smart components connected with redux store), and I find out that there are at least two approaches how I can realize it.
To use initialState in my reducer:
const initialState = {
name: 'John'
};
export default function userState (state = initialState, action) {...}
To use defaultProps
User.defaultProps = {
name:'John'
};
Which one is the best and why?
You should use the initial state. The concept behind redux and every other library managing the application state is the strict separation of data/model and view. These concepts make it easier to reason about your code, reuse views and test both independently.
If you are using redux I would recommend managing you data (and your default data) inside of redux.
Example of separated test cases:
test('state test', t => {
t.deepEqual(userState(undefined, { type: '##INIT' }), { name: 'John' });
t.deepEqual(
userState({ name: 'John' }, { type: 'SET NAME', name: 'Isa' }),
{ name: 'Isa' }
);
});
test('view test', t => {
t.true(render(<User name="John" />).text().includes('John'));
});
I think you misunderstood two different concepts.
Props in containers/components is just a way to tell component how it should looks or handle some events. But default props shouldn't contain business logic and shared business data, like userInformation.
If you have data like userInformation, which important not only for User container, but also can be useful for other components, store that information only in store.
This explanation helped me to really get the difference between props and state, so I'll leave this here.
Dan Abramov, the creator of Redux, put it this way on Twitter, if I remember correctly:
Should I use component state to store X?
If I can calculate X from props -> No.
If I am not using X in the render method -> No.
Else -> Yes.
Dan was talking about component state here, rather than defaultProps, but the principle in this case is the same.
The point of Redux is to have a single source of truth for your application state. So in your example you would want to store your default values (like name: John) in the Redux store. Props are always passed from the store, but if you specify defaultProps, then you're storing application state outside of the store; it won't be managed by your reducer.
The most important thing is to stay consistent throughout your project.
I think the first approach has the advantage or storing everything related to a store in a single file. Your userState reducer is where someone would go and looks to know how it is updated based on an action type. It seems fair to go there to see how it is initiated too.
Related
I am right now working on a project in which there are quite some teams involved, and there is a little mess around where the variables are initialized and communicated to the store.
It seems like the store is mainly looking like the following.
const initialState = {
myStoreData: null
}
export default (state) => {
...
return state
}
Then later in the component the team is writing things like the following. In which they reference the variable that was poorly initialized in the store, and they are setting there the value for the prop.
function mapStateToProps(state, ownProps){
return {
someValue: state.myStoreData.someValue || '',
someOtherProperty: state.myStoreData.someOtherProperty || '',
anotherProperty: state.anotherProperty || false, // NOTE: This one doesn't exist in the store for example
};
Is there some kind of baked article that shows best practices regarding where would be the best place to keep the initial state of the application, and whether doing this kind of assignments are calling for bugs, or just simple ways to not have to modify the store initialState each time?
For me it seems like calling fro trouble, but still, I couldn't find the article backing me up
It's good to have the initialState on the reducer, so you know exactly what properties and initial values are to put in your state.
For an advanced usage of using mapStateToProps, you can check this article, React, Reselect and Redux. It uses reselect which is very performant, efficient and composable. It is very suitable for large applications with a lot of state in redux.
Hope this helps.
I'm not sure I agree with the correlation between the initial state of your store and the default value of what a UI expects, to me they are two separate problems.
For example, myStoreData.someValue could be represented as null in the store because it's never been set, but when rendering the UI null maybe doesn't make sense e.g. if you are binding that field to an input, therefore you want to swap null for '' or even a preset 'YES' / 'NO' etc. but just for the purpose of rendering - I personally don't see why this should be dictated by your data model.
In mapStateToProps you are effectively creating a view model, so it's the point where you prepare data for the view, so I don't see anything wrong with the code in your question (apart from the lack of selector use, but that's a different topic). If you simply don't like the idea of it being done at that point then you could move the logic completely into the view which keeps it self-contained, view specific, and makes it a clear intention that the incoming data should come straight from the store and any fix-ups happen at view level e.g.
class MyComponent extends React.Component {
// let the view set the expected default values for the missing props
defaultProps: {
someProperty: '',
someOtherProperty: '',
anotherProperty: false
}
}
...
function mapStateToProps(state, ownProps){
// build a view model based on the properties that you expect,
// don't include props that don't have a value or not set
const vm = {};
const { myStoreData } = state;
if (typeof myStoreData.someValue === 'string') {
vm.someValue = myStoreData.someValue;
}
if (typeof myStore.someOtherProperty === 'string') {
vm.someOtherProperty = myStoreData.someOtherProperty;
}
if (typeof myStore.anotherProperty === 'boolean') {
vm.anotherProperty = myStore.anotherProperty;
}
return vm;
}
You could leverage a validation lib like validator for simplicity, but you get the idea.
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 have redux store that looks something like this:
{
user: {},
alerts: [],
reports: [],
sourses: []
}
For each one of this parts of state i have a bunch of React Components wrapped in a container wich connected via react-redux. And has mapStateToProps like this
(state) => {alerts: state.alerts}
(state, ownProps) => {alert: _.filter(state, {id: ownProps.curId})}
Problem that when i for example launch some action for Alerts like CREATE_ALERT or EDIT_ALERT and redux state updated, ALL REACT COMPONENTS WILL RESPOND TO THIS CHANGE even ones that works with different parts like sources or reports.
My question: how to "bind" certain components to certain parts of a tree. So each container component WILL UPDATE ONLY WHEN APROPRIATE PART OF REDUX STATE UPDATED and ignore other changes.
Expected behavior
Dispatch CREATE_ALERT -> Alert reducer -> Redux store update -> ONLY Alert container component re-rendering.
When you are changing state in redux the whole state becomes just a new object.
Then your component is given by this new object (new reference) and re-renderes itself.
To fix this behaviour you need to add some logic to compare if your component got props with different value (not reference).
The easiest and fastest way is to use React.PureComponent. You can also override shouldComponentUpdate function and handle changes by yourself. But note that PureComponent works only with primitives (it does a shallow compare).
Check also Immutable.js which helps you with intelligent way of changing references of props.
if you use connect method, then pass only selected redux state to the component, this will prevent rendering of other components
example:
User Component:
const mapStateToProps = state =>({
users: state.users
});
export default connect(mapStateToProps)(User)
Alert Component:
const mapStateToProps = state =>({
alerts: state.alerts
});
export default connect(mapStateToProps)(Alert)
Check this out: Avoid Reconciliation
There explains what Neciu says.
Container components created with connect will always receive notifications of all updates to the store.
The responsibility for consuming these updates falls on the receiving connect component. It should contain the logic to extract the data relevant to it.
After watching the new egghead course by Dan Abramov, I have question regarding the selectors that was mentioned.
The purpose of the selectors is to hide the details of the state tree from the components, so that it is easy to manage code later if tree changes.
If I understand it correctly, that means, the selectors called inside mapStateToProps should only be the ones that live in the top-level reducer. Because the state that is passed to mapStateToProps is the whole application state tree. If this is true, as the application grows, I can imagine it would become very difficult to manage the top level selectors.
Have I miss understood the concept here? or is this a valid concern?
Edit: trying to make my question clearer.
Say my whole state start with
{ byIds, listByFilter } and I have
export const getIsFetching = (state, filter) =>
fromList.getIsFetching(state.listByFilter[filter]);
in my top level reducer reducers/index.js, and components would simply use getIsFetching passing the whole state to is, which is totally fine because it is the top level.
However, later on, I decided my whole app is going to contain a todo app and an counter app. So it make sense to put the current top level reducers into reducers/todo.js, and create a new top level reducers reducers/index.js like this:
combineReducers({
todo: todoReducer,
counter: counterReducer
})
at the point my state would be like
{
todo: {
byIds,
listByFilter
},
counter: {
// counter stuff
}
}
components can no longer use the getIsFetching from reducers/todo.js, because the state in getIsFetching is now actually dealing with state.todo. So i have to in the top level reducer reducers/index.js export another selector like this:
export const getIsFetching = (state, filter) =>
fromTodo.getIsFetching(state.todo);
only at this point, the component is able to use getIsFetching without worring about the state shape.
However, this raises my concern which is all the selectors directly used by components must live in the top-level reducer.
Update 2: essentially we are exporting selectors from the deepest level all the way up to the top-level reducers, while all the exports in the intermediate reducers are not using them, but they are there because the reducer knows the shape of the state at that level.
It is very much like passing props from parent all the way down to children, while the intermediate component aren't using props. We avoided this by context, or connect.
apologize for the poor English.
So while mapStateToProps does take the entire state tree, it's up to you to return what you'd like from that state in order to render your component.
For instance, we can see he calls getVisibleTodos and passes in state (and params from the router), and gets back a list of filtered todos:
components/VisibleTodoList.js
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(state, params.filter || 'all'),
});
And by following the call, we can see that the store is utilizing combineReducers (albeit with a single reducer), as such, this necessitates that he pass the applicable portion of the state tree to the todos reducer, which is, of course, state.todos.
reducer/index.js
import { combineReducers } from 'redux';
import todos, * as fromTodos from './todos';
const todoApp = combineReducers({
todos,
});
export default todoApp;
export const getVisibleTodos = (state, filter) =>
fromTodos.getVisibleTodos(state.todos, filter);
And while getVisibleTodos returns a list of todos, which by is a direct subset of the top-level state.todos (and equally named as such), I believe that's just for simplicity of the demonstration:
We could easily write another perhaps another component where there's a mapStateToProps similar to:
components/NotTopLevel.js
const mapStateToProps = (state, { params }) => ({
todoText: getSingleTodoText(state, params.todoId),
});
In this case, the getSingleTodoText still accepts full state (and an id from params), however it would only return the text of todo, not even the full object, or a list of top-level todos. So again, it's really up to you to decide what you want to pull out of the store and stuff into your components when rendering.
I also came across this issue (and also had a hard time explaining it...). My solution for compartmentalization this follows from how redux-forms handles it.
Essentially the problem boils down to one issue - where is the reducer bound to? In redux-forms they assume you set it at form (though you can change this) in the global state.
Because you've assumed this, you can now write your module's selectors to accept the globalState and return a selector as follows: (globalState) => globalState.form.someInnerAttribute or whatever you want.
To make it even more extensible you can create an internal variable to track where the state is bound to in the global state tree and also an internal function that's like getStateFromGlobalState = (globalState) => globalState[boundLocation] and uses that to get the inner state tree. Then you can change this variable programatically if you decide to bind your state to a different spot in the global state tree.
This way when you export your module's selectors and use them in mapStateToProps, they can accept the global state. If you make any changes to where the where the reducer is bound, then you only have to change that one internal function.
IMO, this is better than rewriting every nested selector in the top level. That is hard to scale/maintain and requires a lot of boilerplate code. This keeps the reducer/selector module contained to itself. The only thing it needs to know is where the reducer is bound to.
By the way - you can do this for some deeply nested states where you wouldn't necessarily be referring about this from globalState but rather some upper level node on the state tree. Though if you have a super nested state it may make more sense to write the selector from a upper state's POV.
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.