I am looking to using the redux reselect lib to write my react-redux selectors. From the docs there is this section that describes how to write selectors that can be used by several component instances.
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { makeGetVisibleTodos } from '../selectors'
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos() // here
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
makeMapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
I am wondering if the differences in how the makeGetVisibleTodos selector is called(as shown below), has implications on how memoization works. will memoization still work?, if not, why?
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { makeGetVisibleTodos } from '../selectors'
const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => {
return {
todos: makeGetVisibleTodos()(state, props) // and here
}
}
return mapStateToProps
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
const VisibleTodoList = connect(
makeMapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
That example will never memoize at all, because it's creating a new selector instance every time mapState runs.
The key to correctly using memoized selectors is:
Reuse the same selector instance multiple times
And keep passing it the same arguments
If you create a new selector instance, it has no cached values. If you have the same instance but pass it different values each time you call it, then the memoization never works.
For more details, see my post Using Reselect Selectors for Encapsulation and Performance.
Related
Not able to access the redux store current state in a Class component.
It shows up console error
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
When I tried to implement the same using a function component with useSelector and useDispatch, everything works as expected. What has gone wrong over here?
reducer.js
let initialState={
count:0
}
const reducer=(state=initialState,action)=>{
switch(action.type){
case ADD_INCREMENT:
return {
...state,
count:state.count+1
};
default: return state;
}
}
export default reducer;
action.js
const Increment=()=>{
return {
type:ADD_INCREMENT
}
}
store.js
import reducer from './reducer';
const store=createStore(reducer);
export default store;
Class Component
import { connect } from 'react-redux';
const mapStateToProps=state=>{
return {
count:state.count
}
}
const mapDispatchToProps=(dispatch)=>{
return {
count:()=>dispatch(action.Increment())
}
}
class Orders extends Component {
render() {
return (
<div>
<h1>Count: {this.props.count} </h1>
</div>
);
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Orders);
In App.js the entire container is wrapped with Provider and store is passed as props
Issue
You've named your state and your action both count, the latter is the one injected as a prop.
const mapStateToProps = state => {
return {
count: state.count // <-- name conflict
}
}
const mapDispatchToProps = (dispatch) => {
return {
count: () => dispatch(action.Increment()) // <-- name conflict
}
}
Solution
Provide different names, count for the state, maybe increment for the action.
const mapStateToProps = state => ({
count: state.count,
});
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch(action.Increment())
})
I am new to redux, I am trying to dispatch action on click event of button in react component.
But i cant update state i have in reducer.
types.js
export const FETCH_USERS_SUCCESS='FETCH_USERS_SUCCESS';
action.js
import {FETCH_USERS_SUCCESS} from './types';
export const fetchUsersSuccess = () =>
{
return (
{
type:FETCH_USERS_SUCCESS,
payload:'natalie',
}
);
}
reducer.js
import {FETCH_USERS_SUCCESS} from './types';
const initialState={
users:'mike',
error:null,
}
const reducer =(state=initialState,action)=> {
switch(action.type){
case FETCH_USERS_SUCCESS:
return {
...state,
users:action.payload,
}
default: return state
}
}
export default reducer;
and this is my app.js
import React from 'react';
import './App.css';
import { connect } from 'react-redux'
import { fetchUsersSuccess } from './action';
class App extends React.Component {
render(){
return (
<div>
<h1>Hello {this.props.users}</h1>
<button type="button" value="submit" onClick={this.props.handleSubmit}>submit</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
users:state.users
}
}
const mapDispatchToProps=dispatch=>{
return {
handleSubmit: () => {dispatch(fetchUsersSuccess)}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I am able to display initial user once but clicking on button does not change state.
Can anyone suggest why i cant update user on click event in react-redux?
Thanks.
https://react-redux.js.org/using-react-redux/connect-mapdispatch#two-forms-of-mapdispatchtoprops
mapDispathToProps has two forms: object and function. Redux documentation recommend to use object. In your case.
const mapDispatchToProps = {
handleSubmit: fetchUsersSuccess,
};
Another approach is to return a dispatch function which will then return the action which is done by using redux-thunk middleware. It is normally used for async operations.
So, your fetchUsersSuccess should be
export const fetchUsersSuccess = (dispatch) =>
{
return function() {
dispatch(
{
type:FETCH_USERS_SUCCESS,
payload:'natalie',
}
);
}
}
ES6
export const fetchUsersSuccess = (dispatch) =>
() => dispatch(
{
type:FETCH_USERS_SUCCESS,
payload:'natalie',
}
);
And then at the component level, do a currying like this.
const mapDispatchToProps=dispatch=>{
return {
handleSubmit: fetchUsersSuccess(dispatch)
}
}
Now you can call the handleSubmit or bind them already.
I'm trying to setup up my react project with redux and I'm following a basic example with a counter, which I can increment and decrement. The counter displays correctly on the page as 0 initially - however when I hit the button, the increment action doesn't seem to be dispatched, and as a consequence, the counter does not update.
My LoginPage.js:
/* eslint-disable no-unused-expressions */
import { connect } from "react-redux";
import React, { Component } from "react";
import { selectCounter } from "./../../selectors/counter";
import { actions as counterActions } from "./../../actions/counter";
class LoginPage extends Component {
componentDidMount() {}
render() {
const { counter, increment } = this.props;
return (
<div>
<p>{`Hi ${counter}`}</p>
<button onClick={() => increment()}>+</button>
</div>
);
}
}
LoginPage = connect(
(state, props) => ({
counter: selectCounter(state, props)
}),
{ ...counterActions }
)(LoginPage);
export default LoginPage;
My actions/counter.js:
import { INCREMENT } from "./../types/counter";
const increment = () => {
return { type: INCREMENT };
};
export const actions = {
increment
};
My /reducers/counter.js:
const { INCREMENT, DECREMENT } = "./../types/counter";
const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
module.exports = { counterReducer };
My /reducers/index.js:
import { combineReducers } from "redux";
import { counterReducer } from "./counter";
const rootReducer = combineReducers({
counter: counterReducer
});
export default rootReducer;
I'm omitting the App.js and index.js files as these are pretty simple and don't seem to be related to the problem.
UPDATE:
My actions/counter.js:
import { INCREMENT } from "./../types/counter";
import { useDispatch } from "react-redux";
const increment = () => {
return { type: INCREMENT };
};
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch(increment())
};
};
export const actions = {
...mapDispatchToProps(useDispatch)
};
Now I am seeing the bug:
react-dom.development.js:14724 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Updated
Need to properly define mapDispatchToProps function and pass it to connect(). In your code increment() doesn't seem to dispatch an action.
const mapDispatchToProps = (dispatch) =>{
increment: ()=>dispatch(actions.increment())
}
LoginPage = connect(
(state, props) => ({
counter: selectCounter(state, props)
}),
mapDispatchToProps
)(LoginPage);
Update
The error is due to useDispatch() usage outside component. It has to be declared and used within a functional component.
I have a functional component that fetches data from an api using redux.
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
The component:
export function Trips(props) {
const trips = useSelector(state => state.trips);
useFetching(fetchTrips)
...
...
}
The thunk:
export const fetchTrips = () => (dispatch) =>
axios.get("/api/v1/trips")
.then(response => dispatch(addTrips(response.data)))
export const addTrips = trips => ({
type: ADD_TRIPS,
payload: trips
})
The reducer:
function tripsReducer(state = INITIAL_STATE, action) {
console.log(action)
if (action.type === ADD_TRIPS) {
return Object.assign({}, state, {
trips: state.trips.concat(action.payload)
});
}
return state
}
My reducer is called. How can I update the UI after the fetched data have been dispatched? My render is not called again.
1st option: Using hooks
You are actually using React and react-redux hooks. Make sure you use the object trips later in your component. Here is a sample using your code:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchTrips } from '../tripsActions';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, []);
}
export function Trips(props) {
const trips = useSelector(state => state.trips);
useFetching(fetchTrips);
return (
<div>
<p>Total trips: {trips.length}</p>
</div>
);
}
2nd option: Using connect
This was the way to connect to the Redux store state before they introduced the hooks.
As you are using react-redux this can be easily done by using the connect() function. You should also provide a mapStateToProps() function to select the part of the data from the store that your component needs and a mapDispatchToProps() function to have access to the actions to be dispatched.
This is how your Trips component would look like with this approach:
import React from 'react';
import { connect } from 'react-redux';
import { fetchTrips } from '../tripsActions';
const mapStateToProps = (state) => {
return {
// will be available as props.trips
trips: state.trips
}
}
const mapDispatchToProps = (dispatch) => {
return {
// will be available as props.fetch()
fetch: () => dispatch(fetchTrips)
}
}
const function Trips(props) {
this.props.fetch();
// some other code. Example:
return (
<div>
<p>Total trips: {this.props.trips.length}</p>
</div>
);
}
export default connect(mapStateToProps)(Trips);
mapStateToProps() receives the Redux store state and returns an object whose fields will be available as props in your component. As you already use props.trips I simply mapped that field to the updated value of the Redux state's trips field.
The call to connect() with your component gives you a connected component. And that latter should be exported rather than the original component. It will not create another component so you will continue to use the Trips component normally.
Now your component will be re-rendered as its props are being updated.
You can have a look at the react-redux documentation to better understand the use of connect() and mapStateToProps() and mapDispatchToProps() functions.
You can do it easily like that:
import React, {
useCallback, useEffect
} from 'react';
import {
useSelector, useDispatch
} from 'react-redux';
// ../ducks for example
import { fetchTrips } from '../ducks';
const function Trips(props) {
const dispatch = useDispatch();
useEffect(() => {
fetchTripsHandler();
}, []);
const fetchTripsHandler = useCallback(
() => dispatch(fetchTrips()),
[]
);
const { trips } = useSelector(state => ({
trips: state.trips
}));
// your other component code
}
With useSelector from react-redux you can not use connect, mapStateToProps and mapDispatchToProps.
After that, your component will be connected to the Store.
Here is more information about react-redux hooks.
I used a structure using React Hooks. It is based on a global Context that contains a combination of reducers (as in the Redux).
Also, I widely use custom hooks to separate logic.
I have a hook that contains asynchronous API requests and it has become quite cumbersome and I have the opportunity to split almost every function of this hook into other hooks, but each of these functions uses a global context (more precisely - dispatch from useReducer()).
So, questions:
Is it ok to use useContext() in every hook who needs it?
How will it affect performance if, for example, I create 10 custom hooks that use useContext() internally and use them in the component.
Example:
providers/Store.js
import React, { createContext, useReducer } from 'react';
export const StoreContext = createContext();
export const StoreProvider = ({ children }) => {
/**
* Store that contains combined reducers.
*/
const store = useReducer(rootReducer, initialState);
return (
<StoreContext.Provider value={store}>{children}</StoreContext.Provider>
);
};
hooks/useStore.js
import { useContext } from 'react';
import { StoreContext } from '../providers';
export const useStore = () => useContext(StoreContext);
hooks/useFoo.js
import { useCallback } from 'react';
import { useStore } from './useStore';
export const useFoo = () => {
const [, dispatch] = useStore();
const doFoo = useCallback(
async params => {
dispatch(actions.request());
try {
const res = await SomeService.getSomething(params);
dispatch(actions.add(res));
dispatch(actions.success());
} catch (error) {
dispatch(actions.failure());
}
},
[dispatch]
);
return { doFoo };
};
hooks/useBar.js
import { useCallback } from 'react';
import { useStore } from './useStore';
export const useBar = () => {
const [, dispatch] = useStore();
const doBar = useCallback(
async params => {
dispatch(actions.request());
try {
const res = await SomeService.getSomething(params);
dispatch(actions.success());
dispatch(actions.add(res));
} catch (error) {
dispatch(actions.failure());
}
},
[dispatch]
);
return { doBar };
};
hooks/useNext.js
...
import { useStore } from './useStore';
export const useNext = () => {
const [, dispatch] = useStore();
...
};
components/SomeComponent.js
const SomeComponent = () => {
// use context
const [store, dispatch] = useStore();
// and here is the context
const { doFoo } = useFoo();
// and here
const { doBar } = useBar();
// and here
useNext();
return (
<>
<Button onClick={doFoo}>Foo</Button>
<Button onClick={doBar}>Bar</Button>
// the flag is also available in another component
{store.isLoading && <Spin />}
</>
)
}
Internally, hooks can reference a state queue owned by component. (Under the hood of React’s hooks system - Eytan Manor
)
useContext is just to reference a global state from the relative Context Provider. There is almost no overhead from useContext as you are concerned.