undefined data from my action creator ? React/redux/thunk - reactjs

I'm trying to make an async action using Redux-thunk. I'm still don't really understanding how Async call works with Redux-Thunk but I'm beginning to get a few things. I know that :
I need an Action-Creator
Some actions related to my Action-creator (something like "I'm calling the data", "I'm waiting for then", "Got it", "Oups there is an error")
Not sure about this but I need a Reducer to handle the action dispatched by my Action-Creator.
To start slowly I just want to pass some data into my redux-store-state. So I can check it through the react dev tool.
My Action-Creator and my Actions :
function loadData(dispatch) {
return axios.get(`http://localhost:3000/authors`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
);
}
export function loadData(data) {
return {
type: LOAD_DATA_SUCCESS,
data
}
}
export function failData(data) {
return {
type: LOAD_DATA_FAILURE,
err
}
}
To make sure that my local URL is sending the data correctly with axios I've also done this :
export function fetchData() {
axios.get('http://localhost:3000/authors')
.then(function (response) {
console.log(response);
})
}
My reducer :
const ThunkData = (state = {}, action) => {
switch (action.type) {
case 'LOAD_DATA_SUCCESS':
return Object.assign({}, state, {
data: action.data,
});
case 'LOAD_DATA_FAILURE':
return action.err
default:
return state;
}
};
My client.js where I'm dispatching my Action-Creator with store.dispatch(loadData()); :
const loggerMiddleware = createLogger();
function configureStore(preloadedState) {
return createStore(
todoApp,
preloadedState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
), window.devToolsExtension ? window.devToolsExtension() : f => f
)
}
const store = configureStore()
store.dispatch(loadData());
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('test')
);
When I launch my app I have a ThunkData object in my store with a data object but undefined. I'm guessing there is something wrong on how my action creator is passing the data and how my reducer is receiving it.
Thanks.

export function loadData(data) {
return {
type: LOAD_DATA_SUCCESS,
data
}
}
export function failData(data) {
return {
type: LOAD_DATA_FAILURE,
err
}
}
These two functions are redundant in your action file. Remove them and add an export to the first loadData function. Rest of the code is fine.

Related

Actions not dispatching

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.

redux/redux-observable error: Actions must be plain objects

I'm using redux with redux-observable and get this strange error:
Actions must be plain objects. Use custom middleware for async >actions.
/* Component.jsx */
import React from "react"
import { serialNumberCheck } from '../actions'
const Component = props => {
...
<button
onClick={() => props.serialNumberCheck('123456789123456')}
>
Check
</button>
...
}
const mapDispatchToProps = dispatch =>
bindActionCreators({serialNumberCheck}, dispatch)
export default compose(
reduxForm({
...
}),
withStyles(styles),
connect(mapDispatchToProps)
)(Component)
/* actions.js */
export const SERIAL_NUMBER_CHECK = 'SERIAL_NUMBER_CHECK'
export const SERIAL_NUMBER_CHECK_SUCCESS = 'SERIAL_NUMBER_CHECK_SUCCESS'
export const serialNumberCheck = (serialNumber) => ({
type: SERIAL_NUMBER_CHECK,
payload: serialNumber
})
export const serialNumberCheckSuccess = (data) => ({
type: SERIAL_NUMBER_CHECK,
payload: data
})
/* epics.js */
...
import { serialNumberCheck } from "../actions"
import ... from 'rxjs'
...
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
serialNumberCheckSuccess({success: true});
}
})
}
...
export const rootEpic = combineEpics(
...
serialNumberCheckEpic
);
/* reducer.js */
import {
SERIAL_NUMBER_CHECK_SUCCESS,
} from '../actions'
...
export default function epicReducer(state = initialState, action) {
switch (action.type) {
case SERIAL_NUMBER_CHECK_SUCCESS:
return {
...state,
success: action.payload
}
}
}
/* JSON-SERVER RESPONSE */
[
{
"id": 1,
"sn": "123456789123456"
}
]
Inside component i'am calling function serialNumberCheck() and passing inside sierial number that we need to check.
Inside Epic im passing serial number to json-server that checks if this number exists in my "database". If serial number exists, server response is .json containing some parameters.
So if response isn't empty we need to write success: true inside redux store.
But in the end we get successfull GET request, and then error: Actions must be plain objects. Use custom middleware for async actions., but no changes inside redux-store and nothing from SERIAL_NUMBER_CHECK_SUCCESS action.
Finally, I found the solution. I've just missed the return before calling action inside my epic.
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
+ return serialNumberCheckSuccess({success: true});
}
})
}

