I want to ask the community about an ideological problem.
Lets imagine todo-list on react/redux, you have single state where todoItems array is served. But now lets imagine I want to have few components on the page that are render todoItems with different UI. And I need to update each these components on CRUD of todoItems. What is your architectural approach of this issue? Don't forget we have a large database and we can get todoItems with pagination only.
Update:
Lets make it clear. When we implement redux life cycle with this UI we have 2 options:
1) Serve one array of todoItems into singleton redux state object.
Advantages: all our components will updates by object changing.
Problems: we can't get ALL data from our database, but have to show different paginated/filtered data, so we can't implement pagination/filtering on frontend-side. We have a few different components and the have to render different objects collection. So it doesn't fit.
2) We can use different keys into our global redux state.
Advantages: we can independently get data for each component
Problems: other components will not feel when object changing in one of them. In this case we have to write custom code.
I just want to know maybe I'm missing something and we have other option or maybe someone have good architectural approach to this problem.
I bet your complications come from the point of view which unfortunately quite common among redux community: trying to keep redux shape as close to UI shape as possible.
Try no to think about redux state as a substitute for the Component states. What redux should know about is actual todos only (id, title, date of creation, etc.). Let Component-specific data like pagination stuff live in Components state. When user goes to next page in one of the Components what should be updated is this Component state (pageNumber, from, to, amount, etc.). redux should be updated only in case necessary todos are missing.
The useful analogy is to thinking about your redux as good old SQL-database: redux store state is data itself, selectors and actions are queries and stored procedures, React Components are views with selected data.
Update: Ok, seems like what you are looking for is state normalization. Separate todos details from the lists of ids. This way updates of todo fields will be sensed by all the Components. On the other hand you'll be able to keep separate collections of todos in different Components. Namely make state look like this:
{
funnyTodos: [ 'id1', 'id2' ],
boringTodos: [ 'id3', 'id4' ],
recentlyDoneTodos: [ 'id1' ],
todos: {
id1: { name: .... },
id2: { name: .... },
id3: { name: .... },
id4: { name: .... },
}
}
Implementing pagination in this case is just a matter of getting list of todos ids for the next page from back-end and then loading missing todos for given ids.
Related
I'm having trouble envisioning application state.
For a multi-page application, should each page only load a chunk of the app state?
For example, let's say I have an app that manages my favorite things, books, movies, and games. Each one of those domains will have their own page to manage them. Is the idea that only portions of app state are loaded based on what's needed in the current context?
My app state would look something like this, conceptually.
{
currentUser: { id: 9, userName: 'JUtah' },
books: {},
movies: {},
games: {}
}
However, if I browsed to Books Management, the app state would look like this:
{
currentUser: { id: 9, userName: 'JUtah' },
books: {
1: { title: 'Kung Fu for Kittens', author: 'Dr. Meowrtin Kibble' }
},
movies: {},
games: {}
}
If I browsed to Movie Management, this:
{
currentUser: { id: 9, userName: 'JUtah' },
books: {}
},
movies: {
1: { title: 'John Wick', star: 'Keanu Reeves' }
},
games: {}
}
and so on.
Is this correct? I'm struggling to determine what app state holds at any given time.
First of all, React's local state and Redux's global state are different things.
Let's assume you don't use Redux for the moment. State management is up to you totally. But, try to construct your components as pure as possible and use the state where do you really need it. For example, think about a favorites app as you said. The decision is, do you want to show all the favorites categories in the same UI? If yes, then you need to keep all of them in one place, in the App. Then you will pass those state pieces your other components: Book, Movie, etc. Book get the book part of your state for example. They won't have any state, your App does. Here, App is the container component, others are presentational ones or dumb ones.
Is your data really big? Then you will think about other solutions like not fetching all of them (from an API endpoint or from your DB) but fetch part by part then update your state when the client wants more.
But, if you don't plan to show all of them in one place, then you can let your components have their state maybe. Once the user goes to Book component, maybe you fetch only the book data then set its state according to that. As you can see there are pros and cons, in the first method you are doing one fetch and distributing your data to your components, in the second method you are doing multiple fetches. So, think about which one suits you.
I can see you removed the Redux tag, but with Redux you will have one global state in the store. Again, in one point you are doing some fetch then update your state. Then, you will connect your components when they need any data from the state. But again, you can have container/presentational components here, too. One container connects to your store then pass the data to your components. Or, you can connect multiple components to your store. As you examine the examples, you will see best practices about those.
If you are new don't think too much :) Just follow the official documentation, read or watch some good tutorials and try to write your app. When you realize you need to extract some component do it, then think about if you need any state there or not?
So, once the question is very broad then you get an answer which is too broad, some text blocks :) You can't see so many answers like that, because here we share our specific problems. Again, don't bloat yourself with so many thoughts. As you code, you will understand it better.
Consider this:
I have an application that is going to end up being pretty large. It is a dashboard which will give you access to various utilities, one of which being a todo app.
If I was just going to build just a todo app, then my state object would look like so:
{ todos:[], completed:false, todoInput:''};
todoInput would be tied to a form field and and upon clicking add, it would alter the todos array and toggle the completed field. So my combineReducers() function would look like this.
combineReducers({todos,completed,todoInput});
This would make sense because all the state is relevant to the todo App because there is JUST a todo app.
Now because I am building a much more complicated application which also has a todo app, this is how my state would potentially look like:
{
otherState:'',evenMoreState:[]',evenMore:{},
todo:{ todos:[], completed:false, todoInput:''}
}
As you can see I have separated todos into a separate object now, so it is encapsulated and more organised. So I have 2 questions.
1) Is this a good idea? It seems like the logical move because my application will grow in size and I don't want all the pieces of state floating around as properties to the main state object. Have I gone about this correctly?
2) My combine reducers (as far as I know) cannot take a nested object. So it will now look like this.
combineReducers({ otherState,evenMoreState,evenMore,todo})
so now my reducer compositions will have to be done inside the reducer which handles the todo state. Is there a better/different way to do this?
Thanks
Yes, you're absolutely on the right track. It's also worth noting that you can use combineReducers multiple times, such as:
const rootReducer = combineReducers({
otherState : otherStateReducer,
todos : combineReducers({
todos : todosReducer,
completed : todosCompletedReducer,
todoInput : todoInputReducer
})
The overall todos section could be be defined separately, and imported and referenced in the top-level combineReducers call if desired.
});
You may want to read through the Redux docs section on "Structuring Reducers" for more information on ways to organize reducer logic, as well as the Redux FAQ on organizing nested state. In addition, the Redux Techniques and Redux Architecture sections of my React/Redux links list have links to a variety of articles about building real-world Redux applications.
I am currently developing an analytics dashboard in React/Redux that is similar to this:
Users of the dashboard will be able to add and remove tiles to customise the dashboard to their own needs, and the configuration of the tiles is stored and retrieved in an API.
The storing of the data for the configuration of tiles seems to fit well with the global state model:
On load, the dashboard component dispatches a 'loadTiles' action
The action fetches the tiles data and passes it to the 'tiles' reducer
From there it goes into the store/global state.
In mapStateToProps, the data is accessed from state.app.tiles
However, a problem arises when populating the data for each tile. The number of tiles and nature of the data is dynamic, so reducers can't be set up ahead of time.
This could be solved by each component managing their own state (as in pure/traditional React using componentWillMount etc) but this will violate some of the architectural principals that have been laid out for the rest of the project (ideally everything is to be managed in global state).
The only way I can see of storing the data is global state would be to have an analytics with a dynamic array of the various data sets, which sounds messy to me.
Is local component state the best solution here? or can this be done in global state cleanly? Are there any example of Redux using queries that are dynamically specified?
One thing you can do is the usage of an ID for each Tile. So your state could look like that:
{
tiles: {
tile1: {},
…
tile100: {}
}
}
Than, in the mapStateToProps() function you can use own props like so:
function mapStateToProps(state, ownProps) {
//test if it exists
if (state.tiles[ownProps.id]) {
return { tileData: state.tiles[ownProps.id] }
}
else
{
return { tileData: <default state> }
}
}
The important part is to hand over a unique ID for each tile, when those are created, one way could be that:
<Tile id={uuid()} other="stuff" />
whereby the uuid() method can be created as described here
I once had an similar issue, have a look here if you want to see a more complicated solution using an higher order component (its my own unaccepted answer). All in all, the above is the simplest solution IMHO.
The current approach is to connect whole book list into Book List Component. However, it is not an efficient to render huge components by changing only several fields in state. I dont know how to map each book component connect to each individual Book state.
The project is using redux. The application global state like this
{
"books": [
{
"field1": "field1",
"field2": "field2",
"field3": "field3",
} ...
]
}
In the Book List component, we connect list with it by using react redux
export default connect(state => ({
books: state.books
}))(BookListComponent);
Now the requirement changes, fields in book will not be changed frequently. The issue is, if one field is changed, it will update the whole BookListComponent. That is not performant component I am expecting.
I would like to push connect logic down to individual Book Component in order to use reselect
The state doesnt have index of the book, I dont know how to write connect to make it work
export default connect(state => ({
books[index]: state.books[index]
}))(BookListComponent);
Thanks for advance and all options are welcome
Your second example is almost correct. A mapState function can be declared to take two parameters instead of one. If two parameters are declared, Redux will call that mapState function with the props that were given to the wrapper component. So, the mapState function for a list item would be:
const mapState = (state, ownProps) => {
const book = state.books[ownProps.index];
return {book}
}
My blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance shows some examples of how to do that, and also discusses the key considerations for application performance in Redux.
You can connect any component you like. There is no hard and fast rule that you can only connect top level components.
While there is a performance hit to connecting top level components and passing the props down I have not witnessed it have a noticeable detrimental effect. The benefit in being able to trace the data through the code is always worth it in my opinion. There is a discussion on it here.
React updates only changed fields
While it's true that render function is called each time you update a book, that doesn't mean that the whole list is re-rendered.
Remember, that in React we are using Virtual DOM. It allows us to update only the elements that are actually changed.
Take a look at this article (it's short and has working code example on codeopen) https://facebook.github.io/react/docs/rendering-elements.html
and this one (a little more detailed)
https://facebook.github.io/react/docs/state-and-lifecycle.html
If you have read those articles, you know that all you need is to inspect your app and see what is actually rendered at each change.
I have to say this is a great way to manage your state for your application. But I have a few questions that I cannot seem to find an answer to anywhere on the internet.
Handling your state across multiple routes, meaning:
I have my state, looks something like this:
{
user: {},
routing: {}
}
And I have a few different pages, meaning that it adds (for example) todos which only matters in the /todo route. How is something like this handled? Or do I not even include this in redux. I was thinking I could add an object for every route, but that would get messy, quickly. Unless this is the suggested route to take?
I am looking for the correct "redux" answer to this. Would I just not include those values in Redux, or would I create only for every route and just nest the values as I went? Anything is helpful at this point, because I'm running out of an ideas for this.
Thank you everyone!
Edit!
Just to reply to what has been going on in the comments. This is what my state looks like:
{
"routing": {
"changeId": 1,
"path": "/",
"state": null,
"replace": false
},
"todos": {
"visibilityFilter": "SHOW_ALL",
"todos": [
{
"text": "Use Redux - This is the initial state.",
"completed": false,
"id": 0
}
]
},
"requests": {
"isFetching": false,
"data": {}
}
}
Pulled almost straight from redux, the routing object is from react-router so this is leading me to believe I don't really need this. But I'm just trying to figure out how keep things clean and how to structure my actions / reducers for a giant application.
The beauty of redux is the composability of the reducers. This means you can store all kinds of information in there, while handling it all quite separately. Here's some oversimplified and generic advice.
Store all app state in Redux. That means a todos property might be there while not on a route that needs it (but it might not be populated).
Decouple routes from data. A route is a method for bundling up a bunch of view elements together. Those view elements may in turn have data requirements, but they are independent of whatever route (or routes!) in which they appear. Your store should have a shape that represents your business domain.
If your application has various entities like todo items, calendar entries, and chat messages, your "model" may look like this:
todos:
todo1:
name: String
completed: boolean
...
threads:
thread1:
participants: [ ... ]
messages: [ ... ]
...
events:
event:
date: Date
todos: [ 'todo1', 'todo2' ]
...
users:
...
...
We may have a "todos" route where we focus on todos, but that is simply a representation of some of our domain data. A TodoList component will need data and hook up to the store to get what it needs. A route on which it appears may trigger an action to request the data - or it may come from someplace else entirely. Browsing to a different route starts the process again, but only for whatever data is needed then.
Cache data in the store. If a user clicks from route A to route B and then back again, a server call to re-fetch all of the data probably isn't necessary. Cache invalidation is beyond the scope of this post.