Using React.js props on Redux's 'compose' - reactjs

I am using react-redux-firebase and firestoreConnect to get information from my database and map it into props vía their premade reducer. To connect its information to my Component, I use the 'compose' method in the following way:
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect([
{
collection: 'questions',
doc: this.props.match.params.id,
subcollection: 'messages',
}
])
)(QuestionDetail);
As you can see, I am trying to access a specific document with an ID that was passed to the component as a prop from its parent. However, 'this' is undefined in the context of 'compose' and I therefore can't use it to access the props, and my parameter.
Is there any other way I can access the id passed to the component so I can request it to Firestore?

After a deeper search through the documentation I found that you can pass props to the firestoreConnect function (Note also that I was querying subcollections in the wrong way). The code ends up being:
export default compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect((props) => [
{
collection: 'questions',
doc: props.match.params.id,
subcollections: [{ collection: 'messages' }],
}
])
)(QuestionDetail);

It should work in older version of react-router.
But I assume you are using the latest version of React Router (v4+). So you have to wrap the whole component with the "withRouter" higher-order component. It will pass updated match, location, and history props to the wrapped component whenever it renders.
At first, you have to import the withRouter.
import { withRouter } from "react-router";
Then you have to wrap it with withRouter.
export default withRouter(compose(
connect(mapStateToProps, mapDispatchToProps),
firestoreConnect((props) =>
[{
collection: 'questions',
doc: props.match.params.id,
subcollections: [{ collection: 'messages' }],
}]
))(QuestionDetail));
This works for me.

Related

Export component using withStyles() and compose() with Redux/Firebase/Material UI

So I'm trying to put together this app, using firebase and redux for storage, and Material UI as the design. I've got the firebase and firestore reducers working and all, I just run into an issue when I try to export a component using both firebase and withStyles();
(It works separately, just throws an error when I try to use both.)
Here's what I've tried:
This works, but the withStyles() is not there.
export default compose(
firebaseConnect([{ collection: 'clients' }]),
connect(mapStateToProps),
)(Clients);
This works, but it's not connected to firebase.
export default connect(mapStateToProps)(withStyles(styles)(Clients));
I've tried combining them, but each one throws an error.
export default compose(
firebaseConnect([{ collection: 'clients' }]),
connect(mapStateToProps),
)(withStyles(styles)(Clients));
export default compose(
firebaseConnect([{ collection: 'clients' }]),
connect(mapStateToProps),
withStyles(styles, {
name: 'Clients',
}),
)(Clients);
The error thrown is Uncaught Error: Path is a required parameter within definition object
Any help would be greatly appreciated. Thank you!
OK, after some messing around, I figured it out. I have to place the firebaseConnect() inside of the connect, where the actions would usually go.
const withFirebase = firebaseConnect([{ collection: 'clients' }]);
export default connect(mapStateToProps, withFirebase)(withStyles(styles)(Clients));

How to point firestoreConnect to a nested collection in React-Redux-Firebase?

The below line directs firestoreConnect to my collection labeled projects.
{ collection: 'projects' }
It works when the projects collection is immediately under the root like this:
root/
projects/
But what if the projects collection is referenced by a doc which itself is within another collection, say, like this:
root/
users/
alice/
projects/
How do I point firestoreConnect to the projects collection?
The documentation is silent on the matter.
Link to video
import React, { Component } from 'react'
import ProjectList from '../projects/ProjectList'
import Notifications from './Notifications'
import { connect } from 'react-redux'
import { firestoreConnect } from 'react-redux-firebase'
import { compose } from 'redux'
import { Redirect } from 'react-router-dom'
class Dashboard extends Component {...}
const mapStateToProps = (state) => {
// console.log(state);
return {
projects: state.firestore.ordered.projects,
auth: state.firebase.auth,
}
}
export default compose(
connect(mapStateToProps),
firestoreConnect([
{ collection: 'projects' }, // <-- Question is about this line
])
)(Dashboard)
Edit: Unsuccessful Attempt
From what I can piece together from the comments here it seems like the answer might be:
firestoreConnect([
{
collection : 'users',
doc : 'alice',
collection : 'projects',
}
])
But that attempt has failed.
It can be done by passing multiple collection/doc settings to the subcollections parameter like so:
firestoreConnect(() => [
{
collection: 'states',
doc: 'CA',
subcollections: [
{ collection: 'cities', doc: 'SF' },
{ collection: 'zips' }
]
}
])
This is noted in the firestoreConnect section of the react-redux-firebase docs (since it is a react specific HOC). The options that can be passed to queries are documented in the README of redux-firestore.
If you are doing nested subcollections, you will probably want to use the v1.*.* versions since subcollections are stored along side top level collections in redux state. Note that the new version has a different API , as described in the v1.0.0 roadmap.
Disclosure: I am the author of redux-firestore

How to update firestoreConnect (using listener) in React-Redux-Firebase?

