I have a React app with a currency unit switch. I have a function to switch the unit and update redux so that every component that has called the unit will be re-rendered. The problem is the redux prop (storedCurrencyUnit) is UNDEFINED whenever I updated the value and call the update function to redux.
Switch component
import { compose } from 'recompose';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { updateCurrencyUnit } from '../../store/actions';
class FrontHeader extends Component {
handleCurrencyChange = (e) => {
const { updateCurrencyUnit, storedCurrencyUnit } = this.props;
updateCurrencyUnit(e.target.checked)
console.log("unit", storedCurrencyUnit) // this is UNDEFINED
this.setState({ aud: e.target.checked }, () => {
localStorage.setItem("currencyUnit", this.state.aud ? "AUD" : "USD")
})
}
render() {
return (
<Switch
checked={this.state.aud}
onChange={this.handleCurrencyChange}
color="secondary"
name="aud"
inputProps={{ 'aria-label': 'currencyUnit' }}
/>
)
}
}
const mapStateToProps = (state) => ({
storedCurrencyUnit: state.storedCurrencyUnit
})
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
updateCurrencyUnit: updateCurrencyUnit,
}, dispatch);
}
export default compose(connect(mapStateToProps, mapDispatchToProps))(FrontHeader);
currencyReducer.js
const storedCurrencyUnit = (state = null, action) => {
switch (action.type) {
case 'UPDATE_CURRENCYUNIT':
return action.payload;
default:
return state;
}
}
export default storedCurrencyUnit;
actions.js
export const updateCurrencyUnit = (updatedCurrencyUnit) => {
return {
type: 'UPDATE_CURRENCYUNIT',
payload: updatedCurrencyUnit,
}
}
How can I solve this?
You need to dispatch the action using dispatcher. only that will maintain the promise and let know the redux store.
this.props.dispatch(updateCurrencyUnit("some value"));
Related
I am trying to make a call that changes redux state but i am having problems with dispatching the action. I am sure all imports are correct. I think the main problem is in mapStateToProps but just cant seem to find it.
Call
onClick={() => this.props.ethereum}
mapStateToProps and other...
const mapStateToProps = state => {
return({
depositMenu: state.depositMenu
})
}
const mapDispatchToProps = dispatch => {
return ( {
visa: () => dispatch(visa()),
bitcoin: () => dispatch(bitcoin()),
ethereum: () => dispatch(ethereum())
})
}
export default connect(
mapStateToProps,mapDispatchToProps
)(Deposit)
Actions
export const visa= () => {
return {
type: 'VISA'
}
}
export const bitcoin = () => {
return {
type: 'BITCOIN'
}
}
export const ethereum = () => {
return {
type: 'ETHEREUM'
}
}
Reducer
const MainPageDeposit = (state = 'visa', action) => {
switch (action.type) {
case 'VISA':
return state = 'visa';
case 'ETHEREUM':
return state = 'ethereum';
case 'BITCOIN':
return state = 'bitcoin';
default:
return state;
}
}
export default MainPageDeposit;
And combine reducers
import MainPageDeposit from './MainPageDeposit';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
depositMenu: MainPageDeposit,
})
export default allReducers;
I think you should change onClick={() => this.props.ethereum} to onClick={this.props.ethereum}
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 working in a react native app with react redux integration. When I call dispatch from a service my store is getting updated but somehow my component is not re-rendering.
Is it wrong to call dispatch from a service file and not from mapDispatchToProps function.
store.js
import { memesReducer } from './memesReducer'
export default combineReducers({
memesReducer
});
export default configureStore = () => {
const store = createStore(rootReducer);
return store;
}
memesReducer.js
const initialState = { memeList: [] }
export const memesReducer = (state = initialState, action) => {
switch (action.type) {
case LOAD_MEMES: {
return { ...state,
memeList: action.data
}
}
default:
return state;
}
}
memeService.js
import configureStore from '../redux/store';
import { loadMemes } from '../redux/actions';
const store = configureStore();
export const getMemesList = () => {
axios('https://jsonplaceholder.typicode.com/albums')
.then(response => {=
store.dispatch(loadMemes(response.data))
})
.catch(error => { console.error('getMemesList : ', error); })
}
memeActions.js
export const loadMemes = memesListData => ({
type: LOAD_MEMES,
data: memesListData
});
MemeList.js
class MemeList extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
getMemesList()
}
render() {
const memeListData = this.props.memeList.map((meme) => <MemeCard meme={meme} />)
return (
<Container>
<Content>
<List>
{ memeListData }
</List>
</Content>
</Container>
)
}
}
const mapStateToProps = state => {
return {
memeList: state.memesReducer.memeList,
}
}
export default connect(mapStateToProps)(MemeList);
memeActions.js
export const getMemesList = () => dispatch => {
axios("https://jsonplaceholder.typicode.com/albums")
.then(response => dispatch(loadMemes(response.data)))
.catch(error => {
console.error("getMemesList : ", error);
});
};
const loadMemes = memesListData => ({
type: "LOAD_MEMES",
data: memesListData
});
memeReducer.js
case "LOAD_MEMES": {
return { ...state, memeList: action.data };
}
index.js
export default combineReducers({
memesReducer: memeReducer
});
memeList.js
class memeList extends Component {
componentDidMount() {
this.props.getMemesList();
}
render() {
console.log(this.props.memeList);
return <div>MemeList</div>;
}
}
const mapStateToProps = state => ({
memeList: state.memesReducer.memeList
});
export default connect(
mapStateToProps,
{ getMemesList }
)(memeList);
Yeah bro it wouldn't work. You should call dispatch in a Redux connected component.
What you can do is await or attach a .then to the Service Call and THEN call the dispatch after the await or inside the .then.
call your actions like this then only i will work.
componentDidMount() {
this.props.getMemesList()
}
for your more clarification check this official documentation react redux
I am using React redux with firebase realtime database.
In App.js I am dispatching an action fetchAllPosts
App.js
class App extends Component {
componentDidMount() {
this.props.fetchAllPosts();
}
render() {
return (
<div className="App">
// something ...
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
fetchAllPosts: () => {dispatch(allPosts())}
}
}
My action looks like this (I am using redux-thunk):
action
export function allPosts() {
return (dispatch) => {
firebase.database().ref('posts/').on('value', (snapshot) => {
dispatch({type: "ALL_POSTS", postsArray: snapshot.val(), loading: false})
})
}
}
Then I am combining reducers (I know in this case it is not necessary):
const rootReducer = combineReducers({
allPosts: postsReducer
})
My reducer looks like this:
reducer
const initialState = {
allPosts: []
}
const postsReducer = (state = initialState, action) => {
switch(action.type) {
case "ALL_POSTS" :
console.log("action payload all posts", action.postsArray)
return {
...state,
loading: false,
allPosts: action.postsArray
}
break;
default:
return state
}
return state
}
And finally: my SinglePostview component looks like this:
SinglePostview.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
class SinglePostview extends Component {
render() {
console.log("ppp", this.props)
return (
<h2>{this.props.post.title}</h2>
)
}
}
const mapStateToProps = (state, ownprops) => {
const postId = ownprops.match.params.postid
return {
post: state.allPosts.allPosts[postId]
}
}
export default connect(mapStateToProps)(SinglePostview);
Here when the render method is executing, this.props.post is undefined and I have the error:
TypeError: Cannot read property 'title' of undefined.
The problem is: when the app loads for the first time, props.post is undefined (so I have an error) and after about 1 second it receives the value but it doesn't change anything - the error still exists and the value is not displaying.
Could anyone help me?
Assuming your reducer is fine, you can fix this by
changing this
render() {
return (
<h2>{this.props.post.title}</h2>
)
}
To this:
render() {
if (!this.props.post){
return null;
}
return (
<h2>{this.props.post.title}</h2>
)
}
or
render() {
return (
<h2>{this.props.post && this.props.post.title}</h2>
)
}
You are defining allPosts to be an array
const initialState = {
allPosts: []
}
But you are trying to access it like an object.
state.allPosts.allPosts[postId]
Hence, if your state.allPosts.allPosts is an array , try using the ES6 find() method to get a post from the array with the postId.
Assuming
state.allPosts.allPosts = [
{postId: 1,title:'abcd'},
{postId:2,title:'def'}
]
state.allPosts.allPosts.find(post => postId === post.postId)
I am trying to pass in store as a property to AddTodo but I am getting the error: Cannot read property 'todos' of undefined
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { connect } from 'react-redux'
import { createStore } from 'redux'
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text
}
default:
return state
}
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
default:
return state
}
}
let store = createStore(todos)
let nextTodoId = 0
const AddTodo = () => {
let input;
//this works
console.log(store.getState())
//this doesn't
console.log(this.props.todos)
return (
<div>
<input ref={node => {input = node}}/>
<button onClick = {() => {
store.dispatch({
type: 'ADD_TODO',
id: nextTodoId++,
text: input.value
});
input.value = ''
}}>
Add Todo
</button>
</div>
)
};
store.subscribe(AddTodo)
ReactDOM.render(
<AddTodo
todos={store.getState()}
/>,
document.getElementById('root'));
I am a little confused why I am getting the error when printing this.props.todos. I thought I was passing in todos as a prop in <AddTodo todos={...}/>
Functional components work differently, they don't have a context of this, but their props are passed through the arguments of the function
To make it work, just change your function call of addToDos, like this:
const AddTodo = (props) => {
let input;
console.log(props.todos);
return (/* and the rest of your code...*/) ;
};
For the rest, as Arun Ghosh is saying, you should revisit your subscribe pattern, for example like this
store.subscribe(() => {
ReactDOM.render(
<AddTodo
todos={store.getState()}
/>,
document.getElementById('root'));
});
You should pass the store object and subscribe for changes
store.subscribe(() => {
// In our case the state will be updated when todos are set
console.log(store.getState().todos);
});