I'm coming from Angular over to React and therefore are scratching my head, trying to figure out how to share an object between two components.
I'm running into trouble when I update my object, as the new values aren't updated across components. As I'm not allowed to reference the same object (due to immutability in Redux) I'm having a hard time to figure out how to do this the best.
What's the correct, most simple and best way of achieving what I want? Here's an illustration:
(this illustration does not represent my app but has been simplified to make it easier to understand what I want to achieve. My React components are nested in multiple layers, which is why I thought Redux would be the solution)
... and here's how I've structured my redux object so far:
export default function reducer(state = {
blocks: [{ title: '', description: '' }],
currentBlock: null,
}, action) {
switch (action.type) {
case 'UPDATE_BLOCK_TITLE':
return {...state,
blocks: state.blocks.map((block, i) => block === action.payload.block ? {...block, title: action.payload.title} : block)
}
case 'SET_CURRENT_BLOCK':
return {...state, currentBlock: action.payload }
}
return state;
}
My UPDATE_BLOCK_TITLE case makes sure to update a field in the object, whilst my SET_CURRENT_BLOCK updates which block to be shown on the right-hand side.
I find it a little cumbersome if I have to update both the title and the currentBlock every time I change the title, especially coming from Angular where this sort of thing would be very easy to achieve.
Maybe I'm looking at implementing it the wrong way? Please let me know what the best way of achieving this is.
The Redux way is to keep your data normalized.
According to your use-case, you should keep only the block.id (or any other block identifier) in currentBlock. Doing it that way - when you modify the blocks store itself, all related components will receive the updated block, because they will rely on the relation block.id.
How do I organize nested or duplicate data in my state?
Data with IDs, nesting, or relationships should generally be stored in a “normalized” fashion: each object should be stored once, keyed by ID, and other objects that reference it should only store the ID rather than a copy of the entire object. It may help to think of parts of your store as a database, with individual “tables” per item type. Libraries such as normalizr and redux-orm can provide help and abstractions in managing normalized data.
I would suggest you to walk through the official Redux documentation about Structuring Reducers. It's very well written and structured, so you will learn the main concepts and best practices from Redux creators.
Related
We are building a new app using React/Redux which is rendered server side.
We wish to follow best practice for Redux and normalize our data on the server before it's passed into the initial state for the store.
For this example, let's say we have a generic 'Products' entity that can be quite complex and is normalized on the root of our store and page level state in another object on the root of the store. So the structure and Reducers follow the typical 'slice reducer' pattern and will look like this:
{
page_x_state: PageReducer
products: ProductsReducer
}
We are using combine reducers to merge the reducers before passing them into the store.
Theoretical use case: We have a 'products' page that shows a list of basic product info. A user can click on a product to show a modal which then loads and shows the complete product data.
For the above example, the state sent from the server will contain only basic product models (3 or 4 fields), this is enough to render the table and fetching all product information at this point is wasteful and not very performant.
When a user clicks a product we will do an AJAX call fetch all data for that product. Once we have all data for the single product, should we update the instance in the products store with a full model? If so, we would then end up with a set of objects all of which could be different states (some could have minimal fields vs some which are full-blown objects with 10s of fields). Is this the best way to handle it?
Also, I would be interested to hear any thoughts of managing different representations of the same underlying model on the server and how to map it to the Redux store (in Java ideally).
EDIT:
Explicitly answering your first question, if your reducers are built up correctly your whole state tree should initialize with absolutely no data in it. But should be the correct shape. Your reducers should always have a default return value - when rendering server side - Redux should only render the initial state
After server-side rendering, when the store (that is now client side) needs updating because of a user action, your state shape for all of your product data is already there (it's just that some of it will probably be default values.). Rather than overwriting an object, your just filling in the blanks so to speak.
Lets say, in your second level view you need name, photo_url, price and brand and the initial view has 4 products on it, your rendered store would look something like this:
{
products: {
by_id: {
"1": {
id: "1",
name: "Cool Product",
tags: [],
brand: "Nike",
price: 1.99,
photo_url: "http://url.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
},
"2": {
id: "2",
name: "Another Cool Product",
tags: [],
brand: "Adidas",
price: 3.99,
photo_url: "http://url2.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
},
"3": {
id: "3",
name: "Crappy Product",
tags: [],
brand: "Badidas",
price: 0.99,
photo_url: "http://urlbad.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
},
"4": {
id: "4",
name: "Expensive product",
tags: [],
brand: "Rolex",
price: 199.99,
photo_url: "http://url4.com",
category: "",
product_state: 0,
is_fetching: 0,
etc: ""
}
},
all_ids: ["1", "2", "3", "4"]
}
}
You can see in the above data some keys are just empty strings or an empty array. But we have our data we need for the actual initial rendering of the page.
We could then make asynchronous calls on the client in the background immediately after the server has rendered and the document is ready, the chances are the server will return those initial calls before the user tries to get the data anyway. We can then load subsequent products on user request. I don't think that's the best approach but it's the one that makes most sense to me. Some other people might have some other ideas. It entirely depends on your app and use-case.
I would only keep one products object in state though and keep ALL the data pertaining to products in there.
I recently deployed an app into production and i'll share some of my
insights. The app, whilst not being too large in size, had a complex
data structure and having gone through the whole process as a newbie
to Redux in production (and having guidance from my architect) – These
are some of our takeaways. There's no right way in terms of architecture but there certainly are some things to avoid or do.
1. Before firing into writing your reducers design a 'static' state
If you don't know where you are going, you can't get there. Writing the whole structure of your state out flat will help you reason about how your state will change over time. We found this saved us time because we didn't have to really rewrite large sections.
2. Designing you state
keep it simple. The whole point of Redux is to simplify state management. We used a lot of the tips from the egghead.io tutorials on Redux that were created by Dan Abramov. They are clear really helped solve a lot of issues we were encountering. i'm sure you've read the docs about normalising state but the simple examples they gave actually carried through in most data patterns we implemented.
Rather than creating complex webs of data each chunk of data only held it's own data if it needed to reference another piece of it data it only referenced it by id we found this simple pattern covered most of our needs.
{
products: {
by_id: {
"1": {
id: "1",
name: "Cool Product",
tags: ["tag1", "tag2"],
product_state: 0,
is_fetching: 0,
etc: "etc"
}
},
all_ids: ["1"]
}
}
In the example above, tags might be another chunk of data with a similiar data structure using by_id and all_ids. All over the docs and tut, Abramov keeps referencing relational data and relational databases this was actually key for us. At first we kept looking at the UI and designing our state around how we thought we were going to show it. When this clicked and we started grouping the data based on it's relationship to other pieces of data, things started to click into place.
Quickly flipping to your question, I would avoid duplicating any data, as mentioned in another comment, personally i'd simply create a key in the state object called product_modal. let the modal take care of it's own state...
{
products: {
...
},
product_modal: {
current_product_id: "1",
is_fetching: true,
is_open: true
}
}
We found following this pattern with page state worked really well as well...we just treated it like any other piece of data with an id/name etc.
3. Reducer Logic
make sure reducers keep track of their own state. a lot of our reducers looked quite similiar, at first this felt like DRY hell but then we quickly realised the power of more reducers...say an action is dispatched and you want to update a whole chunk of state..no probs just check in your reducer for the action and return the new state. If you only want to update one or two fields in the same state...then you just do the same thing but only in the fields you want changing. most of our reducers were just simply a switch statement with an occasional nested if statement.
Combining Reducers
We didnt use combineReducers, we wrote our own. It wasn't hard, it helped us understand what was going on in Redux, and it allowed us to get a little smarter with our state. This tut was invaluable
Actions
Middleware is your friend...we used fetch API with redux-thunk to make RESTful requests. We split the required data requests into separate actions which called store.dispatch() for each data chunk that needed updating for the call. Each dispatch dispatched another action to update state. This kept our state updated modularly and allowed us to update large sections, or granularly as needed.
Dealing with an API
Ok so there's way too much to deal with here. I'm not saying our way is the best...but it has worked for us. Cut short...we have an internal API in java with publically exposed endpoints. The calls from this API didn't always map to the front end easily. We haven't implemented this, but ideally, an initial init endpoint could have been written on their end to get a lump of initial data that was needed to get things rolling on the front end for speeds sake.
We created a public API on the same server as the app, written in PHP. This API abstracted the internal API's endpoints (and in some cases the data too) away from the front end and the browser.
When the app would make a GET request to /api/projects/all the PHP API would then call our internal API, get the necessary data (sometimes across a couple of requests) and return that data in a usable format that redux could consume.
This might not be the ideal approach for a javascript app but we didn't have the option to create a new internal API structure, we needed to use one that has existed for several years, we have found the performance acceptable.
should we update the instance in the products store with a full model
It should be noted that Java and ReactJs+Redux don't have much conceptual overlap. Everything is a Javascript Object, not an Object with a Class.
Generally, storing all the data you receive in the Redux store state is the way to go. To work around the fact that some of the data will be minimal and some will be fully loaded you should make a conditional ajax call in the onComponentWillMount method of the individual product display container.
class MyGreatProduct extends React.Component {
onComponentWillMount() {
if(!this.props.thisProduct.prototype.hasProperty( 'somethingOnlyPresentInFullData' )) {
doAjaxCall(this.props.thisProduct.id).then((result) => {
this.props.storeNewResult(result.data);
}).catch(error=>{ ... })
}
}
// the rest of the component container code
}
const mapStateToProps = (state, ownProps) => {
return {
thisProduct: state.products.productInfo[ownProps.selectedId] || {id: ownProps.selectedId}
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
storeNewResult: (data) => { dispatch(productDataActions.fullProductData(data)) }
}
export default connect(mapStateToProps, mapDispatchToProps)(MyGreatProduct);
With this code, it should be somewhat clear how agnostic the components and containers can be regarding the exact data available in the Store at any given time.
Edit: In terms of managing different representations of the same underlying model on the server and how to map it to the Redux store, I'd try to use the same relative looseness you are dealing with once you have JSON. This should eliminate some coupling.
What I mean by this is just add the data you have to a JSObject to be consumed by React + Redux, without worrying too much about what values could potentially be stored in the Redux state during the execution of the application.
There's probably no right answer, just which strategy you prefer:
The simplest strategy is to add another piece to your reducer called selectedProduct and always overwrite it with the full object of the currently selected product. Your modal would always display the details of the selectedProduct. The downfalls of this strategy are that you aren't caching data in the case when a user selects the same product a second time, and your minimal fields aren't normalized.
Or you could update the instance in your Products store like you said, you'll just need logic to handle it. When you select a product, if it's fully loaded, render it. If not, make the ajax call, and show a spinner until its fully loaded.
If you don't have a concern with storing extra that data in the redux store it's not actually going to hit your performance very much if you use a normalized state. So on that front I would recommend caching as much as you can without risking security.
I think the best solution for you would be to use some redux middleware so your front end doesn't care how it gets the data. It will dispatch an action to the redux store and the middleware can determine whether or not it needs an AJAX call to get the new data. If it does need to fetch the data then the middleware can update the state when the AJAX resolves, if it doesn't then it can just discard the action because you already have the data. This way you can isolate the issue of having two different representations of the data to the middleware and implement a resolution there for it so your front end just asks for data and doesn't care how it gets it.
I don't know all the implementation details so as Jeff said its probably more what you prefer but I would definitely recommend adding some middleware to handle your AJAX calls if you haven't already it should make interfacing with the store much simpler.
If you want to read more on middleware the Redux documentation is pretty good.
https://redux.js.org/docs/advanced/Middleware.html
You could store each entity as an object of its various representations. In the action creator that updates the entity, include the representation as an argument:
const receiveProducts = (payload = [], representation = 'summary') => ({
type: 'PRODUCTS_RECEIVED',
payload, representation
});
const productReducer = (state = {}, action) => {
case 'PRODUCTS_RECEIVED': {
const { payload, representation } = action;
return {
...state,
...payload.reduce((next, entity) => {
next[entity.id] = {
...next[entity.id],
[representation]: entity
};
return next
}, {})
}
}
};
This means that whoever is calling receiveProducts() needs to know which representation is returned.
TL;DR
Is there a library which makes something like Elm-Union Types possible in JavaScript with Redux and ImmutableJS?
Idea
Hi!
I am rather new to Redux, still exploring best patterns to use. I spent some time learning Elm and in Elm the core focus of whole application is the Model.
The core prinicple of Elm is making errorless applications. In comparison with JavaScript, Elm is packed with basic types (String, List, Boolean...) as it's also a static typed language. On the other hand you can use ImmutableJS and Flow and get pretty decent amount of full types and type check also in JavaScript, which makes this very even.
But the contrast between Elm and JavaScript comes with a feature called Union Types. With Union Types you can say something like:
type alias User = {
name: String
}
type Session = LoggedOut | LoggedIn User
What this means is that we have two options when it comes to session:
There is no session and we have no information about the user.
User is authenticated and we know who the user is.
This is extremely useful when dealing with "HOC" as we can say simply:
case session of
LoggedOut ->
displayLoginButton
LoggedIn user ->
displayUser user
Furthermore you can use the same pattern when fetching data. When data is fetching the state is Fetching once it is fetched, the state is Fetched Data and in case of an error, the state is FetchingError Error.
This makes data very secure and very clean. The same problem written in JavaScript/Redux would normaly be tackled something like this:
const model = {
loading: false,
loaded: false,
error: null,
data: null
}
At first this might seem normal, but as you think you see that "impossible" situations can be encountered. For example:
const model = {
loading: true,
loaded: true,
error: null,
data: null
}
The data can't be loading and be loaded at the same time!
Question
Is there a way to acheive similar concept with JavaScript-Redux and still remain the immutability of ImmutableJS?
Or if there's a way to rearrange the model to make impossible states impossible in Redux?
P.S.
Great talk about this in Elm: "Making Impossible States Impossible" by Richard Feldman
No, there's nothing specifically built into React or Redux that enables that. That sort of thing is really more at the language level. You might want to look into TypeScript or Flow to add static types that help with that. Beyond that, you can probably put something together using an explicit state machine approach or careful reducer logic checks.
My React/Redux links list has a section on static typing tools that may be useful in getting started. In addition, since you mentioned the "loading states" example, the React and Ajax category in my list specifically links to several articles that discuss ways to handle that concept in TS, Flow, and a couple other solutions.
You can hack it like this:
const Session = {
LOGGED_IN: user => ({ type: 'LOGGED_IN', user }),
LOGGED_OUT: () => ({ type: 'LOGGED_OUT' })
}
switch (session.type) {
case 'LOGGED_IN': {
viewUser(session.user)
}
case 'LOGGED_OUT': {
viewLoginButton()
}
}
Currently working on a react + redux project.
I'm also using normalizr to handle the data structure and reselect to gather the right data for the app components.
All seems to be working well.
I find myself in a situation where a leaf-like component needs data from the store, and thus I need to connect() the component to implement it.
As a simplified example, imagine the app is a book editing system with multiple users gathering feedback.
Book
Chapters
Chapter
Comments
Comments
Comments
At different levels of the app, users may contribute to the content and/or provide comments.
Consider I'm rendering a Chapter, it has content (and an author), and comments (each with their own content and author).
Currently I would connect() and reselect the chapter content based on the ID.
Because the database is normalised with normalizr, I'm really only getting the basic content fields of the chapter, and the user ID of the author.
To render the comments, I would use a connected component that can reselect the comments linked to the chapter, then render each comment component individually.
Again, because the database is normalised with normalizr, I really only get the basic content and the user ID of the comment author.
Now, to render something as simple as an author badge, I need to use another connected component to fetch the user details from the user ID I have (both when rendering the chapter author and for each individual comment author).
The component would be something simple like this:
#connect(
createSelector(
(state) => state.entities.get('users'),
(state,props) => props.id,
(users,id) => ( { user:users.get(id)})
)
)
class User extends Component {
render() {
const { user } = this.props
if (!user)
return null
return <div className='user'>
<Avatar name={`${user.first_name} ${user.last_name}`} size={24} round={true} />
</div>
}
}
User.propTypes = {
id : PropTypes.string.isRequired
}
export default User
And it seemingly works fine.
I've tried to do the opposite and de-normalise the data back at a higher level so that for example chapter data would embed the user data directly, rather than just the user ID, and pass it on directly to User – but that only seemed to just make really complicated selectors, and because my data is immutable, it just re-creates objects every time.
So, my question is, is having leaf-like component (like User above) connect() to the store to render a sign of anti-pattern?
Am I doing the right thing, or looking at this the wrong way?
I think your intuition is correct. Nothing wrong with connecting components at any level (including leaf nodes), as long as the API makes sense -- that is, given some props you can reason about the output of the component.
The notion of smart vs dumb components is a bit outdated. Rather, it is better to think about connected vs unconnected components. When considering whether you create a connected vs unconnected components, there are a few things to consider.
Module boundaries
If you divided your app into smaller modules, it is usually better to constrain their interactions to a small API surface. For example, say that users and comments are in separate modules, then I would say it makes more sense for <Comment> component to use a connected <User id={comment.userId}/> component rather than having it grab the user data out itself.
Single Responsibility Principle
A connected component that has too much responsibility is a code smell. For example, the <Comment> component's responsibility can be to grab comment data, and render it, and handle user interaction (with the comment) in the form of action dispatches. If it needs to handle grabbing user data, and handling interactions with user module, then it is doing too much. It is better to delegate related responsibilities to another connected component.
This is also known as the "fat-controller" problem.
Performance
By having a big connected component at the top that passes data down, it actually negatively impacts performance. This is because each state change will update the top-level reference, then each component will get re-rendered, and React will need to perform reconciliation for all the components.
Redux optimizes connected components by assuming they are pure (i.e. if prop references are the same, then skip re-render). If you connect the leaf nodes, then a change in state will only re-render affected leaf nodes -- skipping a lot of reconciliation. This can be seen in action here: https://github.com/mweststrate/redux-todomvc/blob/master/components/TodoItem.js
Reuse and testability
The last thing I want to mention is reuse and testing. A connected component is not reusable if you need to 1) connect it to another part of the state atom, 2) pass in the data directly (e.g. I already have user data, so I just want a pure render). In the same token, connected components are harder to test because you need to setup their environment first before you can render them (e.g. create store, pass store to <Provider>, etc.).
This problem can be mitigated by exporting both connected and unconnected components in places where they make sense.
export const Comment = ({ comment }) => (
<p>
<User id={comment.userId}/>
{ comment.text }
</p>
)
export default connect((state, props) => ({
comment: state.comments[props.id]
}))(Comment)
// later on...
import Comment, { Comment as Unconnected } from './comment'
I agree with #Kevin He's answer that it's not really an anti-pattern, but there are usually better approaches that make your data flow easier to trace.
To accomplish what you're going for without connecting your leaf-like components, you can adjust your selectors to fetch more complete sets of data. For instance, for your <Chapter/> container component, you could use the following:
export const createChapterDataSelector = () => {
const chapterCommentsSelector = createSelector(
(state) => state.entities.get('comments'),
(state, props) => props.id,
(comments, chapterId) => comments.filter((comment) => comment.get('chapterID') === chapterId)
)
return createSelector(
(state, props) => state.entities.getIn(['chapters', props.id]),
(state) => state.entities.get('users'),
chapterCommentsSelector,
(chapter, users, chapterComments) => I.Map({
title: chapter.get('title'),
content: chapter.get('content')
author: users.get(chapter.get('author')),
comments: chapterComments.map((comment) => I.Map({
content: comment.get('content')
author: users.get(comment.get('author'))
}))
})
)
}
This example uses a function that returns a selector specifically for a given Chapter ID so that each <Chapter /> component gets its own memoized selector, in case you have more than one. (Multiple different <Chapter /> components sharing the same selector would wreck the memoization). I've also split chapterCommentsSelector into a separate reselect selector so that it will be memoized, because it transforms (filters, in this case) the data from the state.
In your <Chapter /> component, you can call createChapterDataSelector(), which will give you a selector that provides an Immutable Map containing all of the data you'll need for that <Chapter /> and all of its descendants. Then you can simply pass the props down normally.
Two major benefits of passing props the normal React way are traceable data flow and component reusability. A <Comment /> component that gets passed 'content', 'authorName', and 'authorAvatar' props to render is easy to understand and use. You can use that anywhere in your app that you want to display a comment. Imagine that your app shows a preview of a comment as it's being written. With a "dumb" component, this is trivial. But if your component requires a matching entity in your Redux store, that's a problem because that comment may not exist in the store yet if it's still being written.
However, there may come a time when it makes more sense to connect() components farther down the line. One strong case for this would be if you find that you're passing a ton of props through middle-man components that don't need them, just to get them to their final destination.
From the Redux docs:
Try to keep your presentation components separate. Create container
components by connecting them when it’s convenient. Whenever you feel
like you’re duplicating code in parent components to provide data for
same kinds of children, time to extract a container. Generally as soon
as you feel a parent knows too much about “personal” data or actions
of its children, time to extract a container. In general, try to find
a balance between understandable data flow and areas of responsibility
with your components.
The recommended approach seems to be to start with fewer connected container components, and then only extract more containers when you need to.
Redux suggests that you only connect your upper-level containers to the store. You can pass every props you want for leaves from containers. In this way, it is more easier to trace the data flow.
This is just a personal preference thing, there is nothing wrong to connect leaf-like component to the store, it just adds some complexity to your data flow, thus increase the difficulty to debug.
If you find out that in your app, it is much easier to connect a leaf-like component to the store, then I suggest do it. But it shouldn't happen very often.
Why should objects in Redux be immutable?
I know that some frameworks such as Angular2 will use onPush and can take advantage of immutability to compare states of views for faster rendering, but I am wondering if there are other reasons as Redux is framework agnostic and yet it mentions within its own docs to use immutability (regardless of the framework).
Appreciate any feedback.
Redux is a small library that represents state as (immutable) objects. And new states by passing the current state through pure functions to create an entirely new object/application states.
If your eyes-glazed over there don't worry. To sum up, Redux does not represent changes in your application's state by modifying objects ( as you would with object-oriented paradigms). Instead state changes are represented as the difference between the input object and the output object (var output = reducer(input)). If you mutate either input or output you invalidate the state.
To sum up another way, immutability is a requirement of Redux because Redux represents your application state as "frozen object snapshots". With these discrete snapshots, you can save your state, or reverse state, and generally have more "accounting" for all state changes.
State of your app is only changed by a category of pure functions called reducers. Reducers have 2 important properties:
They never mutate, returning newly built objects: This allows reasoning about input + output without side-effects
Their signature is always function name(state, action) {}, so it makes it easy to compose them:
Assume the state looks like this:
var theState = {
_2ndLevel: {
count: 0
}
}
We want to increment the count, so we make these reducers
const INCR_2ND_LEVEL_COUNT = 'incr2NdLevelCount';
function _2ndlevel (state, action) {
switch (action.type) {
case INCR_2ND_LEVEL_COUNT:
var newState = Objectd.assign({}, state);
newState.count++
return newState;
}
}
function topLevel (state, action) {
switch (action.type) {
case INCR_2ND_LEVEL_COUNT:
return Object.assign(
{},
{_2ndLevel: _2ndlevel(state._2ndlevel, action)}
);
}
}
Note the use of Object.assign({}, ...) to create an entirely new objects in each reducer:
Assuming we have wired up Redux to these reducers, then if we use Redux's event system to trigger a state change ...
dispatch({type: INCR_2ND_LEVEL_COUNT})
...Redux will call:
theNewState = topLevel(theState, action);
NOTE: action is from dispatch()
Now theNewState is an entirely new object.
Note: You can enforce immutability with a library (or new language features), or just be careful to not mutate anything :D
For a deeper look, I highly recommend you checkout this video by Dan Abramov (the creator). It should answer any lingering questions you have.
The following benefits of immutability are mentioned in Redux documentation:
Both Redux and React-Redux employ shallow equality checking. In particular:
Redux's combineReducers utility shallowly checks for reference changes caused by the reducers that it calls.
React-Redux's connect method generates components that shallowly check reference changes to the root state, and the return values from the mapStateToProps function to see if the wrapped components actually need to re-render. Such shallow checking requires immutability to function correctly.
Immutable data management ultimately makes data handling safer.
Time-travel debugging requires that reducers be pure functions with no side effects, so that you can correctly jump between different states.
The main reason Redux is using immutability is that it doesn't have to traverse an object tree to check for the changes in every key value. Instead, it will only check the object's reference is changed or not in order to update DOM on state change.
Greate article https://medium.cobeisfresh.com/how-redux-can-make-you-a-better-developer-30a094d5e3ec
Along with immutable data, pure functions are one of the core concepts
of functional programming
Based on the official docs :
There are several reasons why you must not mutate state in Redux:
It causes bugs, such as the UI not updating properly to show the latest values
It makes it harder to understand why and how the state has been updated
It makes it harder to write tests
It breaks the ability to use "time-travel debugging" correctly
It goes against the intended spirit and usage patterns for Redux
Reudx official docs
I am new to React and I don't know what's the best way to do this.
I have a list of cars and on clicking each row it should show slide to full page details of that car.
My code structure is:
I have App which renders two components. CarList and CarDetails. Car Details is hidden initially. The reason I rendered carDetails in app is because it's a massive fix template so I would like to render this once when app is loaded and only update it's data when each row clicked.
CarList also renders CarRow component which is fine.
Now my problem is I have a getDetails function on CarRow component which is making a call to get the details based on the car id. How to get carDetails component data updated ? I used
this.setState({itemDetails:data});
but seems state of the carRow is not the same reference as state in carDetails.
Any help?
This is a fundamental issue that lots of thought and man-hours has gone into in order to try and solve. It probably can't be answered, except on a surface level, in a StackOverflow post. It's not React-centric, either. This is an issue across most applications, regardless of the framework you're using.
Since you asked in the context of React, you might consider reading into flux, which is the de-facto implementation of this one-way data-flow idea in concert with React. However, that architecture is by no means "the best". There are simply advantages and disadvantages to it like everything else.
Some people don't like the idea of the global "event bus" that flux proposes. If that's the case, you can simply implement your own intermediate data layer API that collects query callbacks and A) invokes the callbacks on any calls to save data and B) refreshes any appropriate queries to the server. For now, though, I'd stick with flux as it will give you an idea of the general principles involved in having the things that most people consider to be "good", like a single source of truth for your data, one way flow, etc.
To give a concrete example of the callback idea:
// data layer
const listeners = [];
const data = {
save: save,
query: query
};
function save(someData) {
// save data to the server, and then...
.then(data => {
listeners.forEach(listener => listener(data));
});
}
function query(params, callback) {
// query the server with the params, then
listeners.push(callback);
}
// component
componentWillMount() {
data.query(params, data => this.setState({ myData: data }));
},
save() {
// when the save operation is complete, it will "refresh" the query above
data.save(someData);
}
This is a very distilled example and doesn't address optimization, such as potential for memory leaks when moving to different views and invoking "stale" callbacks, however it should give you a general idea of another approach.
The two approaches have the same policy (a single source of truth for data and one way data flow) but different implementations (global "event bus" which necessitates keeping track of events, or the simple callback method, which can necessitate a form of memory management).