How do I force firestoreConnect to update? It pulls correctly from Firestore when the component mounts. But after Firestore data changes, it does not fetch the new data. Almost like there is no listener attached. Any ideas?
TodoList.js
export default compose(
connect(mapStateToProps),
firestoreConnect([{
collection: 'todos'
}])
)(TodoList)

Redux connect "blocks" navigation with react-router-redux

In an application using react, redux and react-router, I'm using react-router-redux to issue navigation actions. I found that wrapping routes in a component with connect blocks navigation.
I made a sample with CodeSandbox that illustrates the issue: sample.
As is, the navigation doesn't work. However, if in ./components/Routes.jsx, this line:
export default connect(() => ({}), () => ({}))(Routes);
Is replaced by:
export default Routes;
It works.
Any idea how I could use connect in a component that wraps routes without breaking navigation?
See the troubleshooting section in react-redux docs.
If you change Routes.jsx export to:
export default connect(() => ({}), () => ({}), null, { pure: false })(Routes);
it will work.
This is because connect() implements shouldComponentUpdate by default,
assuming that your component will produce the same results given the
same props and state.
route changes, but props don't so the view doesn't update.
You could achieve same with withRouter hoc.
Not meant to be a duplicate.
I fixed it with withRouter like this
import { withRouter } from 'react-router-dom';
and
export default withRouter( connect(mapStateToProps)(App) );
See Redux, Router integration docs here
Have you ever encountered the warning message:
Warning: You cannot change <Router history>
Well use withRouter from react-router-dom
I have searched for this for so long because the Redux was recreating my App.jsx component which has <Route> </Route> as parents and this warning just freezes the routing in my app. I wanted to have React/Redux component, because I needed to pass authenticated props to the Route component, and redirect base on it, simple.
So import { withRouter } from 'react-router-dom'
and surround your component which is connected to redux with:
export default withRouter(connect(mapStateToProps)(App));
Something more:
Most of the times if you want to communicate with the router, takes some props, pass something else to it, get history, locations form it and you are using Redux in your app, surround this component with withRouter and you will have access to these properties as props.

react-redux: how do you access the store from 'anywhere' without importing it?

In the react-redux documentation it states that when using React-redux and connect() importing the store is not recommended. It's an anti-pattern.
http://redux.js.org/docs/faq/StoreSetup.html
Similarly, while you can reference your store instance by importing it
directly, this is not a recommended pattern in Redux. If you create a
store instance and export it from a module, it will become a
singleton. This means it will be harder to isolate a Redux app as a
component of a larger app, if this is ever necessary, or to enable
server rendering, because on the server you want to create separate
store instances for every request.
With React Redux, the wrapper classes generated by the connect()
function do actually look for props.store if it exists, but it's best
if you wrap your root component in and let
React Redux worry about passing the store down. This way components
don't need to worry about importing a store module, and isolating a
Redux app or enabling server rendering is much easier to do later.
How, then, do I access the store from any component of my choosing(even deep down in the application) once I've properly wired in the store to my app? My code properly connects App but I can't get access to the store from any child components at all. store.dispatch() is null, store.getState() is null, etc. I feel that the documentation is lacking in this regard. It's said to be magic but I'd like to know how to use the magic. Do I need to write mapDispatchToProps() again and again for every single container component? A use case would be the currentUser prop that would be available to every single child component in the application. I'd like to pass that down from App to every single child.
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);//App is now a connected component, that part is working
Inside App I have a Login component, and I'd like to dispatch an action inside it. But I need a reference to store, but apparently I'm not supposed to import it.
This is where the concept of containers come into play.
Suppose you wanted to render a Login component inside of your App. You will make a connected container.
Step 1 is to create a simple action:
const LOGIN_ATTEMPT = 'auth/LOGIN_ATTEMPT';
export const login = name => ({
type: LOGIN_ATTEMPT,
payload: { name },
});
You will now use react-redux in order to connect this action to your "presentational component". This will come through to the component as a prop.
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { login } from 'actions/auth'; // your action
import Login from 'components/auth/Login'; // your component to connect it to.
// state refers to the "current state" of your store.
const mapStateToProps = state => ({ currentUser: state.auth.user });
// dispatch refers to `store.dispatch`
const mapDispatchToProps = dispatch => {
// calling this is like calling `store.dispatch(login(...params))`
login: bindActionCreators(login, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
This connect function will take these two functions as parameters. You can now import this connected container and use it with the functions "bound" to it as properties.
Example component below.
export default class Login extends Component {
static propTypes = {
// properties below come from connect function above.
currentUser: PropTypes.shape({
name: PropTypes.string.isRequired,
}).isRequired,
login: PropTypes.func.isRequired,
};
state = { name: "" };
onChange = ({ target: { value: name } }) => this.setState({ name });
onSubmit = e => {
e.preventDefault();
login(this.state.name);
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
placeholder="name"
onChange={this.onChange}
value={this.state.name}
/>
</form>
);
}
}
Notice, you never had to reference the store.

Resources