Redux Connect: mapDispatchToProps pass in the result of an action creator - redux

I'm learning redux and and wanted to know how dispatch passes in result of an action creator as stated in the screenshot below taken from redux doc.
The learning app code is available here: https://github.com/ZhangMYihua/lesson-12/tree/master/src
As per the course instructor the below mapDispatchToProps code updates the header component state using user.action.js. I'm not able to understand how all this works even after reading the redux documentation.https://react-redux.js.org/using-react-redux/connect-mapdispatch
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
The initial state of the currentUser in the header component is set using the user.reducer.js file in the redux folder. This part is clear.
When we sign in using Google SignIn, the createUserProfileDocument(userAuth) function in App.js will check if the google login is available in the firestore users collections. If not it will create a copy of the account with the required fields. The user login details are fetched from the firestore users collections which are passed in the userRef in App.js.
What i do not understand is how data from userRef in componentDidMount() gets passed to header component using dispatch when we login using google signin?
componentDidMount() {
const {setCurrentUser} = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
Below is the example from the react-redux documentation which is similar to the above.

This part in your componentDidMount is passing userRef data to the redux store:
const {setCurrentUser} = this.props;
// ...
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
mapDispatchToProps
The code calls this.props.setCurrentUser and that is function which you define in your mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
So this.props.setCurrentUser function definition is simple like this (where dispatch is defined in upper scope):
this.props.setCurrentUser = user => dispatch(setCurrentUser(user))
The function takes a single parameter user and calls dispatch(setCurrentUser(user)). Here the setCurrentUser(user) is your action creator that produce some action (something like this {action: 'SET_CURRENT_USER', payload: {user}}). This action is dispatched to redux store with dispatch function (the react-redux passes correct dispatch function to your mapDispatchToProps through react-redux connect, see bellow).
connect
The mapDispatchToProps is useless if you are not use it as an argument of the react-redux connect function. You usually wrap your component with connect and export the wrapped component not just the component itself. So whenever you import your App.js module and use the default export from it as component in some JSX tree, it is not just your component, but it is your component wrapped with react-redux magic. This magic ensures, that the mapDispatchToProps is called with correct dispatch argument (store.dispatch from redux library) and enhances your component props object with another properties, the ones form mapStateToProps and the ones from mapDispatchToProps.
// here you are wrapping your App component with `connect` from `react-redux`
export default connect(
null,
mapDispatchToProps
)(App);
In next file you will import and use the wrapped component like this:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './redux/store';
import App from './App'; // here you are importing the wrapped component
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
It is necessary to have your components which uses react-redux have wrapped with Provider in the app component tree. Otherwise connect will not have access to redux store so connecting component to redux store won't work.
EDIT
mapStateToProps
this.props.setCurrentUser is used to dispatching action to your redux store which just MODIFIES it (how the store is modified is up to your implementation of reducer - see user.reducer.js in your example).
If you want to access some part of your redux store, you need to pass the mapStateToProps by the first argument (see above that in example of your App component the first argument of connect was null).
You can imagine the redux store as a single javascript object. The first definition of this object you can see in your root-reducer.js:
export default combineReducers({
user: userReducer
});
This defines that your redux store (object) has the property user on the top level. The value of property user handles user.reducer.js. That reducer defines initial value of the user part of the store and also defines how it can be modified (by redux actions). So according to your example your initial store is this:
{
user: {
currentUser: null
}
}
If you want access it in your component (props) you need to use mapStateToProps as I stated above, like this (see the file header.component.jsx in your example):
// header.component.jsx
const mapStateToProps = state => ({ // react-redux passes here the whole redux store to the argument state
/*
you want access currentUser of user reducer,
so you just access it the same way as you would do it
in javascript object, with use of dots.
*/
currentUser: state.user.currentUser //
});
export default connect(mapStateToProps)(Header);
And then you access the connected store value in your component by props:
// whenever in your component code
console.log(this.props.currentUser);

Related

Using querySelector() in vue Compostion API

I'm new to Vue composition API and need to use the Document method querySelector. However, it is not working as expected. If i write
<nav class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = document.querySelector(".home-menu");
observer.observe(target);
console.log(target);
target is null. Reading docs I see the ref attribuute and if I
<nav ref="navbar" class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const target = ref("navbar");
console.log(target);
I do console an object. Is ref the way that you get a DOM element in composition API? Can I now use target in my observer object? Is it equivalent to querySelector? I tried this
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = ref("navbar");
observer.observe(target);
but got error
Uncaught TypeError: IntersectionObserver.observe: Argument 1 does not implement interface Element.
Thanks.
The reason document.querySelector is returning null is that the DOM does not contain that element yet.
The script setup runs when the component is created, so the component's DOM has not been created yet.
You can use the onMounted lifecycle hook to run code when the component has been mounted. I created a playground to demonstrate this.
From the Lifecycle docs:
For example, the onMounted hook can be used to run code after the component has finished the initial rendering and created the DOM nodes
There are then two approaches to achieve what you want. You can use continue to use querySelector, or you can use template refs.
Personally, I use template refs for static elements (such as navbars, etc.) and querySelector for dynamic selections.
Using Template Refs
A template ref is a regular Vue ref, but by connecting it to an element or child component via the ref attribute you can obtain a direct reference to that element or component.
If you connected it to an element, the value will be that element; if you connected it to a child component, the value will be that component's component instance; if nothing is connected to it, the value will be null.
Steps
Create a ref:
const navbar = ref(null);
This will be used to refer to the element.
Connect the template ref to the element by setting the ref attribute on the element to the name you used for the template ref:
<nav ref="navbar" ...>
Paraphrasing the Template Refs docs:
ref is a special attribute. It allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
Connect the observer when the component is mounted:
onMounted(() => {
observer.observe(navbar.value);
})
Paraphrasing the docs again:
Note that you can only access the ref after the component is mounted. If you try to access navbar before then, it will be null. This is because the element doesn't exist until after the first render!
Optionally (see below), disconnect the observer when the component is being unmounted:
onBeforeUnmount(() => {
observer.disconnect();
})
Note that I don't believe this is technically necessary, as the observer should be garbage collected when the component is destroyed.
You can fiddle around with this experiment I did in the SFC playground, trying to create a memory leak.
Code Example
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const el = ref(null);
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
observer.observe(el.value)
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div ref="el">
I'm the target!
</div>
</template>
Using querySelector
Alternatively, you can still use querySelector. The same lifecycle considerations apply.
<nav class="home-menu ...">
onMounted(() => {
const target = document.querySelector(".home-menu");
observer.observe(target);
})
Code Example
<script setup>
import { onMounted, onUnmounted } from "vue";
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
const target = document.querySelector(".target");
observer.observe(target);
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div class="target">
I'm the target!
</div>
</template>
Other Docs
This is the diagram from the Lifecycle docs:
Side Notes
The reason console.log(target) did log an object is because a Vue ref is an object. The actual value is accessed via the value property.
The technical reason for this is that Vue can then detect when that property is accessed, and do its reactivity magic; including when it was a complete reassignment of the value.
You can use the ref approach, calling the variable with the same name you declared the ref in the template:
const navbar = ref(null);
However, you should await the component to be mounted to observe:
onMounted(() => {
observer.observe(target);
})
remember also to disconnect it when you unmount the component:
onBeforeUnmount(() => {
observer.disconnect();
})

Can we use redux states in a react component without using {connect}?

I usually import {connect} from react-redux to connect my React component to a Redux store.
Is there any other way to access the redux store from react components without using {connect}?
There is useSelector from React Redux Hooks
The selector is approximately equivalent to the mapStateToProps argument to connect conceptually. The selector will be called with the entire Redux store state as its only argument. The selector will be run whenever the function component renders (unless its reference hasn't changed since a previous render of the component so that a cached result can be returned by the hook without re-running the selector). useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched.
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}

Is it a bad idea to dispatch actions from mapDispatchToProps, and why?

We are using react-router-redux for routing in our app.
<Route path='/users/:userId' component={SomeComponent} />
In this way, SomeComponent will receive a userId in its match params. The problem is that in order for the children of SomeComponent to receive the id we must pass it as props from parent to child. As much as possible we try to avoid passing props from parent to child, preferring to use containers and the redux store.
In order to provide the params to children, we are considering having SomeComponent dispatch an action when its props change. A convenient way to do this is:
const mapDispatchToProps = (dispatch: Dispatch<IState>, props: IPropsFromParent): IPropsFromDispatch => {
dispatch(ActionCreators.updateShowingUserId({
userId: props.match.params.userId,
}))
return {
anotherAction: () => {
dispatch(ActionCreators.doOtherStuff())
}
}
}
This way, whenever the component receives a new userId from the router, it will update the value in the store. Components which render deeper in the tree but which are not themselves connected to the router can use this value for conditional rendering, etc...
However, we are concerned that this may be bad practice for reasons that we are not yet aware of. It is known that calling setState in a react component's render function is forbidden. I wonder if there is some similar concrete restriction on calling dispatch in mapDispatchToProps.
Is it a bad idea to dispatch actions in mapDispatchToProps, and if so, why?
Since the dispatch will be run every time the redux store was updated it (and the props of the component change) wouldn't be a good idea to dispatch something inside the mapStateToProps, mapDispatchToProps or mergeProps functions, since you would overwrite the previous value with a default one. You should add the value you're dispatching to the initial store when possible or dispatch the defaults from the parent which controls the component.
Documentation for ReactRedux.connect

How do you snapshot test components with actions from Redux connect?

I want to snapshot test a react component that dispatches a redux action in its componentDidMount() handler. The action is given to the component through Redux's connect().
How should I mock the action?
Right now the action is imported in the component (see Newsfeed.js below). So how would I swap it out for a mock action in my test?
I'm using redux-thunk for my async actions, but that shouldn't matter much.
app/components/Newsfeed.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
// An action that makes an async request to get data
import { loadNewsfeedStories } from '../actions/Newsfeed';
class Newsfeed extends Component {
componentDidMount(){
this.props.loadNewsfeedStories();
}
...
}
export default connect(state => ({
stories: state.newsfeed.stories
}),
{
loadNewsfeedStories
})(Newsfeed)
app/tests/Newsfeed.test.js
import React from 'react';
import { Provider } from 'react-redux';
// This creates a mockStore using my reducers and a saved JSON state.
import { mockStore } from './MockState';
// The component to test
import Newsfeed from '../components/Newsfeed';
test('Newsfeed matches snapshot', () => {
const wrapper = mount(
<Provider store={mockStore}>
<Newsfeed />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
Possible Solutions
Export the unconnected component, and manually pass in all props & mock actions. Will be a lot of extra coding compared to just using the mockStore & provider. Also we won't be testing the 'connected' component.
Use something like Nock? Seems to intercept HTTP calls, so the ajax requests wouldn't actually go anywhere.
For axios, there is a lib called moxios - https://github.com/mzabriskie/moxios
You could try exporting a different connected component, but with a mocked "mapDispatchToProps" function. This way, no action will get through.
But personally, when testing components. I write a mock reducer, these should not change the state but record all dispatched actions (Jests mock functions are very useful for this). This way, you can also test if the correct action is dispatched when clicking a button,...
It's a bad idea to test the component, redux store, and mock http requests at the same time because unit tests should be small.
Another option would be to avoid executing business logic in componentDidMount.
Instead of writing
componentDidMount(){
this.props.loadNewsfeedStories();
}
You can move this logic to the redux module (depends on libs you use).
If you use only redux-thunk without libraries like saga or redux-logic, you can use this https://github.com/Rezonans/redux-async-connect.
but record all dispatched actions
Redux thunks are hard to test. Your mock dispatcher will receive only an array of functions. And you can't verify if a function was created by loadNewsfeedStories() or by someAnotherThunk().

Make api calls from dumb components with Redux

I want to implement dropdown with react and redux. Dropdown will be a part of other component, so, it it really "Dumb" component. But dropdown should call to api to fetch items, apply custom filters and etc. Api calls should be authenticated, tokens stored it global state. Should I pass tokens to component props? Or there is a better way to do this?
A dumb component, by definition, should be dumb: it means that it should take everything it needs "from the top", i.e. via the props.
Only a "Smart" ("connected" to Redux) Component, up the hierarchy, would deal with fetching the data using a new (async) Action, which would then modify the state when the API call returns, which would re-render your Dumb Component with its new props.
So in Redux:
Your Dumb component takes two props: one with the values coming from your API (which actually are part of your "state"), the other one a function that is called when your dropdown selected item changes. <MyDumbComponent data={this.props.data} onChange={this.onChange.bind(this)} />
Then a "Smart" component up the hierarchy will listen to that onChange function, and dispatch an Action (FETCH_DATA)
The (async) Action will call the API, and when receiving the data, call another Action (FETCH_DATA_SUCCESS) with the data
Then Redux, with a reducer, would update its state from the Action payload
Which will re-render your Component with its new props (new data), coming from the current state.
You might want to read this: http://redux.js.org/docs/basics/UsageWithReact.html
And regarding async actions: http://redux.js.org/docs/advanced/AsyncActions.html
Dumb component doesn't mean it can do anything like fetch updates, it means it's 'dumb' to the concept of redux, and knows nothing about your store or what library you're using. The benefit is that you can change your flux implementation and you only have the small bit of work to update the smart components.
For the type of scenario you're describing, you would give your <Menu> a function via props that would run when <Menu> wants to update the data. <Menu> knows nothing about Redux - it's just executing a function - but it's still able to fetch new data. Depending on the complexities, you could pass through the raw action creator (bound to dispatchAction) and have it run that.
import * as dataActions from './actions';
// We have dataActions.fetchItems() which is the action creater to get new items.
// Attach items, and the action creator (bound to dispatch) to props
#connect((store) => {
return {items: store.data.items}
}, dataActions)
class ItemsPage extends Component {
static propTypes = {
items: PropTypes.object, // the items from the store, from #connect
fetchItems: PropTypes.func // action dispatcher, from #connect
}
render() {
return (<Menu onChange={this.props.fetchItems} /> )
}
}
// Our 'dumb' component that doesnt know anything about Redux,
// but is still able to update data (via its smart parent)
class Menu extends Component {
static propTypes = {
onChange: PropTypes.func // same as fetchItems
}
render() {
return (<button onClick={this.props.onChange}>Update items</button>);
}
}

Resources