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
Related
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"));
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}
In React itself, I have the function getTodos(), in which it calls another functiongetTodo(). It passes res.data[0].id to the getTodo() function.
React
Demo here: https://stackblitz.com/edit/react-qvsjrz
Code below:
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
todo: {}
};
}
componentDidMount() {
this.getTodos()
}
getTodos = () => {
axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET'
})
.then(res => {
this.setState({
todos: res.data,
}, () => this.getTodo(res.data[0].id))
})
.catch(error => {
console.log(error);
});
};
getTodo = (todoId) => {
axios({
url: `https://jsonplaceholder.typicode.com/todos/${todoId}`,
method: 'GET'
})
.then(res => {
this.setState({
todo: res.data
})
})
.catch(error => {
console.log(error);
})
}
render() {
console.log(this.state.todo);
return (
<div>
</div>
);
}
}
The above code tries to convert to react + redux.
React + redux
In actions, I declared two functions getTodo andgetTodos. Could someone advise me on how to call the getTodo function in thegetTodos function by passing the getTodo id function?
Demo here: https://stackblitz.com/edit/react-ewpquh?file=actions%2Findex.js
actions
import axios from 'axios';
export const GET_TODOS = 'GET_TODOS';
export const FETCH_SUCCESS = 'FETCH_SUCCESS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const getTodos = () =>
dispatch => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET',
})
.then(({data})=> {
console.log(data);
dispatch({type: GET_TODOS, payload:{
data
}});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
export const getTodo = () =>
dispatch => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET',
})
.then(({data})=> {
console.log(data);
dispatch({type: GET_TODOS, payload:{
data
}});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
Todos
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {getTodos} from '../.././actions';
class Todos extends Component {
componentDidMount() {
this.props.getTodos();
}
render() {
return (
<ul>
{this.props.todos.map(todo => {
return <li key={todo.id}>
{todo.title}
</li>
})}
</ul>
);
}
}
const mapStateToProps = state => {
console.log(state.todos);
const { todos } = state;
return {
todos
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos())
});
export default connect(mapStateToProps, mapDispatchToProps)(Todos);
reducers
import {GET_TODOS} from '../../actions';
const initialState = {
todos: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_TODOS':
return {
...state,
todos: action.payload.data
};
default:
return state;
}
};
export default rootReducer;
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Instead of over-complicating your actions, you should have separate action types for different APIs.
GET_TODOS - For /todos API
GET_TO - For /todos/ API
To add getTodo method with ID, this is how I solved it -
For each li tag, add an onClick that calls your getTodo API. (This is done as an example for the sake of adding getTodo in the workflow.
return <li key={todo.id} onClick={() => this.handleClick(todo.id)}>
Add handleClick which calls getTodo method from props.
First add getTodo in your components mapDispatchToProps:
import { getTodo, getTodos} from '../.././actions';
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id))
});
Add handleClick -
handleClick = id => {
this.props.getTodo(id).then(() => {
console.log(`You Clicked: ${JSON.stringify(this.props.todo)}`)
})
}
Update your getTodo action to take ID as input:
NOTE: The added GET_TODO type
export const getTodo = (id) => dispatch => {
return axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: 'GET',
})
.then(({data})=> {
// console.log(data);
dispatch({type: GET_TODO, payload: data});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
Separate out your reducers into todos and todo and use combineReducers from redux package -
const todos = (state = [], action) => {
const { type, payload } = action;
switch(type) {
case 'GET_TODOS':
return payload;
default:
return state;
}
}
const todo = (state = {}, action) => {
const { type, payload } = action;
switch(type) {
case 'GET_TODO':
return payload;
default:
return state;
}
}
const rootReducer = combineReducers({todos, todo});
Run the app and click on any item in the todo list. Console log for the clicked todo item is shown when API response for that ID is fetched.
The live sandbox is available here - https://stackblitz.com/edit/react-ndkasm
In actions, I declared the actionexport const CLEAR_ARRAY_TODOS = 'CLEAR_ARRAY_TODOS';
I imported this action in reducers. And I created a new case case 'CLEAR_ARRAY_TODOS' in reducers` in the switch statement:
case 'CLEAR_ARRAY_TODOS':
return {
todos: [],
};
In thetodos component I imported the action CLEAR_ARRAY_TODOS. And here I have a problem as in mapDispatchToProps in the functiongetTodos send this action CLEAR_ARRAY_TODOS and connect to the buttonClear Array Todos?
Demo here: https://stackblitz.com/edit/react-iuvdna?file=reducers%2Findex.js
Actions
import axios from 'axios';
export const GET_TODOS = 'GET_TODOS';
export const CLEAR_ARRAY_TODOS = 'CLEAR_ARRAY_TODOS';
export const FETCH_SUCCESS = 'FETCH_SUCCESS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const getTodos = () =>
dispatch => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET',
})
.then(({data})=> {
console.log(data);
dispatch({type: GET_TODOS, payload:{
data
}});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
export const getTodo = () =>
dispatch => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET',
})
.then(({data})=> {
console.log(data);
dispatch({type: GET_TODOS, payload:{
data
}});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
Reducers
import {GET_TODOS, CLEAR_ARRAY_TODOS} from '../../actions';
const initialState = {
todos: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_TODOS':
return {
...state,
todos: action.payload.data,
todo: action.payload.data[0]
};
case 'CLEAR_ARRAY_TODOS':
return {
todos: [],
};
default:
return state;
}
};
export default rootReducer;
Todos
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {getTodos, CLEAR_ARRAY_TODOS} from '../.././actions';
class Todos extends Component {
componentDidMount() {
this.props.getTodos();
}
render() {
return (
<div>
<button>Clear array Todos</button>
<ul>
{this.props.todos.map(todo => {
return <li key={todo.id}>
{todo.title}
</li>
})}
</ul>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state.todos);
console.log(state.todo);
const { todos } = state;
return {
todos
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos())
});
export default connect(mapStateToProps, mapDispatchToProps)(Todos);
CLEAR_ARRAY_TODOS is not an action, is just a variable holding an action type string. You should add a clearTodos action
export const clearTodos = { type: CLEAR_ARRAY_TODOS }
or action creator
export const clearTodos = () => ({ type: CLEAR_ARRAY_TODOS })
and use that in your component mapDispatchToProps (like you do with getTodos)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {getTodos, clearTodos} from '../.././actions';
class Todos extends Component {
componentDidMount() {
this.props.getTodos();
}
render() {
return (
<div>
<button onClick={ this.props.clearTodos }>Clear array Todos</button>
<ul>
{this.props.todos.map(todo => {
return <li key={todo.id}>
{todo.title}
</li>
})}
</ul>
</div>
);
}
}
const mapStateToProps = state => {
console.log(state.todos);
console.log(state.todo);
const { todos } = state;
return {
todos
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
clearTodos: () => dispatch(clearTodos())
});
export default connect(mapStateToProps, mapDispatchToProps)(Todos);
Just add the clearTodos action into mapDispatchToProps
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
clearTodos: () => dispatch({type: CLEAR_ARRAY_TODOS})
});
Then you have to only handle this action when button is clicked, so add the onClick attribute over there:
<button onClick={this.props.clearTodos}>Clear array Todos</button>
without redux it works so that not a api connection problem
I have an express app connected to react with proxy I have already managed to display my data in react but now i want to make that in redux soo:
There is my problem, i have maked all the reducers/action, store and combine reducer but I didn't see any datas in my page and i haven't any errors
There is my code :
Action
export const api = ext => `http://localhost:8080/${ext}`;
//
// ─── ACTION TYPES ───────────────────────────────────────────────────────────────
//
export const GET_ADVERTS = "GET_ADVERTS";
export const GET_ADVERT = "GET_ADVERT";
//
// ─── ACTION CREATORS ────────────────────────────────────────────────────────────
//
export function getAdverts() {
return dispatch => {
fetch("adverts")
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERTS, payload });
});
};
}
export function getAdvert(id) {
return dispatch => {
fetch(`adverts/${id}`)
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERT, payload });
});
};
}
reducer
import { combineReducers } from "redux";
import { GET_ADVERTS, GET_ADVERT } from "../actions/actions";
const INITIAL_STATE = {
adverts: [],
advert: {}
};
function todos(state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ADVERTS:
return { ...state, adverts: action.payload };
case GET_ADVERT:
return { advert: action.payload };
default:
return state;
}
}
const todoApp = combineReducers({
todos
});
export default todoApp;
index.js
//imports
const store = createStore(todoApp, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app")
);
My advertlist page :
//imports..
class Adverts extends Component {
componentDidMount() {
this.props.getAdverts();
}
render() {
const { adverts = [] } = this.props;
return (
<div>
<Header />
<h1>Adverts</h1>
{adverts.map(advert => (
<li key={advert._id}>
<a href={"adverts/" + advert._id}>
{advert.name} {advert.surname}
</a>
</li>
))}
<Footer />
</div>
);
}
}
const mapStateToProps = state => ({
adverts: state.adverts
});
export default connect(
mapStateToProps,
{ getAdverts }
)(Adverts);
I think your problem is here:
function mapStateToProps(state) {
return {
**adverts: state.adverts**
};
}
It should work if you change state.adverts to state.todos.adverts:
function mapStateToProps(state) {
return {
adverts: state.todos.adverts
};
}
Because your reducer is called todos, and it has state { adverts }, that's why you cannot access adverts even tho they are obtained.
You can check out working version here: https://codesandbox.io/s/olqxm4mkpq
The problem is, when you just create a store with one reducer without using combine reducer, it is possible to refer it directly in the ContainerS, like this:
const mapStateToProps = state => {
return{
*name of var*: state.adverts /*direct refers to adverts*/
}
}
But, when it use combined-reducer , it has to refer to an exact reducer that you want to use.like this :
const mapStateToProps = state => {
return{
*name of var* : state.todos.adverts (indirect refers to adverts from combined-reducer todos)
}
}