Common DRY principle violation React Native/Redux - reactjs

I seem to be having a reoccurring issue that I'm hoping there is a design solution out there that I am not aware of.
I'm running into the situation where I need to dispatch the exact same things from two different components. Normally I would set this to a single function and call the function in both of the components. The problem being that if I put this function (that requires props.dispatch) in some other file, that file won't have access to props.dispatch.
ex.
class FeedScreen extends Component {
.
.
.
componentWillReceiveProps(nextProps) {
let {appbase, navigation, auth, dispatch} = this.props
//This is to refresh the app if it has been inactive for more
// than the predefined amount of time
if(nextProps.appbase.refreshState !== appbase.refreshState) {
const navigateAction = NavigationActions.navigate({
routeName: 'Loading',
});
navigation.dispatch(navigateAction);
}
.
.
.
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default connect(mapStateToProps)(FeedScreen)
class AboutScreen extends Component {
componentWillReceiveProps(nextProps) {
const {appbase, navigation} = this.props
//This is to refresh the app if it has been inactive for more
// than the predefined amount of time
if(nextProps.appbase.refreshState !== appbase.refreshState) {
const navigateAction = NavigationActions.navigate({
routeName: 'Loading',
});
navigation.dispatch(navigateAction);
}
}
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default connect(mapStateToProps)(AboutScreen)
See the similar "const navigateAction" blocks of code? what is the best way to pull that logic out of the component and put it in one centralized place.
p.s. this is just one example of this kind of duplication, there are other situations that similar to this.

I think the most natural way to remove duplication here (with a react pattern) is to use or Higher Order Component, or HOC. A HOC is a function which takes a react component as a parameter and returns a new react component, wrapping the original component with some additional logic.
For your case it would look something like:
const loadingAwareHOC = WrappedComponent => class extends Component {
componentWillReceiveProps() {
// your logic
}
render() {
return <WrappedComponent {...this.props} />;
}
}
const LoadingAwareAboutScreen = loadingAwareHOC(AboutScreen);
Full article explaining in much more detail:
https://medium.com/#bosung90/use-higher-order-component-in-react-native-df44e634e860
Your HOCs will become the connected components in this case, and pass down the props from the redux state into the wrapped component.
btw: componentWillReceiveProps is deprecated. The docs tell you how to remedy.

Related

React - How can I force call a props function

I have a Component who calls a function via props
function mapState(state) {
return state;
}
const actionCreators = {
getById: productActions.getById,
};
export default connect(mapState, actionCreators)(ShopBook);
this function is used at the constructor of the Component to retrieve content from my API and store the response into a reducer
constructor(props) {
super(props);
this.props.getById(this.props.match.params.slug)
this.state = {
}
}
then in the render I use the store
render() {
const product = store.getState().product.item
return (
// my code
);
}
my problem is, if I change content in the database the function does not retrieve the latest change vía the api, I only see the changes if I force the browser by pressing the Shift key while click reload.
how can I force to get the latest content, where do I need to use the function instead the constructor?
I already tried this solution:
How to fetch data when a React component prop changes?
You are using redux wrongly. You should pass correct mapStateToPop. If u want to render. Since, u mappping entire state. Redux find no change. That is why it is not rerendering.
Sample:
const mapStateToProps = (state, ownProps) => ({
product: state.product
})
export default connect(mapState, actionCreators)(ShopBook);

Understanding state and props changes with rerendering in react components

I have a component which isn't re-rendering as I'd expect. I'm less concerned about this specific example than having a better understanding about how state, props, updates, re-rendering and more happens in the react-redux lifecycle.
My current code is about creating a delivery with a list of locations. The main issue is that reordering the location's itinerary doesn't seem to work - the state updates in the reducer correctly, but the components are not rerendering.
This is the relevant snippet from delivery.js, a component which uses the LocationSearch custom component to display each location in the list of locations:
{console.log("Rendering...")}
{console.log(delivery.locations)}
{delivery.locations.map((location, index) => (
<div key={index}>
<LocationSearch
{...location}
total={delivery.locations.length+1}
index={index}
/>
</div>
))}
The console.logs print out the correct data where and when expected. When an action to reorder the locations is triggered (from within LocationSearch), the console log prints out the list of locations with the data updated correctly. However the component is not displaying anything updated.
Here is some relevant parts of the LocationSearch component:
export class LocationSearch extends Component {
constructor(props) {
super(props)
this.state = {
searchText: this.props.address
}
this.handleUpdateInput = this.handleUpdateInput.bind(this)
}
handleUpdateInput (searchText) {
this.setState({
searchText: searchText
})
this.props.handleUpdateInput(searchText)
}
render(){
const { type, itineraryOrder, floors, elevator, accessDistance, index} = this.props
return (
...display all the stuff
)
}
}
...map dispatch, connect, etc...
function mapStateToProps(state, ownProps) {
return {
suggestions: state.delivery.suggestions,
data: ownProps.data
};
}
This is where I get confused - I figure I'm meant to do something along the lines of componentWillUpdate, but can't find any good explanations for what happens at each step. Do I just set this.props = nextProps in there? Shouldn't this.props have updated already from the parent component passing them in? How come most components seem to rerender by themselves?
Any help you can give or links to good resources I'd be grateful for. Thanks in advance!
I kept running into this kind of issues right before I discovered redux. Considering you mentioned already using react-redux, maybe what you should do is switch to a container/component structure and forget about componentWillUpdate()
Basically, what this allows for is just passing fresh props to the components that renders the actual HTML, so you don't have to replace the props by hand.
Your container could be something like this
import React from 'react'
import { connect } from 'react-redux'
import PresentationalComponent from 'components/PresentationalComponent'
const Container = props => <PresentationalComponent {...props} />
export default connect( state => ({
searchText: state.UI.searchText,
locations: [...],
}), dispatch => ({
handleUpdateInput: e => dispatch( {
type: "CHANGE_SEARCH_TEXT",
text: e.target.value,
} ),
}))(Container)
And your presentational component
import React from 'react'
const PresentationalComponent = ({ searchText, locations, handleUpdateInput }) =>
<div>
{locations.map(location => <p>{location}</p>)}
<input defaultValue={searchText} type="text" onChange={handleUpdateInput} />
</div>
export default PresentationalComponent

Unable to access this.props data inside constructor, react+redux

I'm using redux, redux-form and react-select inside the Form component as shown below. I am having problem with using props as a multi-select value of the form.
Multi-select value displays and works correctly when props are loaded, or when the page is refreshed. However it doesn't seem to work correctly during normal use cases.
Smart container calls asyncconnect to dispatch book-runner data, and I'm using connect in this component to access this.props.bookRunners.
class Form extends Component {
constructor(props) {
super(props);
const bookRunnerArray = this.getBookRunnerArray(this.props.bookRunners.bookRunners);
this.state = {
options: bookRunnerArray,
value: [],
};
}
Connecting to store:
const mapStateToProps = (state) => {
return {
bookRunners: state.bookrunners,
};
};
const mapDispatchToProps = (dispatch) => {
return { actions: bindActionCreators(dealActions, dispatch) }
};
export default connect(mapStateToProps, mapDispatchToProps)(Form);
I think this.props.bookRunners is empty when I try to set initial state inside the constructor. I tried using componenetWillMount() but no luck. Please help!
Looks like you just have a typo on your mapStateToProps function. Change state.bookrunners to state.bookRunners on the return object.
Adding the code below solved the problem. super(props) and prop accessed in constructor were all still "fetching". Therefore did not have the data needed. componenetWillMount also was the same case. However, I was able to access the data from componentWillReceiveProps(). Please let me know if anyone has any thoughts.
componentWillReceiveProps() {
if(!this.props.bookRunners.isFetching && this.state.options.isEmpty){
const bookRunnerArray = this.getBookRunnerArray(this.props.bookRunners.bookRunners);
this.setState ({
options: bookRunnerArray,
})
}
}
--- day 2 was working on another container with the same problem. And decided that componentDidUpdate() + initializing inside constructor behaved more consistently.
So below + constructor shown above.
componentDidUpdate() {
if(!this.props.bookRunners.isFetching && this.state.options.isEmpty){
const bookRunnerArray = this.getBookRunnerArray(this.props.bookRunners.bookRunners);
this.setState ({
options: bookRunnerArray,
})
}
}

What is mapDispatchToProps?

I was reading the documentation for the Redux library and it has this example:
In addition to reading the state, container components can dispatch actions. In a similar fashion, you can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component.
This actually makes no sense. Why do you need mapDispatchToProps when you already have mapStateToProps?
They also provide this handy code sample:
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
What is this function and why it is useful?
I feel like none of the answers have crystallized why mapDispatchToProps is useful.
This can really only be answered in the context of the container-component pattern, which I found best understood by first reading:Container Components then Usage with React.
In a nutshell, your components are supposed to be concerned only with displaying stuff. The only place they are supposed to get information from is their props.
Separated from "displaying stuff" (components) is:
how you get the stuff to display,
and how you handle events.
That is what containers are for.
Therefore, a "well designed" component in the pattern look like this:
class FancyAlerter extends Component {
sendAlert = () => {
this.props.sendTheAlert()
}
render() {
<div>
<h1>Today's Fancy Alert is {this.props.fancyInfo}</h1>
<Button onClick={sendAlert}/>
</div>
}
}
See how this component gets the info it displays from props (which came from the redux store via mapStateToProps) and it also gets its action function from its props: sendTheAlert().
That's where mapDispatchToProps comes in: in the corresponding container
// FancyButtonContainer.js
function mapDispatchToProps(dispatch) {
return({
sendTheAlert: () => {dispatch(ALERT_ACTION)}
})
}
function mapStateToProps(state) {
return({fancyInfo: "Fancy this:" + state.currentFunnyString})
}
export const FancyButtonContainer = connect(
mapStateToProps, mapDispatchToProps)(
FancyAlerter
)
I wonder if you can see, now that it's the container 1 that knows about redux and dispatch and store and state and ... stuff.
The component in the pattern, FancyAlerter, which does the rendering doesn't need to know about any of that stuff: it gets its method to call at onClick of the button, via its props.
And ... mapDispatchToProps was the useful means that redux provides to let the container easily pass that function into the wrapped component on its props.
All this looks very like the todo example in docs, and another answer here, but I have tried to cast it in the light of the pattern to emphasize why.
(Note: you can't use mapStateToProps for the same purpose as mapDispatchToProps for the basic reason that you don't have access to dispatch inside mapStateToProp. So you couldn't use mapStateToProps to give the wrapped component a method that uses dispatch.
I don't know why they chose to break it into two mapping functions - it might have been tidier to have mapToProps(state, dispatch, props) IE one function to do both!
1 Note that I deliberately explicitly named the container FancyButtonContainer, to highlight that it is a "thing" - the identity (and hence existence!) of the container as "a thing" is sometimes lost in the shorthand
export default connect(...)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
syntax that is shown in most examples
It's basically a shorthand. So instead of having to write:
this.props.dispatch(toggleTodo(id));
You would use mapDispatchToProps as shown in your example code, and then elsewhere write:
this.props.onTodoClick(id);
or more likely in this case, you'd have that as the event handler:
<MyComponent onClick={this.props.onTodoClick} />
There's a helpful video by Dan Abramov on this here:
Redux: Generating Containers with connect() from React Redux (VisibleTodoList)
mapStateToProps() is a utility which helps your component get updated state(which is updated by some other components),
mapDispatchToProps() is a utility which will help your component to fire an action event (dispatching action which may cause change of application state)
mapStateToProps, mapDispatchToProps and connect from react-redux library provides a convenient way to access your state and dispatch function of your store. So basically connect is a higher order component, you can also think as a wrapper if this make sense for you. So every time your state is changed mapStateToProps will be called with your new state and subsequently as you props update component will run render function to render your component in browser. mapDispatchToProps also stores key-values on the props of your component, usually they take a form of a function. In such way you can trigger state change from your component onClick, onChange events.
From docs:
const TodoListComponent = ({ todos, onTodoClick }) => (
<ul>
{todos.map(todo =>
<Todo
key={todo.id}
{...todo}
onClick={() => onTodoClick(todo.id)}
/>
)}
</ul>
)
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
const TodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
Also make sure that you are familiar with React stateless functions and Higher-Order Components
Now suppose there is an action for redux as:
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
When you do import it,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.onTodoClick(); // This prop acts as key to callback prop for mapDispatchToProps
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
const mapDispatchToProps = dispatch => {
return {
onTodoClick: () => { // handles onTodoClick prop's call here
dispatch(addTodo())
}
}
}
export default connect(
null,
mapDispatchToProps
)(Greeting);
As function name says mapDispatchToProps(), map dispatch action to props(our component's props)
So prop onTodoClick is a key to mapDispatchToProps function which delegates furthere to dispatch action addTodo.
Also if you want to trim the code and bypass manual implementation, then you can do this,
import {addTodo} from './actions';
class Greeting extends React.Component {
handleOnClick = () => {
this.props.addTodo();
}
render() {
return <button onClick={this.handleOnClick}>Hello Redux</button>;
}
}
export default connect(
null,
{addTodo}
)(Greeting);
Which exactly means
const mapDispatchToProps = dispatch => {
return {
addTodo: () => {
dispatch(addTodo())
}
}
}
mapStateToProps receives the state and props and allows you to extract props from the state to pass to the component.
mapDispatchToProps receives dispatch and props and is meant for you to bind action creators to dispatch so when you execute the resulting function the action gets dispatched.
I find this only saves you from having to do dispatch(actionCreator()) within your component thus making it a bit easier to read.
React redux: connect: Arguments

Rerendering React components after redux state's locale has updated

I've implemented Redux in my React application, and so far this is working great, but I have a little question.
I have an option in my navbar to change the locale, stored in redux's state. When I change it, I expect every component to rerender to change traductions. To do this, I have to specify
locale: state.locale
in the mapStateToProps function... Which leads to a lot of code duplication.
Is there a way to implicitly pass locale into the props of every component connected with react-redux ?
Thanks in advance!
Redux implements a shouldComponentUpdate that prevents a component from updating unless it's props are changed.
In your case you could ignore this check by passing pure=false to connect:
connect(select, undefined, undefined, { pure: false })(NavBar);
For performance reasons this is a good thing and probably isn't what you want.
Instead I would suggest writing a custom connect function that will ensure locale is always added to your component props:
const localeConnect = (select, ...connectArgs) => {
return connect((state, ownProps) => {
return {
...select(state, ownProps),
locale: state.locale
};
}, ...connectArgs);
};
// Simply use `localeConnect` where you would normally use `connect`
const select = (state) => ({ someState: state.someState });
localeConnect(select)(NavBar); // props = { someState, locale }
To cut down the duplication of code I usually just pass an arrow function to the connect method when mapping state to props, looks cleaner to me. Unfortunately though, I don't think there is another way to make it implicit as your component could subscribe to multiple store "objects".
export default connect((state) => ({
local: state.locale
}))(component);
To solve this problem, you can set the Context of your parent component, and use it in your child components. This is what Redux uses to supply the store's state and dispatch function to connected React components.
In your Parent component, implement getChildContext and specify each variable's PropType.
class Parent extends React.Component {
getChildContext() {
return {
test: 'foo'
};
}
render() {
return (
<div>
<Child />
<Child />
</div>
);
}
}
Parent.childContextTypes = {
test: React.PropTypes.string
};
In your Child component, use this.context.test and specify its PropType.
class Child extends React.Component {
render() {
return (
<div>
<span>Child - Context: {this.context.test}</span>
</div>
);
}
}
Child.contextTypes = {
test: React.PropTypes.string
};
Here's a demo of it working.
I might as well mention that, while libraries like Redux use this, React's documentation states that this is an advanced and experimental feature, and may be changed/removed in future releases. I personally would not recommend this approach instead of simply passing the information you need in mapStateToProps, like you originally mentioned.

Resources