Passing actions down the stack via props - reactjs

I have been using Redux for a few weeks now and I am very happy with it and I am getting used to a Redux way. I am using it with React. Still plenty to learn as both things are new to me.
I have a one problem - maybe I am doing something wrong ... Let me show you:
I have a component structure that looks like this:
App //root of the application aka smart component
CampaignTable
CampaignHeaderRow
CampaignHeader
CampaignDataRow
CampaignData
The App component is initialized as(only related code):
import * as DashboardActions from '../actions.js'
function select(state){
return {
campaigns: state.campaigns, // array of campaign objects, has name, id, time created etc
order: state.order // sort format "byWhichField"
// will affect the way how campaigns are displayed
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(DashboardActions, dispatch)
}
export default connect(select, mapDispatchToProps)(App);
App has now access to state and all actions as props.
The problem I see with it right now is:
I would like CampaignHeader to fire the action that will change the state.order state. Let say I will make <th>Text</th> inside CampaignHeader clickable. This will fire the action to change state.order which will in turn affect campaigns order on a next rerender.
So I have my action available inside App props. To pass it down to
CampaignHeader I would have to:
pass it down to CampaignHeader as props
assign it to variable inside CampaignHeader and pass it down as props to CampaignHeaderRow
assign it to variable inside CampaignHeaderRow and pass it down as props to CampaignHeader
assign it to variable inside CampaignHeader and fire the action inside onClick event....
This is a lot of boilerplate, assignments and bag passing! Just to get action fired.
All the components along the way are aware of this action.
When I decided to implement this feature I have opened CampaignHeader component file. I have added the logic and called the action, I have added the action to action file. All I needed is to get a props set. CampaignHeader component doesn't hold a reference to its parent so I didn't know straight away where should this props be injected from(in this example is obvious but I hope you get a point).
What if I will have even deeper component structure?
Is this approach correct?
Could I tackle this problem differently?
UPDATE:
As #Errorpro suggested will it be ok to connect single action and state.order to CampaignHeader?
Worried about: If I will do it once I will be doing it all the time.

There's a discussion in the issue-section of the Redux github repo about wether it's okay to use multiple connects or if everything should be passed down from the top through props, and in there Dan Abramov (the creator of Redux say's:
[...]
Nobody advocates a single connect.
[...]
The "single" only refers to small apps like the one we create in the
example. Please feel free to amend the docs to better clarify this. I
am now busy with other projects so please don't expect this issue to
get any movement unless somebody makes a PR. You can do it too.
The comment probably makes more sense in context though so check out the entire issue thread https://github.com/rackt/redux/issues/419#issuecomment-140782462

If you use redux you should know about dumb and smart component. So we use this sctructure:
component
index.js
Component.js
ComponentContainer.js
Dumb component just get props and render it. More interesting in smart component. Here it is:
export default compose(
relay({
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
any data from relay
}
`,
},
}),
connect(
null,
{
onCreate: createUserAction,
},
(stateProps, actionProps, parentProps) => ({
...parentProps,
onCreate={() => actionProps.onCreate('user')},
})
),
)(Component);
So, parentProps and onCreate function will be in dumb component's props. There you can use this.props.onCreate and invoke it or pass it farther.

Passing the actions - like any other props - from parent to child to grandchild etc is the idiomatic React way. In my opinion your approach is correct; even if it feels wrong.
But there are a couple of alternatives.
There is a feature in React called context. Context permits the passing of fields from a higher order component to a lower order component whilst skipping the middlemen. However, it's an experimental feature so I would recommend avoiding it for now. https://facebook.github.io/react/docs/context.html
Additionally, there is a Redux specific way where you can make any lower order node of your choosing a "smart component" (in the Redux sense). That is, you wrap your class export in the connect function to plug it directly to the store, in the exact same way you do for the Root node.
Personally I tend to stick to the top-down way. There may be a fair bit of boilerplate involved but at least it means your application is easy to reason about.

Related

How do I access the state of another component via Redux store

Say I have two components, App and DialogBox;
so my react store also has two objects
const rootReducer = combineReducers({
app: appReducer,
dialog: dialogReducer
});
export const store = createStore(rootReducer);
Now, when I have to open the Dialog box, I call the dialogBoxActions.openDialog()
And, when I close the Dialog box, I call dialogBoxActions.closeDialog() and also appActions.notifyDialogClosed();
This works, but is there a way to do this in more clearer way?
For example can I use the state.dialog from the store in App? This is what I tried in the App
const mapStateToProps = (state) => {
return {
checkCloseDialog: state.dialog.openDialog
}
}
The ComponentWillReceiveProps does get the checkCloseDialog object, but it gets the old state. I debugged to find out that it gets triggered correctly after the reducer function of the DialogBox component but I get old data.
Is there any elegant way of accessing each other's store or is it Redux's philosophy that components should communicate with each other via actions?
Ah! Okay, my bad.
Yes, it is possible. And it works. My mistake was I was trying to use it inside ComponentWillReceiveProps() method. The thing is, it seems the redux store gets updated later. So, ComponentWillReceiveProps() will hold an old state.
Hence, render() and ComponentDidUpdate() methods get the updated state.
The aim here is to help reduce the multiple action calls between independent components.
For example, a Dialog should not be concerned with what the caller should do after it closes itself. Instead the caller should subscribe to the state of Dialog to decide what to do.
Hope this helps others.

How to use Redux store in one component without Provider but with connect, because children aren't necessary?

I want to use connect for its performance optimizations and ease of use with mapStateToProps. I don't think I need to pass the store to the component from a <Provider> wrapper component to any child components, because I don't have any child components that will need the store; I want the store to be in one component, which is "Header.jsx". Mainly, I don't have any other components, other than the default React, and Material-UI, ones, that I want to use the store with.
How would I go about it? I tried to pass the store through defaultProps and used export default connect(mapStateToProps)(Header) but it keeps saying Uncaught Invariant Violation: Could not find "store" in the context of "Connect(Header)". I read that context is what's used to get props passed down the tree using the provider.
I'm new to Redux and React so if I'm going about this completely the wrong way, please let me know.
If using connect can't be done without a provider, how would I go about wrapping my class from the inside?
Example:
class componentName extends Component {
constructor(props) {
super(props);
};
render() {
return (
<h1>Hello World!</h1>
);
}
}
export default connect(mapStateToProps)(<Provider store={storeName}>componentName</Provider>); // Maybe something like this?
I think you simply cannot use connect() without the <Provider/> - it depends on that pattern being followed. If you want to use connect(), the connected component must be a descendant of the provider. The example you have suggested of including the <Provider/> in the call to connect() will not work, as:
a) That method takes a react component class, not an already instantiated react element, and
b) Even then, it creates a component class that, upon being instantiated/mounted, checks the context for a store, and this happens both above (in terms of DOM-hierarchy) the Provider that would create the context and before it is mounted and has a chance to create that context.
What's the reason you are against using the <Provider/>? Are you trying to prematurely optimize because you think including the provider at the root of your app will have some performance impact? If so, I think you may find there is no appreciable impact from including it, or if you are experiencing one, I would suggest that the problem may be in the setup of your reducers, not simply in the use of <Provider/>.
If you are absolutely set on not using the reducer, you could take your Store object (returned from wherever you are calling createStore()), and, in the componentDidMount() of your one component that needs it you could store.subscribe() to listen to state changes, then use store.getState() to get those changes and load them into state. But eventually, you'll find you are just reimplementing <Provider/>, although maybe without the context part.
TL;DR: Sounds like an XY problem

Reactjs accessing state of another component

Basically, I have a component called Timeperiod which is a dropdown where users will select values such as "today", "yesterday", "last week", etc.
I want to re-use the <Timeperiod> component in a lot of other components and the problem is I wouldn't be able to access which value they chose if I call it in another.
I know an alternative would be to call my other components inside of Timeperiod and passing them properties based on Timeperiod state but wouldn't that mean I need to create multiple Timeperiod like classes?
Would redux or something be a good way to handle these situations?
Here's my codesandbox: https://codesandbox.io/s/ypyoq8zoz
Right now I only have App.js calling Timeperiod, but I will have many more classes calling it too. I want to be able to access the date chosen in App.js while making the Timeperiod class re-usable.
To decide if you need Redux and how much of Redux do you need, you have to consider carefully the whole application you are developing. Redux adds some boilerplate, which might be too much for the benefits you would gain.
You can however solve your problem without Redux.
Without Redux
One of the key ideas under React is called "Lifting state up". This means that if several components need access to the same piece of data, you should definitely move the storage of that piece of data in a component that is a common ancestor of all those components (see https://reactjs.org/docs/lifting-state-up.html).
In your case, it should not be responsibility of Timeperiod to keep memory of the selected date, but of App (indeed, the closest common ancestor of all components that need access to the selected period). Timeperiod should take the selected date from app via a property, and notify App when user changes the selected date using an event (again, a prop containing a function).
class App {
constructor(props) {
super(props);
this.state = {
start: new Date(), // initial value
end: new Date(), // initial value
}
}
render() {
return (
<Timeperiod
start={this.state.start}
end={this.state.end}
onStartChange={newValue => this.setState({ start: newValue })}
onEndChange={newValue => this.setState({ end: newValue })}
/>
);
}
}
With Redux
Redux allows you to keep a global state and access it in special components called Containers. You can put how many containers you want, in any point of the document tree. This seems great, but has several drawbacks:
Too many container components degrade performance
Having full access to whole state in any point of the tree could create problems if you are not super careful. A component could modify some data it should not be allowed to access, and so on.
Redux introduces some boilerplate that might be too much if the application is simple
For any update you have to define some actions capable of handling it, create a reducer to produce the next state from the previous state and the action, and connect together all these pieces
Conclusion
It really depends on your application whether Redux is good or bad, but if your component hierarchy is not too deep and your app not too complex, vanilla way could be better
So, to follow up our comments. Here's how you could approach it:
The Timeperiod component expects a getDate function that expects a title argument. When you render the Timerperiod component, each time it has a separate state and props.
Check out how I rendered more than one(in the app.js), to show that in action.
Using redux what you could do is have a within your state a timePeriod sub state handled by a dedicated reducer which stores the user's choice.
Then each of your TimePeriod component will be hooked to this state using something like
const ConnectedTimePeriod = connect(
state => state.timePeriod,
{
onTimePeriodChoose (timePeriod) {
return {
type: "CHOOSE_TIME_PERIOD",
timePeriod
}
}
}
)(TimePeriod);
Which hydrates your component with the global shared state and provides a function as a prop to update the time period.
The ConnectedTimePeriod can then be used everywhere and will share the global state.
I would suggest you take a look at the react-redux docs for example and details.

Redux global solution for Cannot read property of undefined in mapStateToProps if store is not yet hydrated

After migrating to react redux land, I'm in love.. yet my heart breaks every time i try to render a route and gets a Cannot read property 'name' of undefined in the mapStateToProps function which is trying to extract a slice of state that is not loaded yet.
Example:
state = { posts:{ blog : {byId:{},ids:[]}, wall: {byId:{},ids{} } };
Say I do fetch all data from the server once the app starts up "index.js entry point". Based on the assumption that all data is loaded, I code multiple components to filter this data and work on it.
Trouble happens when a user opens the app and navigates too fast to a page where the data is not yet loaded. Then I get the hated error of "Cannot read property 'name' of undefined"
Example:
//route ::/posts/blog/1 has something like this
export default connect(
(state,prop)=>({
postId: state.posts.blog.byId[props.params.id].id
}))(props=><div>Post Id:{props.postId}</div>);
Now this components if loaded before blog store is populated it will give an error.
An option is to return a placeholder to connect and check first that post is loaded, or to use a magical proxy in initialState
initialState.blog = {byId:new Proxy({},{get:(obj,name)=>(name in obj)?obj[name]:{}}),ids[]}
//this will suppress error by returing an empty object if undefined byId
Is there any way i can REQUIRE a component to load its data by dispatching an action for example before it even renders?
What i want is something like what propTypes does except that it will defer loading the component or its connect function until the store confirms that it has finished loading.
Idea from propTypes:
Component.propTypes = {
dispatch:PropTypes.func.isRequired,
};
This will give an error if dispatch is not supplied as a prop for component
i want something similar but for store.
Example:
Component.requireData = (state)=>({
posts.blog:<placeHolder actionToDispatch="POST/BLOG/LOAD"/>, //if not posts.blog.isLoaded render placeholdercomponent insteed.
});
So it happens automagically if the required data is not loaded yet, the component will not render, and the placeholder will render in place dispatching an action that load the required missing data.
Whenever a component's render is called, it means the parent decided the component should exist. mapStateToProps is free to examine the state in any detail it needs to and produce any props from it as it sees fit. If a part of the state is missing, it can substitute some other props.
Some part of your system will have to decide when a place holder should be rendered rather than the data presentation component. The component that decides this may be the component itself or a parent. If it's a parent then you can make this component reusable.
No matter which way you turn this, you have to code the part that decides if all the data is ready. There's no magic. At the very least you'd need to pass a field name, but a function would be more flexible. At this point you may as well plug this function into the connect directly.
As such it seems to me that just writing a simple reusable parent component that takes a boolean prop that decides whether to show the child or a placeholder is not only a good and simple solution, but it seems to me that any other solution may not be substantially better or more "magical". Also "magic" is generally regarded as a bad idea these days because it implies a lack of transparency. By sticking to the React-Redux way, you are not adding hard to understand code.
Finally, don't expect either React or Redux to know what to do when your app doesn't provide the data that your app requires. Either don't require it or provide it. Creating a simple reusable parent component solves the issue by not requiring the data (and deciding what to do instead, which neither React, nor Redux, nor probably a third party library knows to decide).
Recap: create your own magic. It's simple, quick, and perfectly tuned to your needs.

Accessing a component's state from another component

Would like a bit of knowledge gain with your help on this one. Just to clarify, I'm still educating myself with Reactjs.
I have two components, A and B. I need to access A's state from B. Is that possible?
var A = React.createClass({
getInitialState(){
return {foo: 'bar'}
},
...
});
var B = React.createClass({
getInitialState(){
return {x: 'y'}
},
render(){
var a = <A />;
var b = a.state.foo; // This was just a guess but I dont understand the docs for something like this.
return({b});
}
});
In B's component, how to render A's state, which is bar? I wish to have two separate components.
I have been reading about ref but still can't figure out how to accomplish what I want with refs.
React version: 0.14.3
Let's look at the purpose of state and props. A component's state is completely internal to a component. It is never known outside of the component and may/not be passed down to the children components (as props). Props on the other hand are explicitly public and are merely consumed by the component. They should be the only means of passing information down to a component.
While designing your components, always consider this that any data required by more than a single component cannot be a state of any one of them. React encourages unidirectional data flow and accessing state across different components violates that, thereby making your components difficult to reason with.
In your case, since B needs to know some information which A has, I suggest something like this -
A publishes this information to a Flux store at an appropriate time.
B has subscribed to the same Flux store, it gets notified of this
information and updates itself.
#HazardousS and #DERIIIFranz are wrong.
In reply to #HazardousS, just because you need data to go from one component to another does not warrant implementing Flux into your app. You always want to use props, then state, then... Flux. The lesson here is, "Only use Flux if you need to." Pete Hunt, an earlier Facebook-er who worked on React published this last week. It disspells a lot of the hype and misinformation in the React community: https://github.com/petehunt/react-howto
In regards to #DERIIIFranz about using Context: DO NOT USE CONTEXT. If you just click on the link the Facebook docs even have a warning for its own feature. It's something cute that the React docs does... they give you a lot of rope to hang yourself with.
It seems like you haven't gone through the React tutorials yet because you're asking basic questions about data messaging. This is something you need to understand in a fundamental way if you want to succeed in React. Put in the time, do the tutorials on the Official React docs page, and get familiar with the "React Way" of doing things. It's weird, and odd... but it'll click if you buckle down and sink in the time to learn the fundamentals.
We have to think of ways to make available the this.state.somevariable of <A/> inside <B/>.
If <A/> and <B/> are siblings of a common <Parent/>, the parent can change this state for both <A/> and <B/> and pass the changed state as this.props.statevariable to the children.
If <B/> can be a child of <A/>, <A/> can always pass its this.state.statevariable as props to <B/>.
If you go through the Flux Architecture, you can maintain these variables required for rendering inside the Stores and access these in as many components as you like.

Resources