When I run an action creator to create my thunk, it does not run the dispatch functions.
For testing, I put a button in my root component's render method, my root component is connected thusly (removed extraneous things):
import { loadAndSetCurrentUser } from '../Actions/SedFF';
import { setSysMenuExpand, setNavMenuExpand, setLoginDialogVisibility } from '../Actions/UI';
render() {
return (
<button onClick={() =>
loadAndSetCurrentUser("username#email.com")}>LASCU</button>
)
}
const mapStateToProps = function (state) {
return {
UI: state.UI,
sedff: state.SedFF,
}
}
const mapDispatchToProps = {
loadAndSetCurrentUser,
}
export default withStyles(styles, { withTheme: true })(withRouter(connect(mapStateToProps, mapDispatchToProps)(WebFF)));
My Actions Creators file looks like this:
export function loadAndSetCurrentUser(username) {
console.log("LASCU: " + username);
return (dispatch, getState) => {
console.log("in dispatch");
dispatch(this.requestingUserData(username));
const user = getState().Users[username];
if (user) {
// if already in store, just change the current username
//FUTURE: check date against user mod date in database
dispatch(setCurrentUsername(username));
dispatch(userDataLoadComplete());
} else {
// user not in store, need to check database //TODO:
fetch('https://jsonplaceholder.typicode.com/users?username=Bret')
.then(response => response.json())
// .then(json => delay(3000, json))
.then(json=> dispatch(ingestUserData(json)))
.then(() => dispatch(userDataLoadComplete()));
}
}
}
export function requestingUserData(username) {
console.log("RUD");
return { type: REQUESTING_USER_DATA, username };
}
export function setCurrentUsername(username) {
return { type: SET_CURRENT_USERNAME, username }
}
export function ingestUserData(userData) {
return (dispatch) =>
{
console.log("IUD");
console.log(userData);
dispatch({ type: SET_USER_DATA, userData })
}
}
export function userDataLoadComplete() {
return { type: USER_DATA_LOAD_COMPLETE };
}
And my reducers are bone-stock, looking like this:
export function SedFF(state = initialSedFFState, action) {
let newState = _.cloneDeep(state);
switch (action.type) {
case SET_CURRENT_USERNAME:
newState.currentUsername = action.username
return newState;
case LOAD_USER_DATA:
//TODO:
return newState;
case REQUESTING_USER_DATA:
newState.isFetchingUserData = true;
return newState;
case RECEIVED_USER_DATA:
//TODO:
newState.isFetchingUserData = false;
return newState
case USER_DATA_LOAD_COMPLETE:
//TODO:
newState.isFetchingUserData = false;
return newState
... etc, etc...
default:
return state
}
}
When I hit the LASCU button, I get the following output: LASCU: username#email.com coming from my action creator (noted on the second line of the action creator file above). I do NOT get the in dispatch output on the 4th line of my action creator file. I do NOT get any actions firing.
I'm unsure why the dispatch is not firing.
I will note that on a different component (a sub-component), I can get it to fire the entire thing, but I'm unable to find any differences. The only real difference appear to be that I don't have the router in the export line:
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(SystemMenu));
For what it's worth, my thunk middleware is connected at the App.js (parent of my root component) like so:
const store = createStore(
RootReducer,
initialState,
composeEnhancer(applyMiddleware(thunk))
);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<CssBaseline />
<BrowserRouter>
<WebFF />
</BrowserRouter>
</Provider>
);
}
}
export default withStyles(styles, { withTheme: true })(App);
Thoughts? Help?
Looking at the render method given in the question, the issue appears to be that you're calling the original imported function, not the bound function from props. In other words, changing to this.props.loadAndSetCurrentUser() should work correctly. Note that you shouldn't be importing the store directly into a component file.
For more details, please see the React-Redux usage guide docs page on "Dispatching Actions with mapDispatch".
You still have to dispatch the Thunk action, otherwise it will just return the action and not do anything with it.
render() {
return (
<button onClick={() =>
store.dispatch(loadAndSetCurrentUser("username#email.com"))
}>LASCU</button>
)
}
So, basically what you were doing was similar to:
const action = function (username) { return function () { alert(username); }}
console.log(action('123')); // This will just log `function () { alert(username); }`, without actually running the alert()
So, even though you call action('123'), it will just return a function that still has to be called somehow. In the case of Thunks, the returned function has to be dispatched.
Related
I'm having some difficulty with React Redux. It's related to components not re-rendering after a state change. Every question that is asked online refers to it probably being that you are mutating the state, however, I am almost 100% sure that I am not making that mistake. After having tried multiple approaches I just don't know what is going wrong.
Here is my original reducer code:
import * as actionTypes from '../actions/actionTypes';
import { updateObject } from '../utility';
const initialState = {
jwsToken: null,
accessToken: null,
};
const updateTokens = (state, action) => {
return updateObject(state, {jwsToken: action.jwsToken, accessToken: action.accessToken})
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_TOKENS: return updateTokens(state, action);
default:
return state;
};
};
export default reducer
I'm using a utility function (updateObject) to make a copy of my object that I want to return in the reducer. It looks like this:
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
I also tried it without the updateObject utility function and using Object.assign():
const updateTokens = (state, action) => {
return Object.assign({}, state, {
jwsToken: action.jwsToken,
accessToken: action.accessToken,
})
};
I fear that I'm doing something super stupid, but I've spent too much time on this now not to ask. Can someone please tell me what I'm doing wrong?
Edit
Here is my component:
import React, { Component } from "react";
import * as actions from "../../store/actions/index";
import { connect } from "react-redux";
class Calendar extends Component {
componentDidMount () {
if (this.props.accessToken) {
this.onGetEvents()
}
}
onGetEvents = () => {
this.props.getEventsSelectedMonth(this.props.selectedMonth,
this.props.accessToken)
}
render() {
return (
//JSX here
)
}
}
const mapStateToProps = state => {
return {
accessToken: state.accessToken,
selectedMonth: state.selectedMonth
};
};
const mapDispatchToProps = dispatch => {
return {
getEventsSelectedMonth: (selectedMonth, accessToken) =>
dispatch(actions.getEventsSelectedMonth(selectedMonth, accessToken))
};
};
export default connect(mapStateToProps, mapDispatchToProps) (Calendar);
To avoid the infinite loop you can make sure to call the required function only when the value is changed:
componentDidUpdate(prevProps, prevState) {
if (prevProps.accesstoken !== this.props.accessToken) {
this.onGetEvents()
}
}
The infinite loop happens as without the check we would continuously change the state on every update(which occurs because of the state change).
From your comments, I'm assuming you're getting different access tokens every time so maybe you just want to call onGetEvents when you get an accessToken for the first time.
componentDidUpdate(prevProps, prevState) {
// only call when the previous token is falsy and there's a new truthy token
if (!prevProps.accesstoken && this.props.accessToken) {
this.onGetEvents()
}
}
I'm wanting to update my trending array with the results calling the tmdb api. I'm not sure if im going about this the wrong way with calling the api or if im messing up somewhere else along the way. So far I've really been going in circles with what ive tried. Repeating the same things and not coming to a real solution. Havent been able to find another question similar to mine.
my actions
export const getTrending = url => dispatch => {
console.log("trending action");
axios.get(url).then(res =>
dispatch({
type: "TRENDING",
payload: res.data
})
);
};
my reducer
const INITIAL_STATE = {
results: [],
trending: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "SEARCH_INFO":
return {
results: [action.payload]
};
case "TRENDING":
return { trending: action.payload };
default:
return state;
}
};
and my component im trying to get the results from
import React, { Component } from "react";
import Trending from "./Treding";
import "../App.css";
import { getTrending } from "../actions/index";
import { connect } from "react-redux";
export class Sidebar extends Component {
componentDidMount = () => {
const proxy = `https://cors-anywhere.herokuapp.com/`;
getTrending(`${proxy}https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
render() {
return (
<div>
<h3 className="trending">Trending</h3>
{
this.props.trending ? (
<Trending movies={this.props.trending} />
) : (
<div>Loading</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
trending: state.trending
};
};
export default connect(mapStateToProps)(Sidebar);
Since you are directly calling the getTrending without passing it to connect method, it might be the issue.
Instead that you can pass getTrending to connect method so it can be available as props in the component. After that it can be dispatched and it will be handled by redux/ redux-thunk.
export default connect(mapStateToProps, { getTrending })(Sidebar);
And access it as props in the component.
componentDidMount = () => {
// const proxy = `https://cors-anywhere.herokuapp.com/`;
this.props.getTrending(`https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
I have an action creator that I'm calling in componentWillMount, the return of that action payload is being assigned to state using setState. However, in componentDidMount I cannot access that property as the async call hasn't completed yet. What is the correct way to access this data in compoentDidMount?
//component
class Dashboard extends Component {
componentWillMount() {
this.setState(this.props.getUser());
}
componentDidMount() {
// this.state.user isn't available yet
}
render(){
return(...);
}
}
//action
export function getUser() {
return async function (dispatch) {
const user = await axios.get(`${API_URL}user?token=${token}`);
return dispatch({
type: USER,
payload: user,
});
}
};
}
Axios returns a promise and you have to wait until it resolves. Then dispatch the success action like this,
export function getUser() {
return function (dispatch) {
axios.get(`${API_URL}user?token=${token}`)
.then(user => {
return dispatch(getUserSuccess(user));
}).catch(error => {
throw error;
});
}
};
export function getUserSuccess(user) {
return {type: USER, payload: user};
}
Also note that you need to have mapStateToProps so it brings the user to your component. Then you can access it using this.props.user within your component. It should be like this.
UserPage.propTypes = {
user: PropTypes.object.isRequired
};
function mapStateToProps(state, ownProps) {
return {
user: state.user
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({getUser}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserPage);
Finally you may access the user like this.
render() {
const {user} = this.props;
return(
<div>
<div>user.name</div>
</div>
);
}
You need to use componentWillReceiveProps to do that, for example:
componentWillReceiveProps(nextProps) {
if (nextProps.user !== this.state.user) {
this.setState({
user: nextProps.user
});
}
}
now you can use user inside your component.
Here you can find more information.
When I try to dispatch an action from inside an ajax call, while the store itself gets updated (Checked in the browser redux plugin), the component doesn't receive the event, and therefore doesn't re-render.
class SomeComponent extends React.Component{
constuctor (props) {
super(props);
this.someAjaxCall();
}
someAjaxCall(){
this.props.dispatch(someAction('Work'));
$.ajax({
type: "POST",
url: "someURL",
success: function(data)
{
this.props.dispatch(someAction("Does Not Work"));
}.bind(this)
});
}
render(){
return (<div></div>);
}
}
const mapStateToProps = (store, ownprops) => {
return {
store: store.get("Some_Key")
}
}
const render = (store,container) => {
let ConnectedComponent = ReactRedux.connect(this.mapStateToProps)(SomeComponent);
ReactDOM.render(
<Provider store={ store }>
<ConnectedComponent/>
</Provider>
, container);
}
const someAction = (state) => {
return {
type: 'SOME_CASE',
state: state
}
}
const reducer = (state, action) => {
switch (action.type) {
case 'SOME_CASE':{
return state.set("stateKey",action.state);
}
default: {
return state;
}
}
}
From the above sample, when I call this.props.dispatch(someAction('Work'));
The store gets updated, the component gets the event and re-renders itself. However, when I call from inside the 'success' function of an ajax call, the store gets updated, but the component never receives the event.
If I am wrong, please correct me.
The second Action creator call should not be failed as someAction is not global. Did you get any errors.
I am trying call ajax in react using redux-thunk and axios .I want to get data from json file
simple way (on button click call like this)
axios.get('data.json').then((data)=>{
console.log(data);
})
But I want to use redux-thunk.In other words I need subscribe in component which will be dispatch using thunk
can we use thunk here ??
here is my code
https://plnkr.co/edit/bcGI7cHjWVtlaMBil3kj?p=preview
const thunk = ReduxThunk.default;
const abc= (state={},action) => {
console.log('in redux', action.type)
switch(action.type){
case 'GET_DATA':
return dispatch =>{
return axios.get('data.json');
};
default :
return state;
}
}
const {createStore,bindActionCreators ,applyMiddleware } =Redux;
const {Provider,connect} =ReactRedux;
const store = createStore(abc,
applyMiddleware(thunk)
);
class First extends React.Component {
constructor (props){
super(props);
}
getDate(){
this.props.getData();
// axios.get('data.json').then((data)=>{
// console.log(data);
// })
}
render(){
return (
<div>
<button onClick={this.getDate.bind(this)}>GET DATA</button>
</div>
)
}
}
const actions = {
getData: () => {
return {
type: 'GET_DATA',
}
}
};
const AppContainer = connect(
function mapStateToProps(state) {
return {
digit: state
};
},
function mapDispatchToProps(dispatch) {
return bindActionCreators(actions, dispatch);
}
)(First);
ReactDOM.render(
<Provider store={store}>
<AppContainer/>
</Provider>
,document.getElementById('root'))
There is a good tutorial on egghead.io about this ... you might want to check it out.
https://egghead.io/lessons/javascript-redux-dispatching-actions-asynchronously-with-thunks
I have used axios as example here for calling apis, you can use fetch or superagent also.You can try something like.
AXIOS
axios.get('//url')
.then(function (response) {
//dispatch action
})
.catch(function (error) {
// throw error
});
So that was for the API call, now coming to the state. In redux there is one state which handles your app. I would suggest you should go through redux basics which you can find here . So once your api call succeeds you need to update your state with the data.
Action to fetch data
function fetchData(){
return(dispatch,getState) =>{ //using redux-thunk here... do check it out
const url = '//you url'
fetch(url)
.then (response ) => {dispatch(receiveData(response.data)} //data being your api response object/array
.catch( error) => {//throw error}
}
}
Action to update state
function receiveData(data) {
return{
type: 'RECEIVE_DATA',
data
}
}
Reducer
function app(state = {},action) {
switch(action.types){
case 'RECEIVE_DATA':
Object.assign({},...state,{
action.data
}
})
default:
return state
}
}