I'm using a router component for routing my app. I have a file like this:
import CustomerDetailsContainer from '../views/CustomerDetails/customerDetailsContainer';
import CustomerInfoContainer from '../views/CustomerInfo/customerInfoContainer';
import { setUIState } from '../../actions/index';
const inCustomerInfo = (store) => {
store.dispatch(setUIState(CURRENT_SCREEN, 'customerInfo'));
};
const inCustomerDetails = (store) => {
store.dispatch(setUIState(CURRENT_SCREEN, 'customerDetails'));
};
export default (store) => {
return [
authenticatedRouteConfig(
store,
`/${CUSTOMER_INFO}`,
CustomerInfoContainer,
inCustomerInfo
),
authenticatedRouteConfig(
store,
`/${CUSTOMER_DETAILS}/:cid`,
CustomerDetailsContainer,
inCustomerDetails
),
];
};
And error is showing that store.dispatch is not a function. What am i missing? Why this message is appearing? Isn't store a global variable?
You have to use dispatch from redux to dispatch action.
import {connect} from "react-redux"
const mapDispatchToProps = (dispatch) => {
return {
inCustomerInfo : (setUIState) => {
dispatch(setUIState(CURRENT_SCREEN, 'customerInfo')
},
inCustomerDetails : (setUIState) => {
dispatch(setUIState(CURRENT_SCREEN, 'customerDetails')
}
}
export default connect(mapDispatchToProps)(Comp)
Related
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 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'v tried so many way to fetch data only once before rendering but have some issue:
1) I Can't call dispatch in componentDidMount because there is the rule that I can do it in Functional component only
2) If I try to call fetch function in the beginning of a Functional component it starts to rerender infinitely because fetch function calls every time and change a state in a redux store
3) I found a solution with useEffect but it generate exception "Invalid hook call" like in first point
How can I call fetch function only once in this component?
here is my component:
import React, { useEffect } from "react";
import { useParams as params} from "react-router-dom";
import { VolunteerCardList } from "./VolunteerCardList";
import { AnimalNeeds } from "./AnimalNeeds";
import { AppState } from "../reducers/rootReducer";
import { connect } from "react-redux";
import { Page404 } from "./404";
import { fetchAnimal } from "../actions/animalAction";
import { Dispatch } from "redux";
import { IAnimalCard } from "../interfaces/Interfaces";
const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
useEffect(() => {
fetch(); //invalid hook call????
}, [])
return (
<div className="container">
some html
</div>
)
}
interface RouteParams {
shelterid: string,
animalid: string,
}
interface mapStateToPropsType {
animal: IAnimalCard,
loading : boolean
}
const mapStateToProps = (state: AppState) : mapStateToPropsType=> {
return{
animal: state.animals.animal,
loading: state.app.loading
}
}
interface mapDispatchToPropsType {
fetch: () => void;
}
const mapDispatchToProps = (dispatch: Dispatch<any>) : mapDispatchToPropsType => ({
fetch : () => {
const route = params<RouteParams>();
dispatch(fetchAnimal(route.shelterid, route.animalid));
}
})
type Props = ReturnType<typeof mapStateToProps> & ReturnType<typeof mapDispatchToProps>;
export default connect(mapStateToProps, mapDispatchToProps as any)(AnimalCard);
this is my reducer:
export const animalReducer = (state: AnimalReducerType = initState, action: IAction) => {
switch (action.type) {
case AnimalTypes.FETCH_ANIMAL:
return {...state, animal: action.payload};
break;
default:
return state;
break;
}
this is action:
export interface IFetchAnimalAction {
type: AnimalTypes.FETCH_ANIMAL,
payload: IAnimalCard
}
export type IAction = IFetchAnimalAction;
export const fetchAnimal = (shelterId : string, animalId: string) => {
return async (dispatch: Dispatch) => {
const response = await fetch(`https://localhost:44300/api/animals/${animalId}`);
const json = await response.json();
dispatch<IFetchAnimalAction>({type: AnimalTypes.FETCH_ANIMAL, payload: json})
}
}
This runs as old lifecycle method componentDidMount:
useEffect(() => {
fetch(); //invalid hook call????
}, [])
I guess the behaviour you want to replicate is the one iterated by componentWillMount, which you cannot do by any of the standard hooks. My go-to solution for this is to let the acquire some loadingState, most explicitly as:
const AnimalCard: React.FC<Props> = ({animal, loading, fetch}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
fetch().then(res => {
// Do whatever with res
setIsLoading(true);
}
}, [])
if(!isLoading){
return null
}
return (
<div className="container">
some html
</div>
)
}
I'm trying to dispatch an action which is by using createStandardAction(typesafe-actions) and then it goes to epic(redux-observable) for api call.
The strange part is that it works perfectly with stub data, it completes the flow(i.e., component->action->epic->reducer->store) but the action doesn't trigger or enters the epic while using it with the actual server
**Component:-**
export const mapDispatchToProps = (dispatch: Dispatch): ReduxActions => ({
loadTestData: () => dispatch(loadTestData())
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(withNavigation(loadData))
**Action**
import { ActionsUnion, createStandardAction } from 'typesafe-actions'
export const LOADDATA_GET = 'LOADDATA_GET'
export const loadData = createStandardAction(LOADDATA_GET)<void>()
const actions = {
loadData
}
export type AllActions = ActionsUnion<typeof actions>
**Epic**
import { Action, MiddlewareAPI } from 'redux'
import { ActionsObservable, Epic } from 'redux-observable'
import { Observable } from 'rxjs'
import {
LOADDATA_GET
} from './loadData.actions'
export const getloadDataEpic: Epic<Action, ReduxState> = (
action$: ActionsObservable<any>,
store: MiddlewareAPI<any, ReduxState>,
{ mobileAPI }: EpicDependencies
) =>
action$
.ofType(LOADDATA_GET)
.mergeMap((action) => {
return Observable.merge(
mobileAPI
.getJSON('/dummypath/loadData')
.mergeMap((response) => {
return Observable.of<any>(
setLoadData(response)
)
})
)}
)
.catch((error) => {
return Observable.of(errorAction(error))
})
I am really confused why the flow doesn't comes to epic for the actual server while for local json data and dummy path it works
Issue fixed, there was some data-mapping issue on the server side
SomeAction is not a function. ( In 'SomeAction()', 'SomeAction' is undefined ).
I get this error when i execute the SomeAction function.
If i only have SomeAction in my actions file and i do
export default SomeAction;
and then import it as below
import SomeAction from 'path/to/action'
it works fine. But since i want more than one functions, i did the following.
this is my Actions.js
const SomeAction = () => dipatch => ({
// Code here
});
const AnotherAction = () => dispatch => ({
// Code here
});
export default { SomeAction, AnotherAction };
then in my App.js
import { SomeAction } from 'path/to/action';
// Here the eslint gives me an error -> 'SomeAction not found in "path/to/action"'
const App = ({ SomeAction }) => {
// Code here
};
App.propTypes = {
SomeAction: PropTypes.func,
}
const mapStateToProps = state => ({
error: state.user.error,
});
export default connect(
mapStateToProps,
{ SomeAction }
)(App);
This worked on a React web app i was coding. Why not in React-Native?
Reviewing, I see you are exporting by default two methods. Normal implementation is one method. Another solution to do this is exporting one by one methods and importing them with their names.
Example of Exporting:
export const SomeAction = () => dipatch => ({
// Code here
});
export const AnotherAction = () => dispatch => ({
// Code here
});
Example of Importing:
import { SomeAction, AnotherAction } from 'path/to/action';
This example is a normal way to export and import functions.
You can not have two default methods exported.
export const SomeAction = () => dipatch => ({
// Code here
};
export const AnotherAction = () => dispatch => ({
// Code here
};
they will be available in your App component in as follow:
import { SomeAction , AnotherAction} from 'path/to/action';
Importing a Defautl export is as follow
import { SomeAction } from 'path/to/action'; or import SomeAction from 'path/to/action';
just use this abpve export const way