I'm trying to get access to the store from outside of the component and subscribe it for store changes. I have separate file which I'm using to make an API call.
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import store from '../store/store'
const currentWeatherApi = {
key: "",
base: "https://api.openweathermap.org/data/2.5/"
}
const API = () => {
const inputValue = useSelector(state => state.inputValue);
store.subscribe(() => {
console.log(inputValue)
})
if(inputValue) {
fetch(`${currentWeatherApi.base}weather?q=rzeszow&units=metric&APPID=${currentWeatherApi.key}`)
.then(res => res.json())
.then(result => {
const temp = (Math.floor(result.main.temp));
const tempMin = result.main.temp_min
const tempMax = result.main.temp_max;
const location = result.name;
const sunrise = new Date(result.sys.sunrise * 1000).toISOString().substr(11, 8);
const sunset = new Date(result.sys.sunset * 1000).toISOString().substr(11, 8);
const country = result.sys.country;
const wind = result.wind.speed;
const pressure = result.main.pressure;
const sky = result.weather[0].main;
})
}
export default API;
When I try to console.log anything, nothing happens, like it's not even read. What am I doing wrong?
API isn't a React component so the useSelector hook won't work. You can import the store and call getState on it to get the current state's value.
getState()
Returns the current state tree of your application. It is equal to the
last value returned by the store's reducer.
Returns
(any): The current state tree of your application.
const API = () => {
const state = store.getState();
if(state.searchingBar.inputValue) {
fetch(`${currentWeatherApi.base}weather?q=rzeszow&units=metric&APPID=${currentWeatherApi.key}`)
.then(res => res.json())
.then(result => {
....
})
}
}
Edit for Demo
Demo code:
const initialState = {
inputValue: ""
};
const slice = createSlice({
initialState,
name: "searchingBar",
reducers: {
updateValue: (state, action) => {
state.inputValue = action.payload;
}
}
});
const rootReducer = combineReducers({
searchingBar: slice.reducer
});
const store = configureStore({
reducer: rootReducer
});
const fetch = (url, options) => {
console.log("fetch", { url });
return new Promise((resolve) => {
const response = {
json: async () => "boom"
};
return resolve(response);
});
};
const currentWeatherApi = {
base: "base",
key: "key"
};
const API = () => {
const state = store.getState();
console.log(JSON.stringify(state));
if (state.searchingBar.inputValue) {
fetch(
`${currentWeatherApi.base}weather?q=rzeszow&units=metric&APPID=${currentWeatherApi.key}`
)
.then((res) => res.json())
.then((result) => {
console.log("Result", result);
store.dispatch(slice.actions.updateValue(""));
});
}
};
export default function App() {
return (
<Provider store={store}>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button type="button" onClick={API}>
Call API
</button>
<button
type="button"
onClick={() => store.dispatch(slice.actions.updateValue("test"))}
>
Update State
</button>
</div>
</Provider>
);
}
Related
I'm doing this in other components very similarly, except this one takes a parameter of title_id. I am getting my data to fetch when I check the Redux DevTool and I console logged the payload from the mediaSlice too, but the useSelector in my Component is bringing nothing in and not updating my state. Can you help me figure out what exactly is it that I am messing up on so my selector can send the payload to my Component's state?
I made comments/questions in my code below where I think my problem areas are.
Here is my mediaSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const KEY = process.env.REACT_APP_API_KEY
const BASE_URL = process.env.REACT_APP_BASE_URL
const SINGLE_MEDIA_API = `${BASE_URL}/titlestest`
const initialState = {
media:[],
status: 'idle',
error:null
}
export const fetchSingleMediaTitle = createAsyncThunk(
'medias/fetchSingleMediaTitle',
async (mediaId) => { // <-- would this be where I declare the param key I am using?
try {
const response = await axios.get(
SINGLE_MEDIA_API,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': KEY,
},
params: {
titleId: mediaId,
}
}
)
return response.data.Item;
} catch (error) {
console.error('API call error:', error.message);
}
}
)
const mediaSlice = createSlice({
name: 'medias',
initialState,
reducers:{},
extraReducers(builder) {
builder
.addCase(fetchSingleMediaTitle.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchSingleMediaTitle.fulfilled, (state, action) => {
state.status = 'succeeded'
const loadedMedia = action.payload
state.media = loadedMedia
console.log("loadedMedia: ", loadedMedia); // this is successfully printing the data object
})
.addCase(fetchSingleMediaTitle.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
// SELECTORS
export const selectSingleMedia = (state, mediaId) => state.media.media.find(item => item.title_id === mediaId); //this is where I am suspecting the problem is in
export const getMediaStatus = (state) => state.media.status;
export const getMediaError = (state) => state.media.error;
export default mediaSlice.reducer
And my Component. Reduced for brevity
import React, { useState, useEffect, useRef, Fragment } from 'react'
import { useSelector, useDispatch } from 'react-redux';
import { getMediaError, getMediaStatus, selectSingleMedia, fetchSingleMediaTitle } from '../../features/medias/mediaSlice'
import { useParams, useHistory } from "react-router-dom";
const SingleMediaTitle = () => {
//STATES
const [data, setData] = useState([]);
const {mediaId} = useParams();
const dispatch = useDispatch();
const media = useSelector((state, mediaId) => selectSingleMedia(state, mediaId)) //This is a suspect line too
const mediaStatus = useSelector(getMediaStatus)
const error = useSelector(getMediaError)
useEffect(() => {
if (mediaStatus === 'idle') {
dispatch(fetchSingleMediaTitle(mediaId))
}
setData(media);
console.log("media: ", media); // This is undefined
}, [mediaId, media, mediaStatus, dispatch])
let content;
if (mediaStatus === 'loading') {
content = <Box className="loading">
<Typography variant="subtitle2">Loading ..</Typography>
</Box>
} else if (mediaStatus === 'succeeded') {
content = <Box>
{media.content_source} //Though redux succeeded, this is not displaying
<Box/>
} else if (mediaStatus === 'failed') {
content = <Box className="loading">
<Typography variant="subtitle2">{error}</Typography>
</Box>
}
return (
<Box sx={{p:3, pt:9}}> {content} </Box>
)
}
export default SingleMediaTitle
I really appreciate your help. Being the only front end dev in house is tough lol
I can spot 2 things that can cause the problem:
1) Your selectors
Since useSelector will get the entire state, you have to dig into your slice first and then deeper into the media data. Since the name of slice is medias this should be the first one. According to this your selectors should be:
// SELECTORS
export const selectSingleMedia = (state, mediaId) => state.medias.media.find(item => item.title_id === mediaId);
export const getMediaStatus = (state) => state.medias.media.status;
export const getMediaError = (state) => state.medias.media.error;
2) The single media selector
const {mediaId} = useParams();
const media = useSelector((state, mediaId) => selectSingleMedia(state, mediaId))
Since mediaId exists in the scope of the selector you should not expect it as an argument of the selector and just pass it as parameter to selectSingleMedia like below:
const {mediaId} = useParams();
const media = useSelector((state) => selectSingleMedia(state, mediaId))
I am trying make an "easy" weather app exercise, just get data from api and render it. I am using "google api map" to get the location from a post code to a latitude and longitud parameters so I can use those numbers and pass it to "open weather map" api to get the weather for that location.
It is working but with bugs...
First I used redux for "location" and "weather". Redux was working but useSelector() wasnt displaying the data properly.
Then I decide to make it easy, on "search" component I am calling an api an getting the location I need, I am storing it with redux and it is working, on "weatherFullDispaly" component I am calling an api for the "weather" details and just pass it as props for the children to render the data but they are not getting it.
The thing is, while the app is running, when I put a post code I get an error because the children are not receiving the data but, if I comment out the children on the parent component and then comment in again, all the data print perfect.
Any help please???
const WeatherFullDisplay = () => {
const [weatherDetails, setWeatherDetails] = useState();
const currentLocation = useSelector(getLocationData);
useEffect(() => {
getWeatherDetails();
}, []);
const getWeatherDetails = async () => {
const API_KEY = process.env.REACT_APP_OPEN_WEATHER_MAP_API_KEY;
const { lat, lng } = await currentLocation.results[0].geometry.location;
const response = await axios.get(
`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lng}&exclude=minutely&units=metric&appid=${API_KEY}`
);
setWeatherDetails(response.data);
};
return (
<div className="weather-full-display-details">
<WeatherNow weatherDetails={weatherDetails} />
<HourlyWeather weatherDetails={weatherDetails} />
<FiveDaysWeather weatherDetails={weatherDetails} />
</div>
);
};
const FiveDaysWeather = ({ weatherDetails }) => {
const displayDailyWeather = () => {
const daysToShow = [
weatherDetails.daily[1],
weatherDetails.daily[2],
weatherDetails.daily[3],
weatherDetails.daily[4],
weatherDetails.daily[5],
];
return daysToShow.map((day, i) => {
return (
<WeatherSingleCard
key={i}
typeOfCard="daily"
weekDay={moment(day.dt * 1000).format("dddd")}
icon={day.weather[0].icon}
weather={day.weather[0].main}
temp={day.temp.day}
/>
);
});
};
return (
<div className="day-single-cards">{displayDailyWeather()}</div>
);
};
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
locationDetails: "",
};
const locationSlice = createSlice({
name: "location",
initialState,
reducers: {
setLocation: (state, action) => {
state.locationDetails = action.payload;
},
cleanLocation: (state) => {
state.locationDetails = ""
}
},
});
export const { setLocation, cleanLocation } = locationSlice.actions;
export const getLocationData = (state) => state.location.locationDetails;
export default locationSlice.reducer;
const SearchBar = () => {
const [postCode, setPostCode] = useState();
const [locationDetails, setLocationDetails] = useState();
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
getLocationDetails();
}, [postCode]);
const getLocationDetails = async () => {
const response = await axios.get(
"https://maps.googleapis.com/maps/api/geocode/json",
{
params: {
components: `country:ES|postal_code:${postCode}`,
region: "ES",
key: process.env.REACT_APP_GOOGLE_API_KEY,
},
}
);
setLocationDetails(response.data);
};
const handleSubmit = (e) => {
e.preventDefault();
dispatch(setLocation(locationDetails));
navigate("/detail-weather");
};
const handleChange = (e) => {
setPostCode(e.target.value);
};
I created a very simple React-Redux App and fetching Users and Posts from https://jsonplaceholder.typicode.com/
In my components I am logging Users and Posts data into the console. As far as I see, in the network tab there is one request for Users and 10 requests for Posts. That's correct but in the console, I see 10 Posts requests for each User.
Does it mean ReactJS renders the component 100 times? What is my mistake in this code?
Any help will be greatly appreciated!
My code and codepen link are below
Please check the code in codepen
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapStateToProps = state => {
return { users: state.users, posts: state.posts };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, [getUsers]);
const renderUsers = () =>
props.users.map(user => {
return (
<div>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts' , props.posts);
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [getPosts, userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const PostsContainer = connect(mapStateToProps, mapDispatchToProps)(Posts);
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Does it mean ReactJS renders the component 100 times? What is my mistake in this code?
you have a UserContainer, that renders and requests for users;
once fetched users, you have an update state. UserContainer rerenders, and now you have 10 PostContainers;
each PostContainer makes a request to fetch posts, 10 on total;
it results in 10 state updates. UserContainer rerenders 10 times, and each PostContainer rerenders 10 times;
The component doesn't renders 100 times, each PostContainer renders the initial mount then rerenders 10 times. since there are 10 PostContainers and each rerenders 10 times that's why you might think that renders 100 times.
you have some issues. the dependency issue, which was pointed out is the first. getUsers useEffect should have an empty dependency, and userId useEffect, should depend on userId.
to solve the 10 rerenders on UserContainer due to posts, you need to have a different mapStateToProps to each. for UserContainer you will map only users, otherwise you will get 10 updates due to 10 posts requests:
const mapUserStateToProps = state => {
return { users: state.users };
};
with that it solves UserContainer 10 rerenders.
now about PostContainer there is something that needs to be fixed first, your reducer. your reducer replaces last posts with the current call. in the end you will have only the posts that arrived last, not all posts. to fix that you need to spread your state.
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
eventually, if in your project you could have a repeated request to same userId than it would be necessary to have some validation for not adding the same posts again
now it leads us to mapping props to PostContainer. you would need to have a filter on posts based on userId. mapStateToProps takes props as second argument, which enables us to accomplish that:
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
this looks the end to solve the issue, but each PostContainer still rerenders 10 times. why does this happens since posts will be the same? that happens because filter will return a new array reference, no matter if its content didn't change.
to solve this issue you can use React.memo. you need to provide the component and a equality function to memo. to compare an array of objects there are some solutions, also few libs that provide some deepEqual function. here I use JSON.stringify to compare, but you are free to use some other one:
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
you would validate also other props that could change but that's not the case
now apply React.memo to posts:
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
After all that applied, UserContainer will rerender one once, and each PostContainer will rerender only once as well.
here follows link with working solution:
https://codepen.io/rbuzatto/pen/BaLYmNK?editors=0010
final code:
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapUserStateToProps = state => {
return { users: state.users };
};
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, []);
const renderUsers = () =>
props.users.map(user => {
return (
<div key={user.id}>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapUserStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts');
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
useEffect() renders the component every time something is changed in the dependencies you provided.
Ideally, you should change your components to re-render only when something changes in props. getUser and getPost change on each render. So, it is better to change it to monitor users and posts from state.
In Users:
const { users, getUsers } = props;
useEffect(() => {
getUsers();
}, []); -- Leaving this empty makes it load only on mount.
In Posts:
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);
I am trying to reproduce something I was doing with Reactjs/ Redux/ redux-thunk:
Show a spinner (during loading time)
Retrieve information from remote server
display information and remove spinner
The approach was to use useReducer and useContext for simulating redux as explained in this tutorial. For the async part, I was relying on redux-thunk, but I don't know if there is any alternative to it for useReducer. Here is my code:
The component itself :
const SearchForm: React.FC<unknown> = () => {
const { dispatch } = React.useContext(context);
// Fetch information when clickin on button
const getAgentsInfo = (event: React.MouseEvent<HTMLElement>) => {
const fetchData:() => Promise<void> = async () => {
fetchAgentsInfoBegin(dispatch); //show the spinner
const users = await fetchAgentsInfo(); // retrieve info
fetchAgentsInfoSuccess(dispatch, users); // show info and remove spinner
};
fetchData();
}
return (
...
)
The data fetcher file :
export const fetchAgentsInfo:any = () => {
const data = await fetch('xxxx');
return await data.json();
};
The Actions files:
export const fetchAgentsInfoBegin = (dispatch:any) => {
return dispatch({ type: 'FETCH_AGENTS_INFO_BEGIN'});
};
export const fetchAgentsInfoSuccess = (dispatch:any, users:any) => {
return dispatch({
type: 'FETCH_AGENTS_INFO_SUCCESS',
payload: users,
});
};
export const fetchAgentsInfoFailure = (dispatch:any) => {
return dispatch({
type: 'FETCH_AGENTS_INFO_FAILURE'
})
};
And my store itself :
import React, { createContext, useReducer } from 'react';
import {
ContextArgs,
ContextState,
ContextAction
} from './types';
// Reducer for updating the store based on the 'action.type'
const Reducer = (state: ContextState, action: ContextAction) => {
switch (action.type) {
case 'FETCH_AGENTS_INFO_BEGIN':
return {
...state,
isLoading:true,
};
case 'FETCH_AGENTS_INFO_SUCCESS':
return {
...state,
isLoading:false,
agentsList: action.payload,
};
case 'FETCH_AGENTS_INFO_FAILURE':
return {
...state,
isLoading:false,
agentsList: [] };
default:
return state;
}
};
const Context = createContext({} as ContextArgs);
// Initial state for the store
const initialState = {
agentsList: [],
selectedAgentId: 0,
isLoading:false,
};
export const ContextProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const value = { state, dispatch };
Context.displayName = 'Context';
return (
<Context.Provider value={value}>{children}</Context.Provider>
);
};
export default Context;
I tried to partially reuse logic from this article but the spinner is never displayed (data are properly retrieved and displayed).
Your help will be appreciated !
Thanks
I don't see anything in the code you posted that could cause the problem you describe, maybe do console.log in the reducer to see what happends.
I do have a suggestion to change the code and move logic out of the component and into the action by using a sort of thunk action and replacing magic strings with constants:
//action types
const BEGIN = 'BEGIN',
SUCCESS = 'SUCCESS';
//kind of thunk action (cannot have getState)
const getData = () => (dispatch) => {
dispatch({ type: BEGIN });
setTimeout(() => dispatch({ type: SUCCESS }), 2000);
};
const reducer = (state, { type }) => {
if (type === BEGIN) {
return { ...state, loading: true };
}
if (type === SUCCESS) {
return { ...state, loading: false };
}
return state;
};
const DataContext = React.createContext();
const DataProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, {
loading: false,
});
//redux-thunk action would receive getState but
// cannot do that because it'll change thunkDispatch
// when state changes and could cause problems when
// used in effects as a dependency
const thunkDispatch = React.useCallback(
(action) =>
typeof action === 'function'
? action(dispatch)
: action,
[]
);
return (
<DataContext.Provider
value={{ state, dispatch: thunkDispatch }}
>
{children}
</DataContext.Provider>
);
};
const App = () => {
const { state, dispatch } = React.useContext(DataContext);
return (
<div>
<button
onClick={() => dispatch(getData())}
disabled={state.loading}
>
get data
</button>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<DataProvider>
<App />
</DataProvider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
A child component has the following button code:
// SelectDonation.js
<button
onClick={(e) => {
e.preventDefault();
this.props.testThunk();
console.log(store.getState());
}}
>Test thunks</button>
this.props.testThunk() does not update the state object. I connected Redux Thunk like so:
// reducer.js
import ReduxThunk from "redux-thunk";
const starting_state = {
log_to_console : 0,
donation_amount : 12,
checkoutStep : 'selectDonation',
};
const reducer = (previous_state = starting_state, action) => {
switch (action.type) {
case 'thunkTest':
return {
...previous_state,
redux_thunk_test_var : action.payload
};
default:
return previous_state;
}
};
export default createStore(reducer, starting_state, applyMiddleware(ReduxThunk));
I expect a new state property redux_thunk_test_var to display in state but it does not onClick. I do see the state variables with initial states in the console though.
Am I not passing down the thunk correctly? Here is App.js
// App.js
{this.props.checkoutStep === checkoutSteps.selectDonation &&
<SelectDonation
dispatch_set_donation_amount = {this.props.dispatch_set_donation_amount}
dispatchChangeCheckoutStep={this.props.dispatchChangeCheckoutStep}
{...this.props}
/>
}
</Modal>
</header>
</div>
);
}
}
const map_state_to_props = (state) => {
return {
log_prop : state.log_to_console,
donation_amount : state.donation_amount,
checkoutStep : state.checkoutStep,
}
};
const map_dispatch_to_props = (dispatch, own_props) => {
return {
dispatch_set_donation_amount : amount => dispatch(set_donation_amount(amount)),
dispatchChangeCheckoutStep : newStep => dispatch(changeCheckoutStep(newStep)),
dispatchUpdateStateData : (stateData, stateVariable) => (dispatch(updateStateData(stateData, stateVariable))),
testThunk
}
};
The action thunk:
// actions.js
export const testThunk = () => {
const testDelay = setTimeout(() => 'Set Timeout done', 2000);
return (dispatch) => {
testDelay.then((data) => dispatch({
type: 'thunkTest',
payload: data })
)
}
};
You need to dispatch the result of the testThunk() action creator. Right now, you're just returning it, and not calling dispatch(testThunk()).
See this gist comparing syntaxes for dispatching to help understand the issue better.
The best way to fix this is to use the "object shorthand" form of mapDispatch. As part of that, I suggest changing the prop names to remove the word "dispatch", which lets you use the simpler ES6 object literal syntax:
const map_dispatch_to_props = {
set_donation_amount,
changeCheckoutStep,
updateStateData,
testThunk,
};
conponentDidMount() {
this.props.testThunk();
}
const map_dispatch_props = {
testThunk
}
//action creator
const fetch = (data) => ({
type: 'thunkTest',
payload: data
})
const fakeFetch = () => new Promise((resolve, reject) => setTimeout(() => resolve('Set Timeout done'), 2000));
export const testThunk = () => (dispatch) => fakeFetch.then(data => dispatch(fetch(data)))