How do I deal with app state in a multi-"page" application? - reactjs

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.

Related

Do I have to save every React component state property to the Redux store?

I have read multiple articles on the need to use Redux and have built two fully-functioning React+Redux applications. I have even posted the question on Quora I still cannot have a final answer to my question:
Do I have to save every component state property to the Redux store?
The first project, I have built by following a tutorial where he basically saves everything to the store.
Here's the Github link.
Since I was learning React and Redux, I did not question this approach and went on with it. But, it does seem somewhat unnecessary to save everything to the store
For example, there's an action that saves the comment data to the store:
postActions.js
// Add Comment
export const addComment = (postId, commentData) => dispatch => {
dispatch(clearErrors());
axios
.post(`/api/posts/comment/${postId}`, commentData)
.then(res =>
dispatch({
type: GET_POST,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
And it is called like this:
CommentForm.js
onSubmit(e) {
e.preventDefault();
const { user } = this.props.auth;
const { postId } = this.props;
const newComment = {
text: this.state.text,
name: user.name,
avatar: user.avatar
};
this.props.addComment(postId, newComment);
this.setState({ text: '' });
}
If I were working on my own project, I would've kept the message data stored locally at the component level:
The second project was a personal project, where the only data I saved in the store, is the user account information because I would need it in different components throughout the app to send it in some backend API requests.
All the other components are basically independent or the flow between them does not go beyond two or three components. So I really could not see why I would make myself code all the actions, reducers...etc for all of the components. So I simply pass the props and functions between components in the plain old react way of doing things.
Most of the answers that I found do not go into this specific detail mentioned in my question. All of them talk from a high-level perspective.
Before going ahead and working on other projects, I would like to :
A clear answer to my question
Whether the approach I used for my personal project is okay. In other words, can I use Redux simply for the user account information and for the rest of the components not use it?
I just want to clear this confusion so that when I am using Redux, I am 100% sure, I am using it because I actually need it.
Do I have to save every component state property to the Redux store?
Short answer: No you don't.
Longer answer: To quote Dan Abramov on a similar question:
Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state.
Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft.
There is nothing wrong with the approach taken in your personal project. Redux is great for storing/sharing global application state, such as the user info you describe.
Before putting state into Redux I'd ask:
Will this state be consumed by other components independent to this one?
If the answer to #1 is yes: then ask how often?
If the answer to #2 is frequently: then ask is a single source of truth (the Redux store) the best way to share this particular piece of state? Would
other techniques (hooks / render props / higher order components) be more appropriate?
Another quote from Dan in the same linked thread is:
If it gets tedious and frustrating don’t be afraid to put state into the components. My point is that use single state tree unless it is awkward, and only do this when it simplifies things for you rather than complicates them. That’s the only guideline.
The mantra Yagni (You Aren't Gonna Need It) springs to mind.
If you're unsure whether state should be abstracted from a component into Redux, then the chances are it's too early todo so. This helps avoid making design decisions too early, whilst keeping your Redux state lean and intentional (i.e: not convoluted with unnecessary single use concerns).
Ultimately the cost of putting state into Redux needs to pay off.

Redux-way in the real life

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.

React & Redux with dynamic elements

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.

How to connect each element of array individually by using react redux

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.

Redux: Handling State between Multiple Routes

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.

Resources