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.
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())
})
The common cause for my issue when researching this is mutating the state and not returning a new object of the state which causes redux to not recognize a change. However, this is not and has never been an issue and i'm well aware of it. I'm returning a new object. In the logger which you can see in the attached image it displays the successful api call resolved and the nextState is updated but never rendered. Refreshing the page acts exactly the same even though i expected to possibly need to do so upon initial landing to root page.
Component:
import pokemonReducer from '../../reducers/pokemon_reducer';
import PokemonIndexItem from './pokemon_index_item';
import {Route} from 'react-router-dom';
import PokemonDetailContainer from './pokemon_detail_container';
class PokemonIndex extends React.Component {
componentDidMount() {
this.props.requestAllPokemon();
}
render() {
const pokemon = this.props.pokemon;
return (
<section className="pokedex">
<Route path='/pokemon/:pokemonID' component={PokemonDetailContainer} />
<ul>{pokemon && pokemon.map(poke => <li>{poke.name}{poke.id}</li>)}</ul>
</section>
);
}
}
export default PokemonIndex;
and the container:
import {connect} from 'react-redux';
import { selectAllPokemon } from '../../reducers/selectors';
import PokemonIndex from './pokemon_index';
import { requestAllPokemon } from '../../actions/pokemon_actions';
const mapStateToProps = state => ({
pokemon: selectAllPokemon(state)
});
const mapDispatchToProps = dispatch => ({
requestAllPokemon: () => dispatch(requestAllPokemon())
});
export default connect(mapStateToProps, mapDispatchToProps)(PokemonIndex);
the reducer:
import { RECEIVE_ALL_POKEMON, RECEIVE_SINGLE_POKEMON} from '../actions/pokemon_actions';
const pokemonReducer = (initialState = {}, action) => {
Object.freeze(initialState);
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, initialState, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
let poke = action.payload.pokemon
return Object.assign({}, initialState, {[poke.id]: poke})
default:
return initialState;
}
};
export default pokemonReducer;
secondary reducer:
import { combineReducers } from 'redux';
import pokemonReducer from './pokemon_reducer'
const entitiesReducer = combineReducers({
pokemon: pokemonReducer,
});
export default entitiesReducer;
rootreducer:
import {combineReducers} from 'redux';
import entitiesReducer from './entities_reducer';
const rootReducer = combineReducers({
entities: entitiesReducer
});
export default rootReducer;
as requested here is the selectors defined in reducers folder
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
export const selectSinglePokemon = (state) => {
Object.values(state.entities.pokemon)
};
and here is the actions created:
export const RECEIVE_ALL_POKEMON = "RECEIVE_ALL_POKEMON";
export const RECEIVE_SINGLE_POKEMON = "RECEIVE_SINGLE_POKEMON";
import * as APIUtil from '../util/api_util';
export const receiveAllPokemon = (pokemon) => (
{
type: RECEIVE_ALL_POKEMON,
pokemon
}
);
export const requestAllPokemon = () => (dispatch) => {
APIUtil.fetchAllPokemon()
.then(
pokemon =>
{ dispatch(receiveAllPokemon(pokemon));}
);
};
export const receiveSinglePokemon = data => (
{
type: RECEIVE_SINGLE_POKEMON,
data
}
);
export const requestSinglePokemon = id => (dispatch) => {
APIUtil.fetchSinglePokemon(id)
.then(pokemon => {dispatch(receiveSinglePokemon(pokemon));
return pokemon;});
};
nextstate showing in console
As you stated in your question, your redux state is getting properly set but your new state is never being rendered and I think this has to do with your selector. It looks to me that you forgot to return your computed state.
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
// will return undefined
For returning your state you have two options:
Explicit return
export const selectAllPokemon = (state) => {
return Object.values(state.entities.pokemon);
};
Implicit return
export const selectAllPokemon = (state) => (
Object.values(state.entities.pokemon);
);
I refer to this article or look at the examples I created in playground to get a better unstanding of implicit and explicit return in arrow functions.
I want my component to fetch an array of objects from the server. Each object is a message with author, body and date. I then want to render these messages in my react component.
My react component currently fetches data from the server before mounting. It will then store this message list in the redux state.|
I'm sure there's a better way of writing this code.
1. Can I place the fetch request in either the Action or Reducer file?
2. Can I write a function in the component to make the async call?
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from '../actions/actions_index.js';
class MessageList extends Component {
constructor(props) {
super(props)
}
componentWillMount() {
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => console.log('An error occured receiving messages', error))
.then((data) => {
this.props.fetchMessages(data.messages);
});
}
render() {
return (
<div className="message-list">
{this.props.messageList.map( (message, index) => { return <Message key={index} message={message}/> })}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchMessages: fetchMessages },
dispatch
)
}
export default connect(mapStateToProps, mapDispatchToProps)(MessageList);
Can I place the fetch request in either the Action or Reducer file?
The fetch request should be placed in action creator. Where the retrieved data will be dispatched to reducer later to manipulate the data, and lastly update the store to show on UI. Here's simple flow for most of react-redux app.
UI -> Action creator (calling request, saga etc..) -> reducer -> store -> UI
Can I write a function in the component to make the async call?
Yes, this should be called action creator, and you can see actions.js below for more reference.
I think you can safely follow this sample pattern where most tutorials out there apply. I'm assuming all files listed here are in the same directory.
constant.js
const MESSAGE_FETCH__SUCCESS = 'MESSAGE/FETCH__SUCCESS'
const MESSAGE_FETCH__ERROR = 'MESSAGE/FETCH__ERROR'
export {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
}
actions.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const fetchMessageError = () => ({
type: MESSAGE_FETCH__ERROR
})
const fetchMessageSuccess = data => ({
type: MESSAGE_FETCH__SUCCESS,
payload: data
})
const fetchMessages = () => {
const data = fetch(...);
// if error
if (data.error)
fetchMessageError();
else fetchMessageSuccess(data.data);
}
export {
fetchMessages
}
reducers.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const INIT_STATE = {
messageList: []
}
export default function( state = INIT_STATE, action ) {
switch(action.type) {
case MESSAGE_FETCH__SUCCESS:
return {
...state,
messageList: action.payload
}
case MESSAGE_FETCH__ERROR:
// Do whatever you want here for an error case
return {
...state
}
default:
return state;
}
}
index.js
Please read the comment I noted
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from './actions';
class MessageList extends Component {
/* If you don't do anything in the constructor, it's okay to remove calling `constructor(props)`
*/
//constructor(props) {
// super(props)
//}
// I usually put this async call in `componentDidMount` method
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div className="message-list">
{
/* Each message should have an unique id so they can be used
for `key` index. Do not use `index` as an value to `key`.
See this useful link for more reference: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js
*/
this.props.messageList.map( message => <Message key={message.id} message={message}/> )
}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
export default connect(mapStateToProps, {
fetchMessages
})(MessageList);
You could use redux-thunk in an action called getMessages.
So:
(The double arrow func, is to return an action, see redux-thunk)
const getMessages = ()=>(dispatch, getState)=>{
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => dispatch(['error', error]))
.then((data) => {
dispatch(data);
})
}
Then you've successfully reduced your component to:
componentWillMount(){
this.props.getMessages()
}
I think #Duc_Hong answered the question.
And in my opinion, I suggest using the side-effect middle-ware to make AJAX call more structured, so that we could handle more complicated scenarios (e.g. cancel the ajax request, multiple request in the same time) and make it more testable.
Here's the code snippet using Redux Saga
// Actions.js
const FOO_FETCH_START = 'FOO\FETCH_START'
function action(type, payload={}) {
return {type, payload};
}
export const startFetch = () => action{FOO_FETCH_START, payload);
// reducer.js
export const foo = (state = {status: 'loading'}, action) => {
switch (action.type) {
case FOO_FETCH_STARTED: {
return _.assign({}, state, {status: 'start fetching', foo: null});
}
case FOO_FETCH_SUCCESS: {
return _.assign({}, state, {status: 'success', foo: action.data});
}
......
}
};
Can I place the fetch request in either the Action or Reducer file?
// Saga.js, I put the ajax call (fetch, axios whatever you want) here.
export function* fetchFoo() {
const response = yield call(fetch, url);
yield put({type: FOO_FETCH_SUCCESS, reponse.data});
}
// This function will be used in `rootSaga()`, it's a listener for the action FOO_FETCH_START
export function* fooSagas() {
yield takeEvery(FOO_FETCH_START, fetchFoo);
}
Can I write a function in the component to make the async call?
// React component, I trigger the fetch by an action creation in componentDidMount
class Foo extends React.Component {
componentDidMount() {
this.props.startFetch();
}
render() {
<div>
{this.props.foo.data ? this.props.foo.data : 'Loading....'}
<div>
}
}
const mapStateToProps = (state) => ({foo: state.foo});
const mapDispatchToProps = { startFetch }
export default connect(mapStateToProps, mapDispatchToProps) (Foo);
//client.js, link up saga, redux, and React Component
const render = App => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
combinedReducers,
initialState,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
store.runSaga(rootSaga);
return ReactDOM.hydrate(
<ReduxProvider store={store}>
<BrowserRouter><AppContainer><App/></AppContainer></BrowserRouter>
</ReduxProvider>,
document.getElementById('root')
);
}
I need to change the "global" state of Redux (I believe it's called storage). This is my code:
reducer
export const user = (state = {}, action) => {
console.log(4);
console.log(action.type)
console.log(action.payload)
switch (action.type) {
case C.SET_USER:
console.log(action.payload);
return action.payload;
case C.CLEAR_USER:
return action.payload;
default:
return state;
}
};
Action:
export const setUser = (user = {}) => {
console.log(user);
return {
type: C.SET_USER,
payload: user,
}
};
Calling the action:
const user = {test:true};
setUser(this.state.user);
But if I run this code, it fails and doesn't call the reducer. It calls the action, but not the reducer. What am I missing?
My current app.js code:
export default class App extends Component {
constructor(p) {
super(p);
this.state = {user: null};
}
setUser = () => {
const {uid} = firebase.auth().currentUser;
firebase.database().ref('Users').child(uid).on('value', r => {
const user = r.val();
this.setState({user: user});
console.log(this.state.user);
setUser(this.state.user);
});
};
componentWillMount() {
if (firebase.auth().currentUser) {
this.setUser();
}
firebase.auth().onAuthStateChanged(async () => {
console.log('authChanged');
if (!firebase.auth().currentUser) {
return null;
}
this.setUser();
});
}
render() {
return (
<div className="App">
<Nav/>
</div>
);
}
}
setUser have to be dispatched and not simply called:
store.dispatch(setUser(user));
But that's not really the react way, you'd better use mapDispatchToProps in your connect function to dispatch actions directly from component props. Something along the lines of:
import { setUser } from 'store/user';
// ...
class UserComponent extends React.Component {
// ...
someMethod() {
this.props.setUser(user);
}
}
export default connect(
null,
({setUser: setUser})
)(UserComponent);
This allows your React component to be linked to your Redux store in an optimized and bug-free way. That's also the way most developer use, so you're likely to find a lot of docs on this.
Example: Your connected Component where you want to use your setUser action with redux
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setUser} from '../../actions';
class YourComponent extends Component {
render(){
// now your redux action is passed to component as prop
// and you can use it like
this.props.setUser(some-user);
return()
}
}
export default connect(null, {setUser})(YourComponent);
first of all you have to dispatch action to change the state , second you have to connect your component to the store
to connect your component to the store
...
import { connect } from 'react-redux';
class MyComponent extends React.Component {
...
}
export default connect((store) => ({...}))
when you connect your component to the store you will have access to dispatch function in the props
to dispatch action do this :
this.props.dispatch(setUser());
I believe it's called storage
BTW it called store
Fairly new to these technologies and am at wit's end. I've got two components; a parent which contains a form (using redux-form) and writes a new record to a database, and a child which lists some data.
The only thing I can't get to work is refreshing that child list when the form submit completes. If I refresh the page, the new data is visible. From what I had read, it was my understanding that by wiring up redux-form, that my state would refresh automatically...or something like that. Am I even going about this the right way? Here's everything...
My index reducer:
import { combineReducers } from 'redux';
import { reducer as formReducer } from "redux-form";
import ItemsReducer from "../reducers/items";
const rootReducer = combineReducers({
form: formReducer,
items: ItemsReducer
});
export default rootReducer;
My items reducer:
import { GET_ALL_ITEMS } from "../actions/items";
export default (state = {}, action) => {
switch (action.type) {
case GET_ALL_ITEMS:
return action.payload.data;
default:
return state;
}
}
My actions:
import axios from "axios";
export const GET_ALL_ITEMS = "GET_ALL_ITEMS";
export const SAVE_ITEM = "SAVE_ITEM";
const ROOT_API_URL = "http://myapi:3000/api";
export function getAllItems() {
let request = axios.get(`${ROOT_API_URL}/items`);
return {
type: GET_ALL_ITEMS,
payload: request
};
}
export function saveItem(item, callback) {
let request = axios
.post(`${ROOT_API_URL}/item`, item)
.then(() => callback());
return {
type: SAVE_ITEM,
payload: request
};
}
The (abbreviated) parent (list and form):
import ItemsList from "./items_list";
...
onSubmit = (item) => {
let { saveItem } = this.props;
saveItem(item, () => {
// this is successful
});
}
...
//the list in render()
<div>
<ItemsList />
</div>
...
//redux-form wired up at bottom
export default reduxForm({
form: "EditItemForm",
})(connect(null, { saveItem })(Items));
The child component:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { getAllItems } from "../actions/items";
class Shows extends Component {
componentDidMount() {
this.props.getAllItems();
}
render() {
return(
<div className="body-content-partial">
{this.renderItems()}
</div>
);
}
renderItems() {
let { items } = this.props;
return items.map(item => {
return(
<a href="#" key={item.id}>
<div className="list-item-noavatar list-lines-div">
<div className="list-title">
{item.name}
</div>
<div className="status-div">
<span className="status-indicator"></span>
{item.active}
</div>
</div>
</a>
);
});
}
}
function mapStateToProps(state) {
return { items: state.items };
}
export default connect(mapStateToProps, { getAllItems })(Items);
OK, absolutely fixed it this time. I had to make a call to getAllItems() on the form submit as well as pass it into the dispatch portion of the connect() call, for the redux-form setup. Respectively:
import { saveItem, getAllItems } from "../actions/items";
...
onSubmit = (item) => {
let { saveItem, onSave, getAllItems } = this.props;
saveItem(item, () => {
onSave();
getAllItems();
});
}
...
export default reduxForm({
form: "ItemEditForm",
})(connect(null, { saveItem, getAllItems })(ItemEditForm));