Why to do I get error: Actions must be plain objects. Use custom middleware for async actions.?

I am trying to get redux working with my application, but there is the problem with me getting error: Actions must be plain objects. Use custom middleware for async actions.
I am new to redux, as I saw the problem is usually because people did not use applyMiddlewear, but I did it and do not understand why my code keeps getting this mistake.
My action that is probably getting mistake:
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('APICALL(here is url actually)', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
console.log(this.props.items)
}
My combine reducers file:
export default combineReducers({
word,
wordsAreFetching,
wordsFetchHasErrored
});
My store creation:
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
How I call:
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (parse) => dispatch(wordsFetchData(parse))
};
};
componentDidMount = () => {
this.props.fetchData(this.state.filterArray);
}
You have mess in your code.
Redux has following main parts:
Actions which should be plain objects having type property. Actions can be created using action creators. Action creators just return action (plain objects). Or you can use bound action creators which also dispatches, like wordsFetchData.
As of your code following functions should be acion creators:
wordsAreFetching(bool)
wordsFetchData
wordsFetchDataSuccess
wordsHasErrored
Reducers accepts state and action and return state. I don't see any reducer in your code.
This call is incorrect
export default combineReducers({
word,
wordsAreFetching,
wordsFetchHasErrored
});
All above function are action creators and shouldn't be placed in combineReducers call.
mapDispatchToProps should look like
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (parse) => wordsFetchData(parse)(dispatch)
};
};
As wordsFetchData returns fucntion which takes dispatch as argument.

Where to store socket connection in react-redux?

Tried two ways:
call connectToServer() from action creator within Starter component in componentDidMount(); and dispatch like this:
let socket = new Socket('ws://address/socket');
socket.connect();
dispatch({
type: Constants.SESSION_SAVE_SOCKET,
socket: socket,
});
const lobbyChannel = socket.channel('lobby');
lobbyChannel.join()
.receive('ok', () => {
dispatch({
type: Constants.SESSION_LOBBYCHANNEL_RECEIVE_OK,
});
dispatch({
type: Constants.SESSION_SAVE_LOBBYCHANNEL,
lobbyChannel: lobbyChannel,
});
}).receive('error', (payload) => {
dispatch({
type: Constants.SESSION_LOBBYCHANNEL_RECEIVE_ERROR,
});
});
Next I receive state by redux's mapStateToProps connect.
The result is component is called four times and props are empty at the result.
put all logic into the reducer, but the result is: component is rendered with empty props (undefined properties) and moment after I see in console logs that connection is established, but component is already rendered.
How to deal with such a issue? Thanks for any suggestions.
The way I found that works is to setup your own middleware for the socket like so.
import {createStore, applyMiddleware} from 'redux';
import startWs, {wsMiddleware} from './ws.api';
function handleData(state = {data1: {}}, action) {
switch (action.type) {
case 'ApiGotData': return Object.assign({}, state, {data1: action.data});
default: return state;
}
}
const store = createStore(handleData, applyMiddleware(wsMiddleware));
startWs(store);
export default store;
import * as Actions from './Actions';
var socket = null;
const newData = {
'React version': '15',
'Project': 'Redux with socket.io',
'currentDateTime': new Date().toLocaleString()
};
export function wsMiddleware() {
return (next) => (action) => {
if (socket && action.type === 'ApiGetData') {
console.log('ApiGetData');
socket.emit('client:GetData', {});
} else if (socket && action.type === 'ApiSetData') {
console.log('ApiSetData');
socket.emit('client:SetData', action.data);
}
return next(action);
};
}
export default function (store) {
socket = new io();
socket.on('server:GetDataDone', (data) => {
console.log('GetDataDone');
store.dispatch(Actions.apiGotData(data));
});
socket.on('server:SetDataDone', () => {
console.log('SetDataDone');
store.dispatch(Actions.apiGetData());
});
store.dispatch(Actions.apiSetData(newData));
}
The project example is ReduxSocketIO at https://github.com/jmarkstevens/ReactPatterns.

how to call ajax in react using `redux-thunk`?

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
}
}

Resources