I am completely lost on the react-redux container (ie connector) concept as it is not doing what I anticipated. My issue is straight forward, and to me reasonable, yet I cannot find a well written example of how to accomplish it.
Let us say we have a react component that connects to a store that has product context, and we will call this component ProductContext.
Furthermore, let's say we want to reuse ProductContext liberally throughout the app so as to avoid the boilerplate code of dispatching actions on every other component that may need products.
Illustratively this is what I mean:
from DiscountuedProducts:
<ProductContext >
// do something with props from container
</ProductContext >
from SeasonalProducts:
<ProductContext >
// do something with props from container
</ProductContext >
From the examples I see at react-redux, it appears to me that their containers lump both seasonal and discontinued products in the container itself. How is that reusable?
from the ProductContextComponent:
<section >
<DiscontinuedProducts />
<SeasonalProducts />
</section >
Complicating matters, while trying to keep a cool head about this most frustrating matter, "nebulous tersity" seems to be the only responses I receive.
So here is my ProductContext:
#connect(state => ({
products: state.productsReducer.products
}))
export default class ProductContext extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { dispatch } = this.props;
const clientId = this.context.clientInfo._id;
dispatch(fetchProductsIfNeeded(clientId));
}
// get from parent
static contextTypes = {
clientInfo: PropTypes.object.isRequired
};
static propTypes = {
children: PropTypes.node.isRequired,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
render() {
if (!this.props.products) return <Plugins.Loading />;
.....
return (
// I want to put the products array here
);
}
};
Then my thinking is if I do this:
from DiscountuedProducts:
<ProductContext >
// do something with props from container
</ProductContext >
DiscontinuedProducts should have knowledge of those products and it can simply filter for what is discontinued.
Am I completely wrong on this? Is this not reasonable to desire?
If anyone knows of a exhaustive example on the Net demonstrating how this can be achieved, I would much appreciate it being pointed out to me. I have spent over a week on this issue and am about ready to give up on react-redux.
UPDATE: A very slick solution below with use of a HOC.
If anyone knows of a exhaustive example on the Net demonstrating how this can be achieved, I would much appreciate it being pointed out to me.
Look at the Shopping Cart example.
Examples code from: shopping-cart/containers/CartContainer.js
Let us say we have a react component that connects to a store that has product context,
There isn't a store for products and a store for users, etc. There is one store for everything. You should use reducers to take the full store and reduce to what your component needs.
From the example:
import { getTotal, getCartProducts } from '../reducers'
const mapStateToProps = (state) => {
return {
products: getCartProducts(state),
total: getTotal(state)
}
}
Here state is the entire store.
let's say we want to reuse ProductContext liberally throughout the app so as to avoid the boilerplate code of dispatching actions
Components don't dispatch actions, they call functions passed to them. The functions live in an actions module, which you import and pass to container as props. In turn, the container passes those functions to component props.
From the example:
import { checkout } from '../actions'
CartContainer.propTypes = {
checkout: PropTypes.func.isRequired
}
connect(mapStateToProps,
{ checkout }
)(CartContainer)
What connect does, it subscribes to store changes, calls the map function, merges with constant props (here the action function), and assigns new props to the container.
DiscontinuedProducts should have knowledge of those products and it can simply filter for what is discontinued
This is actually spot on. The knowledge you mention is the one store, and it should absolutely filter in a reducer.
Hopefully this clears things up.
I have found a more practical way of utilizing repetitive data from redux than the docs. It makes no sense to me to repeat mapToProps and dispatch instantiation on every blessed component when it was already done once at a higher level, and there inlies the solution. Make sure your app is Babel 6 compliant as I used decorators.
1. I created a higher order component for the context ....
product-context.js:
import React, { Component, PropTypes } from 'react';
// redux
import { connect } from 'react-redux';
import { fetchProductsIfNeeded } from '../../redux/actions/products-actions';
// productsReducer is already in state from index.js w/configureStore
#connect(state => ({
products: state.productsReducer.products
}))
export default function ProductContext(Comp) {
return (
class extends Component {
static propTypes = {
children: PropTypes.node,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
static contextTypes = {
clientInfo: PropTypes.object.isRequired;
};
componentDidMount() {
const { dispatch } = this.props;
const clientId = this.context.clientInfo._id;
dispatch(fetchProductsIfNeeded(clientId));
}
render() {
if (!this.props.products) return (<div>Loading products ..</div>);
return (
<Comp products={ this.props.products }>
{ this.props.children }
</Comp>
)
}
}
)
}
2. component utilizing product-context.js
carousel-slider.js:
import React, { Component, PropTypes } from 'react';
......
import ProductContext from '../../../context/product-context';
#Radium
#ProductContext
export default class CarouselSlider extends Component {
constructor(props) {
super(props); }
......
static showSlideShow(carouselSlides) {
carouselSlides.map((slide, index) => {
......
results.push (
......
)
});
return results;
}
render() {
const carouselSlides = this.props.products;
const results = CarouselSlider.showSlideShow(carouselSlides);
return (
<div id="Carousel" className="animation" ref="Carousel">
{ results }
</div>
);
}
}
So there you go. All I needed was a decorator reference to product-context, and as a HOC, it returns the carousel component back with the products prop.
I saved myself at least 10 lines of repetitive code and I removed all related contextType from lower components as it is no longer needed with use of the decorator.
Hope this real world example helps as I detest todo examples.
Related
I want to be able to move from Redux easily (use another flux-like implementation) or reuse our React components to create dynamic views, what distinction should I do and how can I implement this idea? You can use the Dashboard class to illustrate.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// fetches projects from a remote server using Redux actions
import { fetchProjects } from '../../ducks/projects';
export class Dashboard extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
projects: PropTypes.array.isRequired
};
// Voluntarily obfuscated
componentXXX() {
this.fetchData();
}
fetchData() {
const { dispatch, status } = this.props;
dispatch(fetchProjects({ status }));
}
render() {
const { isFetching, projects } = this.props;
return <ProjectsPane projects={projects} isFetching={isFetching} />;
}
}
function mapStateToProps(state) {
const { isFetching, projects } = state;
return {
isFetching,
projects
};
}
export default connect(mapStateToProps)(Dashboard);
export class ProjectsPane extends Component {
// ...
}
One thing you can do in Redux applications is separate your connected (container) components from presentational components. Dan Abramov, the author of Redux, has a great article on this pattern here. To sum it up, you build your presentational component with no knowledge of Redux. It gets its data through props and requests its data through callback props (ex. this.props.onNeedData()). In your example, your Dashboard component is close to being a presentational component already. You can take it a bit further by using mapDispatchToProps to pass in the fetchProjects function as a callback prop. You can read more about the mapDispatchToProps argument here. Additionally, I would follow Dan's advice and start by connecting the top-level component and pass data/callbacks down through props until that becomes less manageable and you need to connect other components. This will help in minimizing how much of your code is aware of Redux.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
// fetches projects from a remote server using Redux actions
import { fetchProjects } from '../../ducks/projects';
export class Dashboard extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
isFetching: PropTypes.bool.isRequired,
projects: PropTypes.array.isRequired
};
// Voluntarily obfuscated
componentXXX() {
this.fetchData();
}
fetchData() {
const { onNeedData, status } = this.props;
onNeedData({ status });
}
render() {
const { isFetching, projects } = this.props;
return <ProjectsPane projects={projects} isFetching={isFetching} />;
}
}
function mapStateToProps(state) {
const { isFetching, projects } = state;
return {
isFetching,
projects
};
}
const mapDispatchToProps = {
onNeedData: fetchProjects
};
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard);
Also, it is worth evaluating your project and deciding if you really need Redux. Are you using Redux because you have evaluated the cost and benefits of it and saw that it would be a positive for your project? A lot of React tutorials around the web make it seem like you can't build a React project without Redux or some state management, but React's state management is pretty good out of the box. In fact, Dan Abramov has an article title You Might Not Need Redux that is worth checking out.
I will try and make this as short and sweet as possible while being detailed and descriptive. When my application initializes or loads for the first time, it makes an api call to fetch some blog data. Here is what that looks like more or less:
As you can see the api successfully returns the blogPosts. I then reference these blogPosts in another component using createStructuredSelector from the reselect library. Here is the code:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { compose } from 'redux';
import VisionBlogItem from '../../components/VisionBlogItem';
import { getBlogItem, getFollowingBlogItems } from './selectors';
export class VisionBlogItemPage extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props, context) {
super(props, context);
this.state = {
};
}
render() {
return (
<div>
{this.props.blogItem && <VisionBlogItem blog={this.props.blogItem} blogItems={this.props.blogItems} />}
</div>
);
}
}
VisionBlogItemPage.propTypes = {
dispatch: PropTypes.func.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
}),
blogItem: PropTypes.object,
blogItems: PropTypes.array,
};
VisionBlogItemPage.defaultProps = {
blogItem: {},
blogItems: [],
};
const mapStateToProps = (state, ownProps) => {
return createStructuredSelector({
blogItem: getBlogItem(ownProps.location.pathname.match(/([^/]*)\/*$/)[1]),
blogItems: getFollowingBlogItems(ownProps.location.pathname.match(/([^/]*)\/*$/)[1])
});
};
function mapDispatchToProps(dispatch) {
return {
dispatch,
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
export default compose(
withConnect,
)(VisionBlogItemPage);
What these selector methods are doing, getBlogItem and getFollowingBlogItems, are checking the props location pathname end slug and using it to iterate and array to find the needed blog in the Redux State Store. They then get set to the props named blogItem and blogItems to be passed to the presentational component to render. That presentational component looks something like this:
At the bottom of this presentational component are some links that will change the slug name. So if I'm at localhost:3000/blogPosts/blog-post-0 and I clicked on one of the three blog posts at the bottom, it would then change the url to localhost:3000/blogPosts/blog-post-1 or blog-post-2, blog-post-3, etc. These blog posts at the bottom are links from react-router-dom and the code is written as such:
<Link to={props.blogItems[2].fields.slug} style={styles.seeMoreLink}>
<img src={props.blogItems[2].fields.articleImage.fields.file.url} style={styles.seeMoreImg} alt="" key="2" />
</Link>
So the idea is that it changes the slug of the url and renders the appropriate blog post. However, this is not what is occurring. The url slug does change and I can even see in my container component that the props update and change as well. The location pathname changes from blog-post-1 to blog-post-2. My question then lies why does my component not re-render? I am aware of the life-cycle method of componentWillReceiveProps, but I do not know how to use it in conjunction with something like createStructuredSelector from reselect. Anyone who could possibly give me a clear path as to how I am suppose to handle this it would be greatly appreciated!
I have a number of React components that need to fetch certain items to display. The components could be functional components, except for a very simple componentDidMount callback. Is there a good design pattern that would allow me to return to functional components?
class Things extends React.Component {
componentDidMount() {
this.props.fetchThings()
}
render() {
things = this.props.things
...
}
}
I'm using react-redux and I'm also using connect to connect my component to my reducers and actions. Here's that file:
import { connect } from 'react-redux'
import Things from './things'
import { fetchThings } from '../../actions/thing_actions'
const mapStateToProps = ({ things }) => ({
things: things,
})
const mapDispatchToProps = () => ({
fetchThings: () => fetchThings()
})
export default connect(mapStateToProps, mapDispatchToProps)(Things)
Would it make sense to fetch the things in this file instead? Maybe something like this:
import { connect } from 'react-redux'
import Things from './things'
import { fetchThings } from '../../actions/thing_actions'
const mapStateToProps = ({ things }) => ({
things: things,
})
class ThingsContainer extends React.Component {
componentDidMount() {
fetchThings()
}
render() {
return (
<Things things={this.props.things} />
)
}
}
export default connect(mapStateToProps)(ThingsContainer)
Functional components are meant to be components that don't do anything. You just give them props and they render. In fact, if your component needs to fetch anything at all, it's likely your component should be transformed into a container which fetches the data you need. You can then abstract the UI part of your component into one or more pure functional components which your container renders by passing the data it got as props.
I believe the presentational/container component split is the pattern you're looking for here.
I have a container that passes props and an apiCall action to a component which will mainly just render the result of that call. My question is should I leave the invoking of that action up to the component or move it out into the container and just pass the array of items to the component?
Here is my container code. The fetchShowingsListShowings is the one in question. Also, I will be renaming that soon enough so bear with me.
import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as actions from '../actions/showingsListActions';
import ShowingsList from '../components/ShowingsList';
const ShowingsListContainer = (props) => {
return (
<ShowingsList
isLoading={props.isLoading}
showings={props.showings}
fetchShowingsListShowings={props.actions.fetchShowingsListShowings}
/>
);
};
ShowingsListContainer.propTypes = {
isLoading: PropTypes.bool.isRequired,
showings: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
return {
isLoading: state.showingsList.isLoading,
showings: state.showingsList.showings
};
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(actions, dispatch)
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(ShowingsListContainer);
And my component. Which calls the API action on componentWillMount.
import React, { PropTypes } from 'react';
import ShowingsListItem from './ShowingsListItem';
class ShowingsList extends React.Component {
componentWillMount() {
this.props.fetchShowingsListShowings();
}
render() {
return (
this.props.isLoading ? <h1>Loading...</h1> :
<ul className="list-unstyled">
{this.props.showings.map((showing,index) => <ShowingsListItem showing={showing} key={'showing' + index}/>)}
</ul>
);
}
}
ShowingsList.propTypes = {
isLoading: PropTypes.bool.isRequired,
showings: PropTypes.array.isRequired,
fetchShowingsListShowings: PropTypes.func.isRequired
};
export default ShowingsList;
Thanks in advance.
So in React with Redux the term 'Container' just means a component that is connected to the Store, essentially whatever you use the react-redux 'connect' method with. Your ShowingsList can be a 'dumb' (or functional) component meaning it's just a component that takes in data and displays content. The general 'best' practice is to have your dumb components just be concerned with presentation, and your container components handle all the logic interacting with the Redux Store. If you follow this logic, fetch the data in the container, and pass the data to the nested component. That being said, it'll work either way so you don't really need to change anything if you're happy with it now.
To follow this pattern do something like this:
modify your Container component to be an ES6 class extends React.Component.. and optionally change your ShowingsList to be a functional component (like your ShowingsList is now)
put a componentWillMount in your Container and put the API call there.
pass the list to the presentational component.
Here's an article written by Dan Abramov, the author of Redux on this very topic.
https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.g695y2gwd
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { setLocation } from 'redux/modules/filters'
import SearchForm from 'components/SearchForm/SearchForm'
type Props = {
};
export class HomeSearchContainer extends React.Component {
props: Props;
static contextTypes = {
router: React.PropTypes.object.isRequired
};
constructor(props) {
super(props)
this.onSearch = this.onSearch.bind(this)
}
onSearch(address, event) {
event.preventDefault()
if (address) {
this.props.actions.setLocation(address)
}
this.context.router.push('/browse_items')
}
render() {
return (
<SearchForm
onSearch={this.onSearch}
currentLocation={this.props.currentLocation}
/>
)
}
}
const mapStateToProps = (state) => {
return {
currentLocation: state.filters.location
}
}
const mapDispatchToProps = (dispatch) => {
var actions = {
setLocation
}
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeSearchContainer)
I have a few questions to validate my understanding.
Do we ever re-use containers? Am I correct if I say we intend to re-use components but not containers?
In the above code, I want to create another container that doesn't redirect to /browse_items. In other words, I just want to override onSearch function of this container. Is it okay to extend this container?
First of all, in my mind a container is a certain kind of component, so following this article: https://medium.com/#dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.jqwffnfup I'll rather talk about container components and presentational components.
Ad 1) I would say we can re-use container components as well as presentational components - this always depends on how our code is structured. E.g. a container component might have child components, which can be different in different parts of the screen, but the container component is being re-used.
Ad 2) If you just want to have a different onSearch functionality, I might consider passing the onSearch callback to the HomeSearchContainer via the props. That way, you can re-use the same container, but it behaves differently.
Looking closely at your code, there is then not much left to the HomeSearchContainer, so you might as well use the SearchForm directly. If you need the SearchForm in multiple places, it might be worth pulling out the onSearch function into its own file and re-using that. But this is hard to judge without seeing the rest of the application. So I'm trying to throw some ideas at you here, which may or may not apply to your codebase.