I am designing an application in react/redux and I am new to flux/redux and unidirectional patterns. I ran into a problem which I am not sure how to properly solve within a unidirectional architecture
I want to illustrate the problem with the example of a user registration form that might show up in different parts of the app. Please note that the core problem is about if and where to handle subcomponent completion events and the UserRegistration is just an example.
Let's say I have a component UserRegistration which will render as a form. This component will handle its own submit action which may include some asynchronous calls to a server endpoint and in the end should lead to a modification of the store/state (e.g. adding new user data).
The user flow is the following:
user clicks on 'register' button
user sees registration form
user types in data and clicks 'submit'
user registration form disappears
I want to include this UserRegistration form in different places in the app.
Let's say I want to show the registration form based on a route /register and after succesfully registering I want to route back to /.
The intuitive thing for me to do (coming from a non-unidirectional world) would look something like this
function MyApp() {
const isRegisterRoute = //... somehow check if /register is active
return (
<button onClick={ () => { routeTo('/register') }}>register</button>
{
isRegisterRoute && (
<UserRegistration // handles actual registration internally (network calls and store modification)
onCompletion={ () => { routeTo('/' } } // pass completion handler which will route back to initial path
/>
)
}
)
}
This would solve the problem but I am wondering if having a callback for such a component with a complex flow is an anti pattern in a unidirectional architecture.
One concern is that the callback basically inverts the direction and it's no longer unidirectional.
Another concern is that I would have to pass the onCompletion handler down the chain within my component so it gets executed once the registration is complete. This passing around of the handler does not feel clean as it leaks into a lot of places.
On the other hand I don't want to handle the routing back to '/' inside the UserRegistration component as the success route or action might be different depending on where I use it so I want that logic to be decoupled from the component.
What would be the appropriate/best-practice solution for the problem described above within flux?
It's not data flow at all
It's not an anti-pattern as I don't see any data flow from sub-component to parent, like
<SubComponent onComplete={data=>this.setState({data})}/> // not good
reroute to another router is logic, the next router is the property passed to UserRegistration component, so it's the same as
<UserRegistration nextRouteOnComplete="/"/> // property
and handle routeTo logic in UserRegistration.
A function can be dynamic, do make sure no data will be passed to the callback function, the callback is designed to handle logics after complete, no data should be passed, and data update should follow the unidirectional data flow,
dispatch action -> reduce action -> mutate state -> rerender
Related
Let's suppose I have a Modal component that triggers an MODAL_CLOSE action when the user closes it.
Let's suppose I have an application that uses Modal component in many different places and, in some cases I want to change the application store when the MODAL_CLOSE event is triggered.
Is it correct to have, say a user reducer that listens for the MODAL_CLOSE action to make any change to the user portion of the store? Or by doing this I'm actually creating a coupling between the user "domain" and the Modal component?
What's the best practice in this case?
I'd say it's fine, because it's not coupling with the component, the connect call is doing the coupling.
Your reducer doesn't depend on the implementation of the component or even the existence of the component, just that there is an action MODAL_CLOSE(D?).
Likewise, your component is not coupled to or aware of the logic of the reducer.
I think it's correct. You would use something like <Modal onClose={closeModalAndDoSomethingAction} in the places where closing it has special behavior. The Modal component could then either dispatch its default onClose action, or the special one, if provided via prop. The special action would either be something other than MODAL_CLOSE or maybe have something in the payload that the reducer needs to make a distinction.
I have multiple instances of the same component rendered on the same page. Each of these components have identical behavior and call the same actions. The called actions are asynchronous and wait for the response from an API (after which more actions are created so that the response can be consumed by stores).
If an action is triggered from one of these components, that component needs to handle the eventual response in isolation from its siblings.
For example:
Three identical buttons are rendered on a page
Clicking a button will create an action that asynchronously queries an API and dispatches a new action with the response's payload (true)
Once this response is received, the clicked button needs to turn blue
The unclicked buttons must be unaffected.
How would I go about implementing this simple example in a React/Flux fashion?
The immediate solution that comes to mind is to assign a unique identifier with the component, pass that ID along with the actions, and listen for that specific ID in the store to indicate that it is time to turn blue.
Another solution is to lift the "color" state in each of the rendered components up to their container and mirror this in the store (i.e., have button1Color, button2Color, ... in the store). The downside is that any addition of a button needs to have its own state variable in a store (more brittle!). Is that the better option?
Is there a better way?
This was also asked in Flux store emitting changes to specific react components rather than all components., but I don't believe there was a clear or satisfying answer.
Edit: Further resources:
http://briandipalma.github.io/flux-for-components/
https://medium.com/front-end-development-2/multiple-components-in-flux-acd73d8fceef
I think you're unnecessarily splitting the proper answer to your question in half, and debating which half to use. You should use both.
Your store ought to have a hash of values that correspond to different sub-components's eventual states, hashed on some sort of id.
That store might have a state that looks like;
{
id_1: {
//api response object
},
id_2: {
//api response object
},
...
}
You then need a container whose job is to take the data from the flux store, and parse it to provide it's child components with proper props.
That container should have a function on it that does a lookup to find the correct color for each child, e.g.;
getColor: function(id) {
return this.props.flux_store[id].some_value == 'condition' ? 'red' : 'green';
}
And then that container should render;
<SubComponent color={this.getColor('id_1')} key='id_1' />
I am subscribing a user to events using pusher-js, and am having an issue with the information I am passing in the callback not staying updated. I use react router and have a 'location' context on my components.
connect() {
let {pusher, channelName, eventName} = this.props;
let pusher_channel = pusher.subscribe(channelName);
pusher_channel.bind(eventName, () => console.log(this.context.location.pathname));
}
For demonstration purposes I am just console logging the current url pathname the user is on when the event comes through. This component gets loaded and stays loaded when the user loads that application. The problem is that, if a user navigates to another page in the app and an event comes in, the pathname does not reflect the actual page the user is on but rather the page the user was on when they initially loaded the app.
Is there a way to keep the callback updated as to any context or prop changes that have occurred?
Updating Context
Don't do it.
React has an API to update context, but it is fundamentally broken and you should not use it.
Read more.
Use React Router's location descriptors instead.
I'm in trouble with React and Flux... We have an application that is pretty similar to the new Flux chat example. We have the famous error "cannot dispatch in the middle of dispatch". But, it's hard to us to think in a good way to resolve this problem in some cases.
Our doubt is identical to this: https://groups.google.com/forum/#!topic/reactjs/mVbO3H1rICw, but I can't understand very well the solution adopted. As far as I understand, is not a very elegant solution.
Here is the sequence of events:
Action A is dispatched;
The Store updates it's internal state and emits the change message;
A react component X receives the change message (by the callback of the listener) and updates it's state (setState);
The component X renders and as part of that a new component Y is mounted too. We choose the component (Y, Z, etc...) to be rendered using the information of the state;
The new component Y needs data to display that isn't initially loaded. So we call a API in the componentDidMount() of the component Y, that calls an action B.
Then, with the new dispatcher in the Action B, we have this dispatch error.
If you consider that our application logic have some issue, I can bring some practicals examples to show why this scenario is common for us. Any idea of how refactor this "flux" is very welcome.
Thanks for any help!
i think you need to use the waitFor token from the dispatcher before launching action b (youre using the dispatcher from the flux npm module right?). additionally, what can you do is make the ajax call from your store during action A if youre always going to need that data from action b.
solution b would look like this in /* store.js */
Dispatcher.register(function(payload){
switch payload.type {
case ACTION_A:
/* do your state logic */
StoreDataAccessLayer.apiCall()
}
}
where you have a Data Access Layer class / object that wraps your ajax call to the api. using the state from your store as inputs, and calling trigger change from the success function. this is the primitive solution i've seen when using the flux npm module if youre not going with waitFor
I'm trying to wrap my head around Facebook's Flux...
Say I have an app with a side menu that can be hidden and shown via a button in the header.
My header is one component, my side menu is another component.
Currently my Header component just sets a class on the HTML div side menu element which gets animated to hidden by CSS.
What's the general idea here?
ReactJs doesn't really care about how it gets its data (how it's data is getting passed in or how that data should be handled across the web application). That's where Flux comes in, it creates a functional approach on how data is handled. Data-flow is essentially:
Action -> Data Store -> Component
Mutation of data happen through calling Actions. The Data Stores themselves have to listen on the actions and mutate the data within the store. This keeps the data structure and logic flat.
In your case, your dataflow would probably look something like this:
Header --> User Click --> Fires action --> Updates store --> Side menu listening and responding to that store change.
Your case is a simple example which you probably don't really need Flux. I think it's easier if you have a parent component that maintains the view state logic, and use props/callbacks for the 2 children components (side menu and header). But a more advanced example that you need to make ajax calls and maintain session, Flux would become useful. Like if you have a Login Component, and you want to show different side-menu options and header options depending on user:
Login Component --> User Logins --> Calls Action #signIn --> Showing Loading State
--> Dispatcher handles action (make api call to authenticate user and load user data)
On success (for the api call), alert sessionStore, and populate store with data
On error, maybe fire another action that says login failed or something
SessionStore ---> Header Component (Listens to Store) --> Update view based on store information
---> Side Menu Component (Listens to Store) --> Update
speaking more general:
flux is a software architecture for a unidirectional Dataflow. It's Chain is Action -> Delegation -> Store -> View... The action - for example a Button Click - gets delegated to stores where your applicationlogic and data is kept... here your action and data will be changed and processed. The store eventually emits an event which views (for example react components) previously registered on with a callback. In this callback you can GET your data from your stores. It is important to mention that you can only access the stores READ-Only.
So for your case... if you want component A to affect component B you will have to register component B to the store eventEmitter and GET the desired data from the store. Once component a triggers an action it gets delegated to the store, your functions are performed and eventually the event gets thrown that starts component B's callback.
Hope this got clear enough... its way cooler with some nice drawings.