How do you use dispatch in React components with state? - redux

I'm using React + Redux and have a number of child components <IndividualValue/> that each load a value from an API when they mount. In addition they dispatch an action which accumulates the total of these values that is displayed in another component <TotalValues />. However you can't call dispatch inside componentDidMount.
I am able to achieve this using connect and mapDispatchToProps but what is the correct way to do this with Redux and Hooks?
Key parts of my code:
class IndividualValue extends React.Component {
constructor(props) {
super(props);
this.state = {value: 0}
const dispatch = useDispatch();
}
async componentDidMount() {
let value = axios.get(`api/{this.props.name}`).data.value;
this.setState({value: value})
dispatch(incrementTotalByValue(value)); // <--- ???
}
render() {
return (
<div>{this.props.name} = {this.state.value}</div>
)
}
}
function TotalValues() {
const total = useSelector((state) => state.counter.total)
return (
<div>
<div>
<span>{total}</span>
</div>
</div>
)
}
ReactDOM.render(
<Provider store={store}>
<IndividualValue name="nick" />
<IndividualValue name="oscar" />
<IndividualValue name="michael" />
<TotalValues />
</Provider>,
document.getElementById('root')
);

It seems that a way to achieve this is:
const [value, setValue] = useState(null);
const dispatch = useDispatch();
...
useEffect(() => {
(async () => {
let value = await axios.get(`api/{props.name}`).data.value;
setValue(value)
dispatch(incrementTotalByValue(value));
})();
}, [])
I don't know if that is the recommended way to do things with hooks. It appears useEffect can be used in a similar way to componentDidMount but there are differences and it's not a drop in replacement.

In Class Components, I usually do this:
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Actions as NameActions } from "../../store/ducks/actionsHere";
class Main extends Component {
render() {
return <h1>Text</h1>
}
}
const mapStateToProps = (state) => ({
data: state.data,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators(NameActions, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Main);

Related

wrapper.getInitialPageProps from next-redux-wrapper don't return the props to the component

I just updated my next-redux-wrapper package and did some changes according to their latest doc. However, the component didn't receive the props returned from MyComponent.getInitialProps = wrapper.getInitialPageProps(store => (context) => ({ foo: "bar }))
When I console the props on MyComponent, there is no "foo"
Has anyone solved this or know why this happens?
I tried to change my __app.js and use getInitialProps without wrapper.getInitialPageProps, but it changes nothing. Here is my __app.js
import { wrapper } from 'store';
import { Provider } from 'react-redux';
import App from 'next/app';
const MyApp = ({ Component, ...rest }) => {
const { store, props } = wrapper.useWrappedStore(rest);
return (
<Provider store={store}>
<Component {...props.pageProps} />
</Provider>
);
};
MyApp.getInitialProps = wrapper.getInitialAppProps(
(store) => async (appCtx) => {
const appProps = await App.getInitialProps(appCtx);
return {
pageProps: {
...appProps.pageProps,
},
};
}
);
export default MyApp;

Cannot get latest value of useState in Redux subscribe

When the state in Redux is updated, if it is different from the useState of the current page, the useState in the page will be updated, but the value in the red box will not change after the update, it is always the default true.
import { useEffect, useState } from "react";
import store from "./redux";
import { useDispatch } from "react-redux";
import { setState } from "./redux/modules/menu";
function App() {
const dispatch = useDispatch();
const [baseState, setBaseState] = useState(true);
useEffect(() => {
console.log("baseState :>> ", baseState);
}, [baseState]);
useEffect(() => {
const unSubscribe = store.subscribe(() => {
console.log(store.getState().menu.state, baseState);
if (store.getState().menu.state !== baseState) {
setBaseState(store.getState().menu.state);
}
});
return () => unSubscribe();
}, []);
const buttonEvent = () => {
const storeState = store.getState().menu.state;
dispatch(setState(!storeState));
};
return (
<div className="App">
<h1>value: {baseState + ""}</h1>
<button onClick={buttonEvent}>change</button>
</div>
);
}
export default App;
run result :
So if i got your question, what you are doing is, on a button click, you are changing the redux state and on that basis, you want to change the local state.
But in your useEffect() (2nd one);
useEffect(() => {
const unSubscribe = store.subscribe(() => {
console.log(store.getState().menu.state, baseState);
if (store.getState().menu.state !== baseState) {
setBaseState(store.getState().menu.state);
}
});
return () => unSubscribe();
}, []);
You are giving an empty array as 2nd argument, which means this useEffect is going to get triggered only on the first render(similar to componentDidMount in class components), thus will never know if the redux state has changed.
Thus, to make it working, just remove them (like we have componentDidUpdate in class components).
I found the most appropriate solution, using Redux useSelector

How to use mapDispatchToProps

I am learning Redux React. How to use mapDispatchToProps ? My code is like below
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getAddress } from '../store/actions/addressActions';
class Dashboard extends Component {
componentDidMount = () => {
this.props.getAddress();
};
render() {
return <div>Hello { console.log(this.props) } </div>;
}
}
const mapStateToProps = state => ({
address: state.address
});
const mapDispatchToProps = dispatch => ({
getAddress
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
I am getting console output like below
Where I am doing mistake ? How to use mapDispatchToProps properly ?
Either you define mapDispatchToProps as an object or you return the dispatched function from mapDispatchToProps instead of an object
Using first approach your mapStateToProps will look like
const mapDispatchToProps = {
getAddress
};
Using second approach it would look like
const mapDispatchToProps = dispatch => ({
getAddress: (...args) => dispatch(getAddress(...args));
});
Also since you are using combineReducers you need to change how you access the state in mapStateToProps
const mapStateToProps = state => ({ address: state.addressReducer.address });
I think, initially in store the address is undefined. correct? and the getAddress action will set the address value.
Here is what you missed,
1) you have dispatch the action. you can use any of following
const mapDispatchToProps = dispatch => ({
getAddress: (...args) => dispatch(getAddress(...args));
});
or
import { bindActionCreators } from "redux";
const mapDispatchToProps = dispatch => bindActionCreators({getAddress}, dispatch)
Your should update mapDispatchToProps method a little bit. In addition, you should export it to writing test.
import { bindActionCreators } from 'redux';
export const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(
getAddress,
dispatch,
),
})

