I used this guide to set up authentication with my api using jwt token authentication from Django rest framework. I can log in just fine. But currently I set up another endpoint to get the user info of the currently authorized user (determined by the token they send with the request).
It's dispatched like so (done some time after the user logs in)
fetchUser: () => dispatch(loadUser()),
My action to load username:
import { RSAA } from 'redux-api-middleware';
import withAuth from '../reducers'
export const USER_REQUEST = '##user/USER_REQUEST';
export const USER_SUCCESS = '##user/USER_SUCCESS';
export const USER_FAILURE = '##user/USER_FAILURE';
export const loadUser = () => ({
[RSAA]: {
endpoint: '/api/user/info/',
method: 'GET',
headers: withAuth({}),
types: [
USER_REQUEST, USER_SUCCESS, USER_FAILURE
]
}
});
Reducer:
import jwtDecode from 'jwt-decode'
import * as user from '../actions/user'
const initialState = {};
export default (state=initialState, action) => {
switch(action.type) {
case user.USER_SUCCESS:
return action.payload;
default:
return state
}
}
export const userInfo = (state) => state.user;
Reducer index.js:
export function withAuth(headers={}) {
return (state) => ({
...headers,
'Authorization': `Bearer ${accessToken(state)}`
})
}
export const userInfo = state => fromUser.userInfo(state.user);
But when I try to get the user info of the logged in user, I get an error..
TypeError: Cannot read property 'type' of undefined
Why is the action type undefined?
Note: I tagged this with thunk because my project does use thunk, but not for this bit of redux.
Edit: My middleware.js and my store.js.
The issue is that in store.js that thunk is applied before the redux-api-middleware middleware.
Just move it to after like this:
const store = createStore(
reducer, {},
compose(
applyMiddleware(apiMiddleware, thunk, routerMiddleware(history)),
window.devToolsExtension ? window.devToolsExtension() : f => f,
),
);
Related
So I have a movie app, and I have a page for a single movie. I have a section on that page where I display all of the videos from an API related to a certain movie.
So my Videos component looks like this:
const Videos = ({videos} :{videos:IVideos | null}) => {
return (
<div>{videos?.results.map((video, i) =>
<div key={i}>{video.name}</div>
)}</div>
)
}
It's just a basic component which gets props from a higher component. But the main thing is redux slice, which looks like this:
Initial state:
const initialState: IMovieVideosState = {
movieVideos: null,
fetchStatus: null,
}
export interface IMovieVideosState {
movieVideos: IVideos | null;
fetchStatus: FetchStatus | null;
}
And finally slice:
const videosSlice = createSlice({
name:'videos',
initialState,
reducers:{},
extraReducers(builder) {
builder
.addCase(fetchVideos.pending, (state, action) => {
state.fetchStatus = FetchStatus.PENDING
})
.addCase(fetchVideos.fulfilled, (state, action) => {
state.fetchStatus = FetchStatus.SUCCESS
state.movieVideos = action.payload
})
.addCase(fetchVideos.rejected, (state, action) => {
state.fetchStatus = FetchStatus.FAILURE
//state.error = action.error.message
})
}
})
As you see, these are basic reducers, where if promise is successful I assign payload to an existing array.
And also thunk function:
export const fetchVideos = createAsyncThunk('videos/fetchVideos', async (id: number) => {
const response = await axios.get<IVideos>(`${API_BASE}movie/${id}/videos?api_key=${TMDB_API_KEY}`);
console.log(response.data);
return response.data;
})
But in the browser I have the next error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
And also another one:
A non-serializable value was detected in an action, in the path: `<root>`. Value:
Promise { <state>: "pending" }
Take a look at the logic that dispatched this action:
Promise { <state>: "pending" }
I have no idea why I could have these errors, because my reducer is the same as another one in my project, but this one doesn't work for some reason.
UseEffect for dispatching all reducers:
useEffect(() =>{
dispatch(fetchDetail(Number(id)));
dispatch(fetchCredits(Number(id)));
dispatch(fetchPhotos(Number(id)));
dispatch(fetchRecommended(Number(id)));
dispatch(fetchSimilar(Number(id)));
dispatch(fetchVideos(Number(id))); //dispatching fetchVideos()
}, [dispatch, id])
So in my case, all of the other functions work fine besides fetchVideos().
Another example of a thunk for movie details:
export const fetchDetail = createAsyncThunk('detail/fetchDetail', async (id: number) => {
const response = await axios.get<IMovie>(`${API_BASE}movie/${id}?api_key=${TMDB_API_KEY}`);
console.log(response.data);
return response.data;
})
My store file:
import thunk from "redux-thunk";
export const store = configureStore({
reducer: {
popular,
top_rated,
playing,
upcoming,
detail,
credits,
videos,
photos,
recommended,
similar
},
middleware: [thunk]
})
export type RootState = ReturnType<typeof store.getState>;
instead of using create Async Thunk method add think malware where you create store of videos then you can pass Async actions into it without nothing.
import { applyMiddleware, combineReducers, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
// import your videos reducer here from file
export interface State {
videos: IVideos;
}
const rootReducer = combineReducers<State>({
videos: VideosReducer,
});
export const rootStore = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
This is the first time I am working with redux saga. I have a backend route called https://localhost:5000/developers/signup. I have a signup form:
import { FormEvent, useState } from 'react';
import HttpService from 'services/Http';
export default function Signup() {
const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', password: '' });
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
try {
const httpService = new HttpService('api/developers/signup');
const res = await httpService.create(formData);
// I receive the user data + JWT token
console.log(res);
} catch (err) {
console.log(err);
}
};
return (
<main>
<h1>Signup</h1>
<form onSubmit={handleSubmit}>
// Some JSX to show the form
</form>
</main>
);
}
Store:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import authReducer from './ducks/auth';
import rootSaga from './sagas/root';
const reducers = combineReducers({
auth: authReducer,
});
const sagas = createSagaMiddleware();
const composeSetup =
/*#ts-ignore eslint-disable */
process.env.NODE_ENV !== 'production' && typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? /*#ts-ignore eslint-disable */
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
/*eslint-enable */
const middleWare = [sagas];
const store = createStore(reducers, composeSetup(applyMiddleware(...middleWare)));
sagas.run(rootSaga);
export default store;
I am unable to understand what redux saga does, It would be great if someone could explain this. I've seen a lot of posts and youtube video. I looked at the docs but then it did not have a basic AJAX example. I would like to have a redux state structure like this:
{
auth: {
// Some auth data like the token & user details
}
}
Also, I am using functional components, so It would be great if your solution is compatible with that. Looking forward to talking, thanks in advance!
you should create action to send data to the server
and use the action in your function
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
dispatch(sendDataForSignup(e))
};
On submit fire an action with form data
import { signUp } from "actions";
const handleSubmit = async (e) => {
e.preventDefault();
signUp(formData);
};
which will forward the type and payload to watcher
export const signUp = (payload) => ({
type: "SIGNUP_REQUEST",
payload
});
the watcher will take the latest action and pass it to worker function that will make the api call and dispatch an action with response type and data
import { put, takeLatest, all } from "redux-saga/effects";
function* signupWorker() {
const json = yield fetch("URL/signup").then((response) => response.json());
yield put({ type: "SIGNUP_SUCCESS", json: json.data });
}
function* signupWatcher() {
yield takeLatest("SIGNUP_REQUEST", signupWorker);
}
export default function* rootSaga() {
yield all([signupWatcher()]);
}
and the reducer will handle the data as you like based on the types
const reducer = (state = {}, action) => {
switch (action.type) {
case "SIGNUP_REQUEST":
return { ...state, loading: true };
case "SIGNUP_SUCCESS":
return { ...state, ...action.payload, loading: false };
default:
return state;
}
};
end developer and recently I started to learn front-end. I have troubles with adding some new data to redux store. I am working with dandelion-pro react template and can't figure out how to add my reducers to their stores, it seems much more complex then redux stores I have build for other projects, also I observed they used redux saga. I am trying to introduce a global state for user data on login.
Here is code for my reducer
import { CallToAction } from '#material-ui/icons';
import { SUCCESSFUL_LOGIN, FETCH_LOGIN, ERROR_LOGIN } from '../../actions/actionConstants';
const initialState = {
auth: false,
isLoading: false,
errMess: null,
isAdmin: false,
token: ''
}
export default function userReducer (state = initialState, action) {
console.log("Action: ")
console.log(action)
switch (action.type) {
case SUCCESSFUL_LOGIN: return {
...state,
auth: true,
isLoading: false,
errMess: null,
isAdmin: action.payload.isAdmin,
token: action.payload.token
}
case FETCH_LOGIN: return {
...state,
auth: false,
isLoading: true,
errMess: null
}
case ERROR_LOGIN: return {
...state,
auth: false,
isLoading: false,
errMess: action.payload
}
default: return state
}
}
Code for fetch user data
import { SUCCESSFUL_LOGIN, FETCH_LOGIN, ERROR_LOGIN } from '../../actions/actionConstants';
import axios from 'axios';
import { server } from '../../config'
export const fetchUser = (username, password) => (dispatch) => {
console.log("a ajuns")
dispatch(loginLoading(true));
axios.post(`${server + "/auth/login"}`, { username, password })
.then(res => {
const user = res.data;
console.log(user);
if (user.status) {
window.location.href = '/app';
return dispatch(loginUser(user));
}
else {
var errmess = new Error("False Status of User");
throw errmess;
}
})
.catch(error => dispatch(loginFailed(error.message)))
}
export const loginLoading = () => ({
type: FETCH_LOGIN
});
export const loginFailed = (errmess) => {
return ({
type: ERROR_LOGIN,
payload: errmess
})
};
export const loginUser = (user) => ({
type: SUCCESSFUL_LOGIN,
payload: user
})
Section that combine reducers
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { reducer as form } from 'redux-form/immutable';
import { combineReducers } from 'redux-immutable';
import { connectRouter } from 'connected-react-router/immutable';
import history from 'utils/history';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import login from './modules/login';
import uiReducer from './modules/ui';
import initval from './modules/initForm';
import user from '../my_redux/modules/initForm';
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers = {}) {
const rootReducer = combineReducers({
user,
form,
login,
ui: uiReducer,
initval,
language: languageProviderReducer,
router: connectRouter(history),
...injectedReducers,
});
// Wrap the root reducer and return a new root reducer with router state
const mergeWithRouterState = connectRouter(history);
return mergeWithRouterState(rootReducer);
}
I try to connect my Login component like this
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = dispatch => ({
fetchUser: (username, password) => dispatch(fetchUser(username, password))
});
// const mapDispatchToProps = dispatch => ({
// actions: bindActionCreators(userActions, dispatch),
// });
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Login));
The store is created here
/**
* Create the store with dynamic reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import { fromJS } from 'immutable';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
export default function configureStore(initialState = {}, history) {
let composeEnhancers = compose;
const reduxSagaMonitorOptions = {};
// If Redux Dev Tools and Saga Dev Tools Extensions are installed, enable them
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production' && typeof window === 'object') {
/* eslint-disable no-underscore-dangle */
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({});
// NOTE: Uncomment the code below to restore support for Redux Saga
// Dev Tools once it supports redux-saga version 1.x.x
// if (window.__SAGA_MONITOR_EXTENSION__)
// reduxSagaMonitorOptions = {
// sagaMonitor: window.__SAGA_MONITOR_EXTENSION__,
// };
/* eslint-enable */
}
const sagaMiddleware = createSagaMiddleware(reduxSagaMonitorOptions);
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [sagaMiddleware, routerMiddleware(history)];
const enhancers = [applyMiddleware(...middlewares)];
const store = createStore(
createReducer(),
fromJS(initialState),
composeEnhancers(...enhancers),
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.injectedReducers = {}; // Reducer registry
store.injectedSagas = {}; // Saga registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
store.replaceReducer(createReducer(store.injectedReducers));
});
}
return store;
}
on login form submit I call this.props.fetchUser("admin", "admin"); but I get the following error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
at dispatch (redux.js:198)
at eval (middleware.js:29)
at eval (redux-saga-core.dev.cjs.js:1412)
at Object.fetchUser (Login.js?f3c5:66)
at Login.submitForm (Login.js?f3c5:30)
at onSubmit (Login.js?f3c5:49)
at executeSubmit (handleSubmit.js?e3b3:39)
at handleSubmit (handleSubmit.js?e3b3:131)
at Form._this.submit (createReduxForm.js?d100:362)
at HTMLUnknownElement.callCallback (react-dom.development.js:149)
I reviewed my answer, and update it according to your question update
The syntax you use for defining async function is called a thunk a fancy name for a function that return a promise (or async function), anyway to use that pattern in code you need a library called redux-thunk
To apply the redux-thunk middle ware for your application,
npm install redux-thunk
then apply the middleware in your app store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(rootReducer, applyMiddleware(thunk));
example from official repo of redux-thunk
and for your code just add the thunk imported from redux-thunk in middleware array
import thunk from 'redux-thunk';
const middlewares = [sagaMiddleware, routerMiddleware(history), thunk];
Now for Saga
you need to have a root saga that run others sagas, and run the root saga from the created saga middleware
here're the steps:
1- create saga middleware(just like how you did, but we need to run the root saga from there too)
import createSagaMiddleware from 'redux-saga'
const sagaMiddleware = createSagaMiddleware();
// after you've created the store then run the root saga
sagaMiddleware.run(rootSagas);
2- create your rootSaga
export function* rootSagas() {
try {
yield fork(fetchUsersSaga);
} catch (error) {
console.warn(error);
}
}
3- create your fetch user saga
import { take, put, call } from "redux-saga/effects";
export function* fetchUsersSaga() {
while (true) {
const action: FetchUser = yield take(FETCH_USER);
try {
const response = yield call(usersService.fetchUsersOfProject, { ...paramsPassedToFetchUserFunction })
if (response) {
const { data: { response: { user } } } = response;
yield put(setUser({ user }));
}
} catch (error) {
yield put(fetchUser());
}
}
}
now you need to notice the big difference between saga and thunk, for thunk you write an action that is hard coded to do one thing(or multiple but it still for a more specific case) and in saga you listen for what ever action the store has dispatched and react to that action in generator code style
I saw this question has been asked a lot. But I failed to solve my issue. Forgive me for my incompetence. Trying redux first time. I am trying to do a login, logout operation.
Store configuration:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(applyMiddleware(...middleware)),
);
export default store;
Reducer Index:
import { combineReducers } from 'redux';
import loginReducer from './loginReducer';
export default combineReducers({
loginData: loginReducer,
});
Login Reducer:
import { ERROR_LOG, LOGIN, LOGOUT } from '#/actions/types';
const initialState = {
token: null,
data: null,
error: null,
};
export default (state = initialState, action) => {
switch (action.type) {
case LOGIN:
return {
...state,
data: action.payload,
token:action.payload.token
};
case LOGOUT:
console.log('logout',action)
return{
...state,
data:null,
}
case ERROR_LOG:
return { ...state, error: action.payload };
default:
return state;
}
};
Action:
import axios from 'axios';
import { ERROR_LOG, LOGIN, LOGOUT } from './types';
import common from '../models/common';
export const loginAction = (data) => async (dispatch) => {
try {
const res = await axios.post(`${common.api}/sign-in`, data);
const loginData = await res.data;
dispatch({
type: LOGIN,
payload: loginData,
});
} catch (error) {
dispatch({
type: ERROR_LOG,
payload: error.response.data,
});
}
};
export const logOutAction = () => dispatch => {
console.log("inside action")
try {
console.log("inside try")
dispatch({
type: LOGOUT,
});
} catch (error) {
console.log("inside error")
dispatch({
type: ERROR_LOG,
payload: error.response.data,
});
}
};
Types are imported from another file.
export const ERROR_LOG = 'ERROR_LOG';
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
So on login button I am invoking the logicAction which is working fine. Still providing the code:
const handleSubmit = (values) => {
loginAction(values);
};
import and the react-redux connect used as docs said. Same way I tried to add logout to logout button.
onMenuClick = (event) => {
this.props.logOutAction();
}
But the same thing throwing me
Error: Actions must be plain objects. Use custom middleware for async
actions.
From console I can see it throws error immediately as it hits the dispatch . For that no console.log is printed.
I have absolutely no idea what is the problem here.
EDIT:
mapStateToProps in login component where it is working fine:
const mapStateToProps = (state) => ({
loginData: state.loginData,
});
export default connect(mapStateToProps, { loginAction })(Login);
mapStateToProps in header component:
const mapStateToProps = (state) => ({
loginData: state.loginData,
});
export default connect(mapStateToProps, { logOutAction })(AvatarDropdown);
Basically I just copy/paste from login component.
Try to remove the initialState from your store configuration. I don't think you need it and it might be the source of your error. Try to use this:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const middleware = [thunk];
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(...middleware)),
);
export default store;
I was using a framework which has built in store. So there was a conflict between stores.
My project consists of a backend(nodejs , express, mysql) and a frontend (reactjs, redux).
The flow of a rendered component is in a simple redux pattern-
in ComponentDidMount I call an action creator this.props.getResource()
in action creator I use axios to call the backend and dispatch an action in callback like so :
actions.js
export const getResource = () => dispatch => {
axios.get(API_URL/path/to/resource)
.then(res => {
dispatch({
type: SOME_RESOURCE,
payload: res.data
});
})
.catch(e =>
dispatch({
type: ERROR,
payload: e
})
);
};
in reducer I send back to component the state with the new array :
reducers.js
export default function(state = initialState, action) {
switch (action.type) {
case SOME_RESOURCE:
return {
...state,
resources: [...state.resources, action.payload] // add new resource to existing array
};
}
default: return state;
}
}
It is working as it should using REST APIs but now I wish to replace a certain API call with a socket so that data is shown in real-time without needing to refresh the page.
How can I convert above example to use sockets instead of API calls?
This is what I have tried:
Flow starts the same - I call an action creator in ComponentDidMount
I changed the action creator to the following :
actions.js
import io from 'socket.io-client';
const socket = io(); // localhost backend
export const getResource= () => dispatch => {
socket
.on("getResourceEvent", res => {
dispatch({
type: SOME_RESOURCE,
payload: res.data
});
})
.on("onError", e => {
dispatch({
type: ERROR,
payload: e
});
});
};
no changes in reducers.js
This works but with each rendering of the component, the store.getState() gets called 1 additional time. On first render getState() is called 1 time and if I refresh the page I get 2 calls from getState() and so on.
What's causing this behavior and how can I prevent it?
Edit:
store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [ thunk ];
var createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
var store = createStoreWithMiddleware(
rootReducer,
initialState,
applyMiddleware(...middleware)
)
store.subscribe(() => console.log("Store.getState()", store.getState()))
export default store;