mapDispatchToProps function is undefined

I am trying to get redux working in my react-native app. Basically, I have a signIn action defined in my authActions.js file:
const signInAction = () => {
return {
type: 'signIn',
};
};
export { signInAction };
Then I have an authReducer defined as this in authReducer.js:
const initialState = {
isAuthenticated: false,
}
const authReducer = (state = initialState, action) => {
switch(action.type) {
case "signIn":
return Object.assign({}, state, {
isAuthenticated: true,
})
default: return state;
}
};
export default authReducer;
I combine that reducer in my rootReducer.js file
import { combineReducers } from 'redux';
import auth from 'app/src/redux/reducers/authReducer.js';
const rootReducer = combineReducers({
auth,
});
export default rootReducer;
and then created a store in reduxIndex.js:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from 'app/src/redux/reducers/rootReducer.js';
let store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
export default store;
I wrapped my app in a <Provider> component, and that seems to be working fine (I can read from the state and see the value of isAuthenticated. However, when I try to dispatch an action using mapDispatchToProps in one of my views the function is undefined:
// More imports
// ...
import { connect } from 'react-redux';
import { signInAction } from 'app/src/redux/actions/authActions.js';
const mapStateToProps = (state) => {
return {};
}
const mapDispatchToProps = (dispatch) => {
return {
onSignIn: () => { dispatch(signInAction) },
};
}
class SignIn extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: "",
}
}
onSignInPress() {
// ******* this is where the error occurrs ****
this.props.onSignIn();
}
render() {
const {navigation} = this.props;
return (
<View style={SignInStyles.container}>
<ScrollView>
<View>
<Button
large
title="SIGN IN"
backgroundColor={colors.primary}
onPress={this.onSignInPress}
/>
</View>
</ScrollView>
</View>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SignIn);
I cant really see where I am going wrong, but im sure its a simple mistake somewhere. The specific error I get is :
"undefined is not an object. Evaluating this.props.onSignIn"
The onSignInPress callback isn't bound to any particular object, so when it gets called this is undefined.
The easy way to fix it is to use arrow syntax to make it always be bound. In your class definition:
onSignInPress = () => {
this.props.onSignIn();
}
Google found me this Medium article from Miron Machnicki which explains the differences and possible alternative syntaxes in pretty good detail.

Next.js + Redux server side rendering: Has data, but doesn't render on server side

I'm trying to add redux integration to my Next.js app, but I can't get serverside rendering working the way it should. I based my implementation off the official nextjs redux example.
In the end, when the page comes back from the server, the data is present as JSON data in the output, but the actual rendering based on this data did not happen. The weird thing is that before I used redux, the content DID render the way it should.
Naturally, I'm also getting React's checksum warning, indicating that the markup on the server is different.
I have no idea how to make this work properly on the server side. Is there something that I'm missing?
Here's the HTML generated by Next.js:
<h1 data-reactid="3">Test page</h1>
</div></div></div><div id="__next-error"></div></div><div><script>
__NEXT_DATA__ = {"props":{"isServer":true,"store":{},
"initialState":{"authors":{"loading":false,"items":{"4nRpnr66B2CcQ4wsY04CIQ":… }
,"initialProps":{}},"pathname":"/test","query":{},"buildId":1504364251326,"buildStats":null,"assetPrefix":"","nextExport":false,"err":null,"chunks":[]}
module={}
__NEXT_LOADED_PAGES__ = []
__NEXT_LOADED_CHUNKS__ = []
__NEXT_REGISTER_PAGE = function (route, fn) {
__NEXT_LOADED_PAGES__.push({ route: route, fn: fn })
}
__NEXT_REGISTER_CHUNK = function (chunkName, fn) {
__NEXT_LOADED_CHUNKS__.push({ chunkName: chunkName, fn: fn })
}
</script><script async="" id="__NEXT_PAGE__/test" type="text/javascript" src="/_next/1504364251326/page/test"></script><script async="" id="__NEXT_PAGE__/_error" type="text/javascript" src="/_next/1504364251326/page/_error/index.js"></script><div></div><script type="text/javascript" src="/_next/1504364251326/manifest.js"></script><script type="text/javascript" src="/_next/1504364251326/commons.js"></script><script type="text/javascript" src="/_next/1504364251326/main.js"></script></div></body></html>
AS you can see, the initialState value is populated, it contains all the required data, but the DOM still shows empty!.
If I render the dom on the client side, the js picks up the initial content and rerenders the page with the loaded content in place.
Here's my test page JS file:
import React from 'react'
import map from 'lodash.map';
import { initStore } from '../lib/store';
import * as actions from '../lib/actions';
import withRedux from 'next-redux-wrapper';
class IndexPage extends React.PureComponent {
static getInitialProps = ({ store, req }) => Promise.all([
store.dispatch(actions.fetchAll)
]).then( () => ({}) )
render() {
const latestPlants = this.props.plants.latest || [];
return (
<div>
<h1>Test page</h1>
{ map(this.props.plants.items, p => (
<div>{p.fields.name}</div>
))}
</div>
)
}
}
export default withRedux(initStore, data => data, null)(IndexPage)
For whatever it's worth, here's the action that I call above:
export const fetchAll = dispatch => {
dispatch({
type: LOADING_ALL
})
return axios.get('/api/frontpage')
.then( response => {
const data = response.data
dispatch({
type: RESET_AUTHORS,
payload: data.authors
})
dispatch({
type: RESET_PLANTS,
payload: data.plants
})
dispatch({
type: RESET_POSTS,
payload: data.posts
})
});
}
Any help with this would be greatly appreciated, I'm at a loss on how to make this work as expected. Anyone have any leads? Please also comment if there's something I can clarify.
I recommend to split the code in different parts. First, I'll create a store, with something like this:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducers'
export const initStore = (initialState = {}) => {
return createStore(reducer, initialState, applyMiddleware(thunkMiddleware))
}
Then I'll create the store with the types to handle:
const initialState = {
authors: null,
plants: null,
posts: null
}
export default (state = initialState, action) => {
switch (action.type) {
case 'RESET':
return Object.assign({}, state, {
authors: action.authors,
plants: action.plants,
posts: action.posts
})
default:
return state
}
}
In the actions I'll have something like this:
export const fetchAll = dispatch => {
return axios.get('/api/frontpage')
.then( response => {
const data = response.data
dispatch({
type: 'RESET',
authors: data.authors,
plants: data.plants,
posts: data.posts
})
});
}
The index will be something like this:
import React from 'react'
import { initStore } from '../store'
import withRedux from 'next-redux-wrapper'
import Main from '../components'
class Example extends React.Component {
render() {
return (
<div>
<Main />
</div>
)
}
}
export default withRedux(initStore, null)(Example)
And the component Main:
import React, {Component} from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { fetchAll } from '../../actions'
class Data extends Component {
componentWillMount() {
this.props.fetchAll()
}
render() {
const { state } = this.props
return (
<div>
<h1>Test page</h1>
{ map(state.plants.items, p => (
<div>{p.fields.name}</div>
))}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDistpatchToProps = dispatch => {
return {
fetchAll: bindActionCreators(fetchAll, dispatch)
}
}
export default connect(mapStateToProps, mapDistpatchToProps)(Data)
Make the changes for what you need.
You can check some full examples here:
Form handler
Server Auth

Resources